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  }