package renderer
import (
"path/filepath"
"strings"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
type linkTransformer struct{}
func (t *linkTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
link, ok := n.(*ast.Link)
if !ok {
return ast.WalkContinue, nil
}
dest := string(link.Destination)
if isRelativeMarkdown(dest) {
newDest := modifyLink(dest)
link.Destination = []byte(newDest)
}
return ast.WalkContinue, nil
})
}
func isRelativeMarkdown(dest string) bool {
// Skip absolute URLs
if strings.Contains(dest, "://") || strings.HasPrefix(dest, "/") || strings.HasPrefix(dest, "mailto:") || strings.HasPrefix(dest, "tel:") {
return false
}
// Skip anchors
if strings.HasPrefix(dest, "#") {
return false
}
// It's relative. Check if it's a markdown file or extensionless
ext := filepath.Ext(dest)
if ext == "" || strings.ToLower(ext) == ".md" {
return true
}
return false
}
func modifyLink(dest string) string {
destLower := strings.ToLower(dest)
// Handle readme.md -> index.html
if strings.HasSuffix(destLower, "readme.md") {
// Ensure we handle "readme.md" and "path/to/readme.md"
if destLower == "readme.md" {
return "index.html"
}
if strings.HasSuffix(destLower, "/readme.md") {
return dest[:len(dest)-9] + "index.html"
}
}
// Handle other .md files -> .md.html
if strings.HasSuffix(destLower, ".md") {
return dest + ".html"
}
// Handle extensionless links -> .html
if filepath.Ext(dest) == "" {
return dest + ".html"
}
return dest
}
type linkExtension struct{}
func (e *linkExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(
parser.WithASTTransformers(
util.Prioritized(&linkTransformer{}, 100),
),
)
}
func NewLinkExtension() goldmark.Extender {
return &linkExtension{}
}