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
`
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
{{if .Path}}
{{end}}
{{if .Entries}}
{{range .Dirs}}
{{end}}
{{range .Files}}
{{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
{{if .Commits}}
{{range .Commits}}
{{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
}