1 package main 2 3 import ( 4 "bytes" 5 "github.com/PuerkitoBio/goquery" 6 "github.com/alecthomas/chroma" 7 "github.com/alecthomas/chroma/styles" 8 "github.com/alecthomas/chroma/lexers" 9 "github.com/alecthomas/chroma/formatters/html" 10 "io" 11 "io/ioutil" 12 "log" 13 "os" 14 "regexp" 15 "strings" 16 "fmt" 17 ) 18 19 // print CSS needed for colorizing the code parts 20 func printCSS(w io.Writer, formatter, style string) { 21 f := html.New(html.WithClasses(true), html.TabWidth(4)) 22 23 s := styles.Get(style) 24 if s == nil { 25 s = styles.Fallback 26 } 27 28 println(f.WriteCSS(w, s)) 29 } 30 31 // Highlight some text. 32 // Lexer, formatter and style may be empty, in which case a best-effort is made. 33 func Highlight(w io.Writer, source, lexer, formatter, style string) error { 34 // Determine lexer. 35 l := lexers.Get(lexer) 36 if l == nil { 37 l = lexers.Analyse(source) 38 } 39 if l == nil { 40 l = lexers.Fallback 41 } 42 l = chroma.Coalesce(l) 43 44 f := html.New(html.WithClasses(true), html.TabWidth(4)) 45 46 // Determine style. 47 s := styles.Get(style) 48 if s == nil { 49 s = styles.Fallback 50 } 51 52 it, err := l.Tokenise(nil, source) 53 if err != nil { 54 return err 55 } 56 return f.Format(w, s, it) 57 } 58 59 func replaceCodeParts(mdFile []byte) (string, error) { 60 byteReader := bytes.NewReader(mdFile) 61 doc, err := goquery.NewDocumentFromReader(byteReader) 62 if err != nil { 63 return "", err 64 } 65 66 re := regexp.MustCompile("(<pre><code class=\"language-).*?(\">)") 67 rp := strings.NewReplacer("<pre><code class=\"language-", "", "\">", "") 68 69 langs := re.FindAllString(string(mdFile), -1) 70 71 // find code-parts via css selector and replace them with highlighted versions 72 doc.Find("pre").Each(func(i int, s *goquery.Selection) { 73 lang := rp.Replace(string(langs[i])) 74 75 if lang == "console" { 76 lang = "bash" 77 } 78 79 buf := new(bytes.Buffer) 80 81 err = Highlight(buf, s.Text(), lang, "html", "monokai") 82 if err != nil { 83 log.Fatal(err) 84 } 85 86 s.ReplaceWithHtml(string(buf.String())) 87 }) 88 new, err := doc.Html() 89 if err != nil { 90 return "", err 91 } 92 93 // remove unnecessarily added html tags 94 new = strings.Replace(new, "<html><head></head><body>", "", 1) 95 96 return new, nil 97 } 98 99 func main() { 100 if len(os.Args) != 2 { 101 log.Fatal("No file name provided") 102 } 103 104 args := os.Args[1:] 105 html, err := ioutil.ReadFile(args[0]) 106 if err != nil { 107 log.Fatal(err) 108 } 109 110 replaced, err := replaceCodeParts(html) 111 if err != nil { 112 log.Fatal(err) 113 } 114 115 fmt.Println(replaced) 116 // printCSS(os.Stdout, "html", "gruvbox") 117 }