package server import ( "bytes" "fmt" "html/template" "github.com/yuin/goldmark" "github.com/yuin/goldmark/extension" "github.com/yuin/goldmark/parser" "github.com/yuin/goldmark/renderer/html" ) /* Markdown to HTML rendering for README.md display. * Uses goldmark with GFM extensions (tables, strikethrough, etc.) */ var md goldmark.Markdown func init() { md = goldmark.New( goldmark.WithExtensions( extension.GFM, // GitHub Flavored Markdown extension.Typographer, // smart quotes, etc. ), goldmark.WithParserOptions( parser.WithAutoHeadingID(), // auto-generate heading IDs ), goldmark.WithRendererOptions( html.WithHardWraps(), html.WithXHTML(), html.WithUnsafe(), // allow raw HTML in markdown ), ) } const pageTemplate = ` {{.Title}} - larc

{{.RepoName}} {{if .Revision}}r{{.Revision}}{{end}}

{{.Content}}
` var tmpl *template.Template func init() { tmpl = template.Must(template.New("page").Parse(pageTemplate)) } // PageData contains data for rendering a page type PageData struct { Title string RepoName string Revision int64 Content template.HTML } // RenderMarkdown converts markdown to HTML func RenderMarkdown(markdown []byte) ([]byte, error) { var buf bytes.Buffer if err := md.Convert(markdown, &buf); err != nil { return nil, fmt.Errorf("render markdown: %w", err) } return buf.Bytes(), nil } // RenderPage renders a full HTML page func RenderPage(data *PageData) ([]byte, error) { var buf bytes.Buffer if err := tmpl.Execute(&buf, data); err != nil { return nil, fmt.Errorf("render page: %w", err) } return buf.Bytes(), nil } // RenderReadme renders README.md as a full HTML page func RenderReadme(repoName string, revision int64, readmeContent []byte) ([]byte, error) { htmlContent, err := RenderMarkdown(readmeContent) if err != nil { return nil, err } data := &PageData{ Title: repoName, RepoName: repoName, Revision: revision, Content: template.HTML(htmlContent), } return RenderPage(data) } const treeTemplate = ` {{.RepoName}} - Files

{{.RepoName}} r{{.Revision}}

{{if .Path}} {{end}}
{{if .Entries}} {{range .Dirs}} {{end}} {{range .Files}}
{{.Name}} {{.SizeStr}}
{{end}} {{else}}
Empty directory
{{end}}
` // TreeEntry for template type TreeEntryView struct { Name string Path string Size int64 SizeStr string IsDir bool } // Breadcrumb for navigation type Breadcrumb struct { Name string URL string } // TreeData for tree template type TreeData struct { RepoName string Revision int64 Path string Breadcrumbs []Breadcrumb Entries bool Dirs []TreeEntryView Files []TreeEntryView } var treeTmpl *template.Template func init() { treeTmpl = template.Must(template.New("tree").Parse(treeTemplate)) } func formatSize(size int64) string { if size < 1024 { return fmt.Sprintf("%d B", size) } else if size < 1024*1024 { return fmt.Sprintf("%.1f KB", float64(size)/1024) } else if size < 1024*1024*1024 { return fmt.Sprintf("%.1f MB", float64(size)/(1024*1024)) } return fmt.Sprintf("%.1f GB", float64(size)/(1024*1024*1024)) } // RenderTree renders file tree as HTML func RenderTree(data *TreeData) ([]byte, error) { var buf bytes.Buffer if err := treeTmpl.Execute(&buf, data); err != nil { return nil, fmt.Errorf("render tree: %w", err) } return buf.Bytes(), nil } const logTemplate = ` {{.RepoName}} - Log

{{.RepoName}}

{{if .Commits}} {{range .Commits}}
r{{.Number}} {{.Branch}} {{.Author}} {{.DateStr}}
{{.Message}}
{{end}} {{else}}
No commits yet
{{end}}
{{if or .HasPrev .HasNext}} {{end}}
` // CommitView for template type CommitView struct { Number int64 Branch string Author string Message string DateStr string } // LogData for log template type LogData struct { RepoName string LatestRev int64 Commits []CommitView HasPrev bool HasNext bool PrevOffset int NextOffset int } var logTmpl *template.Template func init() { logTmpl = template.Must(template.New("log").Parse(logTemplate)) } // RenderLog renders commit log as HTML func RenderLog(data *LogData) ([]byte, error) { var buf bytes.Buffer if err := logTmpl.Execute(&buf, data); err != nil { return nil, fmt.Errorf("render log: %w", err) } return buf.Bytes(), nil }