package publish import ( "context" "fmt" "gitea.maximumdirect.net/eric/distributor/internal/state" "gitea.maximumdirect.net/eric/distributor/internal/storage" "gitea.maximumdirect.net/eric/distributor/internal/transform" ) func PlanOutputs(ctx context.Context, req Request) ([]Output, error) { var outputs []Output if req.Publish.Source { sourceOutputs, err := PlanSourceOutputs(req) if err != nil { return nil, err } outputs = append(outputs, sourceOutputs...) } if req.Publish.HTML { transformer, err := resolveTransformer(req.Transformers, transform.MarkdownToHTML) if err != nil { return nil, err } generatedOutputs, err := transformer.Generate(ctx, transform.Request{ SourceBundle: req.SourceBundle, SourceBackend: req.SourceBackend, Markdown: transform.MarkdownOptions{ Mode: req.Transform.MarkdownToHTML.Mode, Input: req.Transform.MarkdownToHTML.Input, }, }) if err != nil { return nil, err } if len(generatedOutputs) == 0 { return nil, fmt.Errorf("publish html requested but no markdown source files were found") } for _, generated := range generatedOutputs { outputs = append(outputs, Output{ SourcePath: generated.SourcePath, DestinationPath: generated.Path, Kind: state.OutputKindGenerated, Transform: generated.Transform, Data: generated.Data, SHA256: generated.SHA256, Size: generated.Size, }) } } if err := rejectOutputCollisions(outputs); err != nil { return nil, err } return outputs, nil } func resolveTransformer(resolver TransformerResolver, name string) (transform.Transformer, error) { if resolver == nil { return nil, fmt.Errorf("transformer resolver is required for %s", name) } transformer, ok := resolver.Get(name) if !ok { return nil, fmt.Errorf("transformer %s is not registered", name) } return transformer, nil } func PlanSourceOutputs(req Request) ([]Output, error) { outputs := make([]Output, 0, len(req.SourceBundle.Manifest.Files)) for _, file := range req.SourceBundle.Manifest.Files { if err := storage.ValidatePath(file.Path); err != nil { return nil, fmt.Errorf("destination output path %q: %w", file.Path, err) } outputs = append(outputs, Output{ SourcePath: file.Path, DestinationPath: file.Path, Kind: state.OutputKindSource, SHA256: file.SHA256, Size: file.Size, }) } return outputs, nil } func rejectOutputCollisions(outputs []Output) error { seen := make(map[string]struct{}, len(outputs)) for _, output := range outputs { if err := storage.ValidatePath(output.DestinationPath); err != nil { return fmt.Errorf("destination output path %q: %w", output.DestinationPath, err) } if _, exists := seen[output.DestinationPath]; exists { return fmt.Errorf("destination output path collision: %s", output.DestinationPath) } seen[output.DestinationPath] = struct{}{} } return nil } func (o Output) StateOutputFile() state.OutputFile { return state.OutputFile{ Path: o.DestinationPath, Kind: o.Kind, SourcePath: o.SourcePath, Transform: o.Transform, URL: o.URL, SHA256: o.SHA256, Size: o.Size, } } func (o Output) ManagedPath() string { return o.DestinationPath } func StateOutputFiles(outputs []Output) []state.OutputFile { files := make([]state.OutputFile, 0, len(outputs)) for _, output := range outputs { files = append(files, output.StateOutputFile()) } return files } func ManagedOutputPaths(outputs []Output) []string { paths := make([]string, 0, len(outputs)) for _, output := range outputs { paths = append(paths, output.ManagedPath()) } return paths } func stateOutputManagedPaths(outputs []state.OutputFile) []string { paths := make([]string, 0, len(outputs)) for _, output := range outputs { paths = append(paths, output.Path) } return paths }