Files
distributor/internal/transform/markdown/markdown.go

135 lines
3.8 KiB
Go

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(), req.Markdown.CssHref), nil
}
func markdownMode(mode string) string {
if mode == "" {
return transform.MarkdownModeSidecar
}
return mode
}