github.com/kyleu/dbaudit@v0.0.2-0.20240321155047-ff2f2c940496/app/controller/cutil/formatter.go (about) 1 // Package cutil - Content managed by Project Forge, see [projectforge.md] for details. 2 package cutil 3 4 import ( 5 "fmt" 6 h "html" 7 "strings" 8 9 "github.com/alecthomas/chroma" 10 "github.com/alecthomas/chroma/formatters/html" 11 "github.com/alecthomas/chroma/lexers" 12 "github.com/alecthomas/chroma/styles" 13 "github.com/pkg/errors" 14 15 "github.com/kyleu/dbaudit/app/util" 16 ) 17 18 var ( 19 lineNums *html.Formatter 20 noLineNums *html.Formatter 21 ) 22 23 func FormatJSON(v any) (string, error) { 24 return FormatLang(util.ToJSON(v), "json") 25 } 26 27 func FormatLang(content string, lang string) (string, error) { 28 l := lexers.Get(lang) 29 return FormatString(content, l) 30 } 31 32 func FormatLangIgnoreErrors(content string, lang string) string { 33 ret, err := FormatLang(content, lang) 34 if err != nil { 35 return fmt.Sprintf("encoding error: %s\n%s", err.Error(), content) 36 } 37 return ret 38 } 39 40 func FormatFilename(content string, filename string) (string, error) { 41 l := lexers.Match(filename) 42 if l == nil { 43 l = lexers.Fallback 44 } 45 return FormatString(content, l) 46 } 47 48 func FormatString(content string, l chroma.Lexer) (string, error) { 49 if l == nil { 50 return "", errors.New("no lexer available for this content") 51 } 52 s := styles.MonokaiLight 53 var f *html.Formatter 54 if strings.Contains(strings.TrimSpace(content), "\n") { 55 if lineNums == nil { 56 lineNums = html.New(html.WithClasses(true), html.WithLineNumbers(true), html.LineNumbersInTable(true)) 57 } 58 f = lineNums 59 } else { 60 if noLineNums == nil { 61 noLineNums = html.New(html.WithClasses(true)) 62 } 63 f = noLineNums 64 } 65 i, err := l.Tokenise(nil, content) 66 if err != nil { 67 return "", errors.Wrap(err, "can't tokenize") 68 } 69 x := &strings.Builder{} 70 err = f.Format(x, s, i) 71 if err != nil { 72 return "", errors.Wrap(err, "can't format") 73 } 74 75 ret := x.String() 76 ret = strings.ReplaceAll(ret, "\n</span>", "<br /></span>") 77 ret = strings.ReplaceAll(ret, "</span>\n", "</span><br />") 78 ret = strings.ReplaceAll(ret, "\n<span", "<br /><span") 79 ret = strings.ReplaceAll(ret, ">\n", ">") 80 if l.Config().Name == "SQL" { 81 ret = strings.ReplaceAll(ret, `<span class="err">$</span>`, `<span class="mi">$</span>`) 82 } 83 ret = strings.Replace(ret, `<td class="lntd"><pre tabindex="0" class="chroma"><span class="lnt">1<br /></span></pre></td>`, "", 1) 84 return ret, nil 85 } 86 87 func FormatMarkdown(s string) (string, error) { 88 match, end := "<pre><code class=\"language-", "</code></pre>" 89 idx := strings.Index(s, match) 90 for idx > -1 { 91 startQuote := idx + len(match) 92 endQuote := strings.Index(s[startQuote:], "\"") 93 lang := s[startQuote : startQuote+endQuote] 94 if lang == "shell" { 95 lang = "bash" 96 } 97 contentStart := startQuote + endQuote + 2 98 contentEnd := strings.Index(s[startQuote:], end) + startQuote 99 content := s[contentStart:contentEnd] 100 content = h.UnescapeString(content) 101 code, err := FormatLang(content, lang) 102 if err != nil { 103 return "", err 104 } 105 s = s[:idx] + code + s[contentEnd+len(end):] 106 idx = strings.Index(s, match) 107 } 108 return s, nil 109 } 110 111 func FormatMarkdownClean(s string, icon string) (string, string, error) { 112 ret, err := FormatMarkdown(s) 113 if err != nil { 114 return "", "", err 115 } 116 title := "" 117 if h1Idx := strings.Index(ret, "<h1>"); h1Idx > -1 { 118 if h1EndIdx := strings.Index(ret, "</h1>"); h1EndIdx > -1 { 119 title = s[h1Idx+4 : h1EndIdx] 120 } 121 ic := fmt.Sprintf(`<svg class="icon" style="width: 20px; height: 20px;"><use xlink:href="#svg-%s" /></svg> `, icon) 122 ret = ret[:h1Idx+4] + ic + ret[h1Idx+4:] 123 ret = strings.ReplaceAll(ret, "<h3>", "<h4>") 124 ret = strings.ReplaceAll(ret, "</h3>", "</h4>") 125 ret = strings.ReplaceAll(ret, "<h2>", "<h4>") 126 ret = strings.ReplaceAll(ret, "</h2>", "</h4>") 127 ret = strings.ReplaceAll(ret, "<h1>", "<h3 style=\"margin-top: 0;\">") 128 ret = strings.ReplaceAll(ret, "</h1>", "</h3>") 129 ret = strings.ReplaceAll(ret, "<a href=\"http", "<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"http") 130 } 131 return title, ret, nil 132 }