package markdown import ( "bytes" "context" "fmt" "strings" "github.com/yuin/goldmark" "gitea.maximumdirect.net/eric/distributor/internal/bundle" "gitea.maximumdirect.net/eric/distributor/internal/storage" "gitea.maximumdirect.net/eric/distributor/internal/transform" ) type Transformer struct { renderer goldmark.Markdown } func New() *Transformer { return &Transformer{renderer: goldmark.New()} } func (t *Transformer) Generate(ctx context.Context, req transform.Request) ([]transform.Output, error) { if t.renderer == nil { t.renderer = goldmark.New() } switch markdownMode(req.Markdown.Mode) { case transform.MarkdownModeSidecar: return t.generateSidecars(ctx, req) case transform.MarkdownModeIndex: return t.generateIndex(ctx, req) default: return nil, fmt.Errorf("markdown mode %q is not supported", req.Markdown.Mode) } } func (t *Transformer) generateSidecars(ctx context.Context, req transform.Request) ([]transform.Output, error) { var outputs []transform.Output for _, file := range req.SourceBundle.Manifest.Files { if !strings.HasSuffix(file.Path, ".md") { continue } html, err := t.render(ctx, req, file.Path) if err != nil { return nil, err } outputPath := strings.TrimSuffix(file.Path, ".md") + ".html" outputs = append(outputs, transform.Output{ Path: outputPath, SourcePath: file.Path, Transform: transform.MarkdownToHTML, Data: html, SHA256: bundle.FileDigest(html), Size: int64(len(html)), }) } return outputs, nil } func (t *Transformer) generateIndex(ctx context.Context, req transform.Request) ([]transform.Output, error) { input, err := selectIndexInput(req) if err != nil { return nil, err } html, err := t.render(ctx, req, input) if err != nil { return nil, err } return []transform.Output{{ Path: "index.html", SourcePath: input, Transform: transform.MarkdownToHTML, Data: html, SHA256: bundle.FileDigest(html), Size: int64(len(html)), }}, nil } func selectIndexInput(req transform.Request) (string, error) { if req.Markdown.Input != "" { if err := storage.ValidatePath(req.Markdown.Input); err != nil { return "", fmt.Errorf("markdown input %q: %w", req.Markdown.Input, err) } for _, file := range req.SourceBundle.Manifest.Files { if file.Path != req.Markdown.Input { continue } if !strings.HasSuffix(file.Path, ".md") { return "", fmt.Errorf("markdown input %q must end in .md", req.Markdown.Input) } return file.Path, nil } return "", fmt.Errorf("markdown input %q is not listed in the source manifest", req.Markdown.Input) } var markdownFiles []string for _, file := range req.SourceBundle.Manifest.Files { if strings.HasSuffix(file.Path, ".md") { markdownFiles = append(markdownFiles, file.Path) } } switch len(markdownFiles) { case 0: return "", fmt.Errorf("markdown index mode requires one markdown source file or transform.markdown_to_html.input") case 1: return markdownFiles[0], nil default: return "", fmt.Errorf("markdown index mode found multiple markdown source files; set transform.markdown_to_html.input") } } func (t *Transformer) render(ctx context.Context, req transform.Request, sourceFile string) ([]byte, error) { sourcePath, err := storage.Join(req.SourceBundle.RootRelativePath, sourceFile) if err != nil { return nil, err } data, err := req.SourceBackend.ReadFile(ctx, sourcePath) if err != nil { return nil, fmt.Errorf("read markdown source %q: %w", sourceFile, err) } var rendered bytes.Buffer if err := t.renderer.Convert(data, &rendered); err != nil { return nil, fmt.Errorf("render markdown source %q: %w", sourceFile, err) } return wrapHTML(rendered.Bytes()), nil } func markdownMode(mode string) string { if mode == "" { return transform.MarkdownModeSidecar } return mode }