so embedded images resolve on the web.
func Markdown(src []byte, ctx MD) (template.HTML, error) {
var buf bytes.Buffer
if err := md.Convert(src, &buf); err != nil {
return "", err
}
out := buf.String()
if ctx.Repo != "" {
out = rewriteRelImages(out, ctx)
}
return template.HTML(out), nil // #nosec G203 -- trusted repo content, rendered by goldmark
}
var imgSrcRe = regexp.MustCompile(`(
]*?\bsrc=")([^"]*)(")`)
// rewriteRelImages points relative
at the repo's raw endpoint.
func rewriteRelImages(htmlStr string, ctx MD) string {
return imgSrcRe.ReplaceAllStringFunc(htmlStr, func(m string) string {
g := imgSrcRe.FindStringSubmatch(m)
dest := g[2]
if !isRelativeURL(dest) {
return m
}
clean := path.Join(ctx.Dir, dest) // resolves ./ and ../ against the file's dir
return g[1] + "/r/" + ctx.Repo + "/raw/" + ctx.Ref + "/" + clean + g[3]
})
}
func isRelativeURL(u string) bool {
if u == "" {
return false
}
for _, p := range []string{"http://", "https://", "//", "/", "data:", "#", "mailto:"} {
if strings.HasPrefix(u, p) {
return false
}
}
return true
}
// Highlight returns syntax-highlighted HTML for code, choosing a lexer by
// filename then content. Binary content yields ok=false so callers can show a
// "binary file" notice instead.
func Highlight(filename string, code []byte) (out template.HTML, ok bool) {
if isBinary(code) {
return "", false
}
lexer := lexers.Match(filename)
if lexer == nil {
lexer = lexers.Analyse(string(code))
}
if lexer == nil {
lexer = lexers.Fallback
}
lexer = chroma.Coalesce(lexer)
iterator, err := lexer.Tokenise(nil, string(code))
if err != nil {
return "", false
}
var buf bytes.Buffer
if err := formatter.Format(&buf, classStyle, iterator); err != nil {
return "", false
}
return template.HTML(buf.String()), true // #nosec G203 -- escaped by chroma formatter
}
// FileDiff is one file's slice of a commit diff: its name, highlighted hunks,
// and add/delete line counts.
type FileDiff struct {
Name string
HTML template.HTML
Added int
Deleted int
Binary bool
}
// SplitDiff breaks a unified diff into per-file sections. Each section's git
// metadata (diff --git / index / ---/+++ lines) is dropped — the filename is
// surfaced separately — and the hunks are highlighted on their own.
func SplitDiff(diff string) []FileDiff {
if strings.TrimSpace(diff) == "" {
return nil
}
lines := strings.Split(diff, "\n")
var files []FileDiff
var cur []string
flush := func() {
if len(cur) == 0 {
return
}
files = append(files, buildFileDiff(cur))
cur = nil
}
for _, ln := range lines {
if strings.HasPrefix(ln, "diff --git ") {
flush()
}
cur = append(cur, ln)
}
flush()
return files
}
func buildFileDiff(block []string) FileDiff {
fd := FileDiff{Name: diffName(block)}
hunk := -1
for i, ln := range block {
switch {
case strings.HasPrefix(ln, "@@") && hunk < 0:
hunk = i
case strings.HasPrefix(ln, "Binary files "), strings.HasPrefix(ln, "GIT binary patch"):
fd.Binary = true
case strings.HasPrefix(ln, "+") && !strings.HasPrefix(ln, "+++"):
fd.Added++
case strings.HasPrefix(ln, "-") && !strings.HasPrefix(ln, "---"):
fd.Deleted++
}
}
if hunk >= 0 {
fd.HTML = HighlightDiff(strings.Join(block[hunk:], "\n"))
}
return fd
}
// diffName extracts a display name from a file block's git header, formatting
// renames as "old → new".
func diffName(block []string) string {
for _, ln := range block {
if strings.HasPrefix(ln, "diff --git ") {
fields := strings.Fields(ln)
if len(fields) >= 4 {
a := strings.TrimPrefix(fields[len(fields)-2], "a/")
b := strings.TrimPrefix(fields[len(fields)-1], "b/")
if a != b {
return a + " → " + b
}
return b
}
}
}
for _, ln := range block {
if strings.HasPrefix(ln, "+++ b/") {
return strings.TrimPrefix(ln, "+++ b/")
}
}
return "diff"
}
// HighlightDiff renders a unified diff with chroma's diff lexer (class-based:
// .gi inserts, .gd deletes, .gu/.gh hunk headers). Falls back to escaped text.
func HighlightDiff(diff string) template.HTML {
lexer := lexers.Get("diff")
if lexer == nil {
return template.HTML("" + template.HTMLEscapeString(diff) + "
") // #nosec G203 -- escaped
}
iterator, err := lexer.Tokenise(nil, diff)
if err != nil {
return template.HTML("" + template.HTMLEscapeString(diff) + "
") // #nosec G203 -- escaped
}
var buf bytes.Buffer
if err := diffFormatter.Format(&buf, classStyle, iterator); err != nil {
return template.HTML("" + template.HTMLEscapeString(diff) + "
") // #nosec G203 -- escaped
}
return template.HTML(buf.String()) // #nosec G203 -- escaped by chroma formatter
}
// IsMarkdown reports whether a filename should be rendered as Markdown.
func IsMarkdown(name string) bool {
switch strings.ToLower(path.Ext(name)) {
case ".md", ".markdown", ".mdown":
return true
}
return false
}
// isBinary uses a NUL-byte heuristic over the first chunk, like git.
func isBinary(b []byte) bool {
const sniff = 8000
if len(b) > sniff {
b = b[:sniff]
}
if bytes.IndexByte(b, 0) >= 0 {
return true
}
return !utf8.Valid(b)
}