summaryrefslogtreecommitdiffstats
path: root/helpers/pygments.go
diff options
context:
space:
mode:
Diffstat (limited to 'helpers/pygments.go')
-rw-r--r--helpers/pygments.go402
1 files changed, 0 insertions, 402 deletions
diff --git a/helpers/pygments.go b/helpers/pygments.go
deleted file mode 100644
index 4d7631ced..000000000
--- a/helpers/pygments.go
+++ /dev/null
@@ -1,402 +0,0 @@
-// Copyright 2019 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package helpers
-
-import (
- "bytes"
- "crypto/sha1"
- "fmt"
- "io"
- "io/ioutil"
- "os/exec"
- "path/filepath"
- "regexp"
- "sort"
- "strconv"
- "strings"
-
- "github.com/alecthomas/chroma"
- "github.com/alecthomas/chroma/formatters"
- "github.com/alecthomas/chroma/formatters/html"
- "github.com/alecthomas/chroma/lexers"
- "github.com/alecthomas/chroma/styles"
- bp "github.com/gohugoio/hugo/bufferpool"
-
- "github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/hugofs"
- jww "github.com/spf13/jwalterweatherman"
-)
-
-const pygmentsBin = "pygmentize"
-
-// hasPygments checks to see if Pygments is installed and available
-// on the system.
-func hasPygments() bool {
- if _, err := exec.LookPath(pygmentsBin); err != nil {
- return false
- }
- return true
-}
-
-type highlighters struct {
- cs *ContentSpec
- ignoreCache bool
- cacheDir string
-}
-
-func newHiglighters(cs *ContentSpec) highlighters {
- return highlighters{cs: cs, ignoreCache: cs.Cfg.GetBool("ignoreCache"), cacheDir: cs.Cfg.GetString("cacheDir")}
-}
-
-func (h highlighters) chromaHighlight(code, lang, optsStr string) (string, error) {
- opts, err := h.cs.parsePygmentsOpts(optsStr)
- if err != nil {
- jww.ERROR.Print(err.Error())
- return code, err
- }
-
- style, found := opts["style"]
- if !found || style == "" {
- style = "friendly"
- }
-
- f, err := h.cs.chromaFormatterFromOptions(opts)
- if err != nil {
- jww.ERROR.Print(err.Error())
- return code, err
- }
-
- b := bp.GetBuffer()
- defer bp.PutBuffer(b)
-
- err = chromaHighlight(b, code, lang, style, f)
- if err != nil {
- jww.ERROR.Printf("Highlight failed: %s\nLang: %q\nCode: \n%s", err, lang, code)
- return code, err
- }
-
- return h.injectCodeTag(`<div class="highlight">`+b.String()+"</div>", lang), nil
-}
-
-func (h highlighters) pygmentsHighlight(code, lang, optsStr string) (string, error) {
- options, err := h.cs.createPygmentsOptionsString(optsStr)
-
- if err != nil {
- jww.ERROR.Print(err.Error())
- return code, nil
- }
-
- // Try to read from cache first
- hash := sha1.New()
- io.WriteString(hash, code)
- io.WriteString(hash, lang)
- io.WriteString(hash, options)
-
- fs := hugofs.Os
-
- var cachefile string
-
- if !h.ignoreCache && h.cacheDir != "" {
- cachefile = filepath.Join(h.cacheDir, fmt.Sprintf("pygments-%x", hash.Sum(nil)))
-
- exists, err := Exists(cachefile, fs)
- if err != nil {
- jww.ERROR.Print(err.Error())
- return code, nil
- }
- if exists {
- f, err := fs.Open(cachefile)
- if err != nil {
- jww.ERROR.Print(err.Error())
- return code, nil
- }
-
- s, err := ioutil.ReadAll(f)
- if err != nil {
- jww.ERROR.Print(err.Error())
- return code, nil
- }
-
- return string(s), nil
- }
- }
-
- // No cache file, render and cache it
- var out bytes.Buffer
- var stderr bytes.Buffer
-
- var langOpt string
- if lang == "" {
- langOpt = "-g" // Try guessing the language
- } else {
- langOpt = "-l" + lang
- }
-
- cmd := exec.Command(pygmentsBin, langOpt, "-fhtml", "-O", options)
- cmd.Stdin = strings.NewReader(code)
- cmd.Stdout = &out
- cmd.Stderr = &stderr
-
- if err := cmd.Run(); err != nil {
- jww.ERROR.Print(stderr.String())
- return code, err
- }
-
- str := string(normalizeExternalHelperLineFeeds(out.Bytes()))
-
- str = h.injectCodeTag(str, lang)
-
- if !h.ignoreCache && cachefile != "" {
- // Write cache file
- if err := WriteToDisk(cachefile, strings.NewReader(str), fs); err != nil {
- jww.ERROR.Print(stderr.String())
- }
- }
-
- return str, nil
-}
-
-var preRe = regexp.MustCompile(`(?s)(.*?<pre.*?>)(.*?)(</pre>)`)
-
-func (h highlighters) injectCodeTag(code, lang string) string {
- if lang == "" {
- return code
- }
- codeTag := fmt.Sprintf(`<code class="language-%s" data-lang="%s">`, lang, lang)
- return preRe.ReplaceAllString(code, fmt.Sprintf("$1%s$2</code>$3", codeTag))
-}
-
-func chromaHighlight(w io.Writer, source, lexer, style string, f chroma.Formatter) error {
- l := lexers.Get(lexer)
- if l == nil {
- l = lexers.Analyse(source)
- }
- if l == nil {
- l = lexers.Fallback
- }
- l = chroma.Coalesce(l)
-
- if f == nil {
- f = formatters.Fallback
- }
-
- s := styles.Get(style)
- if s == nil {
- s = styles.Fallback
- }
-
- it, err := l.Tokenise(nil, source)
- if err != nil {
- return err
- }
-
- return f.Format(w, s, it)
-}
-
-var pygmentsKeywords = make(map[string]bool)
-
-func init() {
- pygmentsKeywords["encoding"] = true
- pygmentsKeywords["outencoding"] = true
- pygmentsKeywords["nowrap"] = true
- pygmentsKeywords["full"] = true
- pygmentsKeywords["title"] = true
- pygmentsKeywords["style"] = true
- pygmentsKeywords["noclasses"] = true
- pygmentsKeywords["classprefix"] = true
- pygmentsKeywords["cssclass"] = true
- pygmentsKeywords["cssstyles"] = true
- pygmentsKeywords["prestyles"] = true
- pygmentsKeywords["linenos"] = true
- pygmentsKeywords["hl_lines"] = true
- pygmentsKeywords["linenostart"] = true
- pygmentsKeywords["linenostep"] = true
- pygmentsKeywords["linenospecial"] = true
- pygmentsKeywords["nobackground"] = true
- pygmentsKeywords["lineseparator"] = true
- pygmentsKeywords["lineanchors"] = true
- pygmentsKeywords["linespans"] = true
- pygmentsKeywords["anchorlinenos"] = true
- pygmentsKeywords["startinline"] = true
-}
-
-func parseOptions(defaults map[string]string, in string) (map[string]string, error) {
- in = strings.Trim(in, " ")
- opts := make(map[string]string)
-
- for k, v := range defaults {
- opts[k] = v
- }
-
- if in == "" {
- return opts, nil
- }
-
- for _, v := range strings.Split(in, ",") {
- keyVal := strings.Split(v, "=")
- key := strings.ToLower(strings.Trim(keyVal[0], " "))
- if len(keyVal) != 2 || !pygmentsKeywords[key] {
- return opts, fmt.Errorf("invalid Pygments option: %s", key)
- }
- opts[key] = keyVal[1]
- }
-
- return opts, nil
-}
-
-func createOptionsString(options map[string]string) string {
- var keys []string
- for k := range options {
- keys = append(keys, k)
- }
- sort.Strings(keys)
-
- var optionsStr string
- for i, k := range keys {
- optionsStr += fmt.Sprintf("%s=%s", k, options[k])
- if i < len(options)-1 {
- optionsStr += ","
- }
- }
-
- return optionsStr
-}
-
-func parseDefaultPygmentsOpts(cfg config.Provider) (map[string]string, error) {
- options, err := parseOptions(nil, cfg.GetString("pygmentsOptions"))
- if err != nil {
- return nil, err
- }
-
- if cfg.IsSet("pygmentsStyle") {
- options["style"] = cfg.GetString("pygmentsStyle")
- }
-
- if cfg.IsSet("pygmentsUseClasses") {
- if cfg.GetBool("pygmentsUseClasses") {
- options["noclasses"] = "false"
- } else {
- options["noclasses"] = "true"
- }
-
- }
-
- if _, ok := options["encoding"]; !ok {
- options["encoding"] = "utf8"
- }
-
- return options, nil
-}
-
-func (cs *ContentSpec) chromaFormatterFromOptions(pygmentsOpts map[string]string) (chroma.Formatter, error) {
- var options = []html.Option{html.TabWidth(4)}
-
- if pygmentsOpts["noclasses"] == "false" {
- options = append(options, html.WithClasses())
- }
-
- lineNumbers := pygmentsOpts["linenos"]
- if lineNumbers != "" {
- options = append(options, html.WithLineNumbers())
- if lineNumbers != "inline" {
- options = append(options, html.LineNumbersInTable())
- }
- }
-
- startLineStr := pygmentsOpts["linenostart"]
- var startLine = 1
- if startLineStr != "" {
-
- line, err := strconv.Atoi(strings.TrimSpace(startLineStr))
- if err == nil {
- startLine = line
- options = append(options, html.BaseLineNumber(startLine))
- }
- }
-
- hlLines := pygmentsOpts["hl_lines"]
-
- if hlLines != "" {
- ranges, err := hlLinesToRanges(startLine, hlLines)
-
- if err == nil {
- options = append(options, html.HighlightLines(ranges))
- }
- }
-
- return html.New(options...), nil
-}
-
-func (cs *ContentSpec) parsePygmentsOpts(in string) (map[string]string, error) {
- opts, err := parseOptions(cs.defatultPygmentsOpts, in)
- if err != nil {
- return nil, err
- }
- return opts, nil
-
-}
-
-func (cs *ContentSpec) createPygmentsOptionsString(in string) (string, error) {
- opts, err := cs.parsePygmentsOpts(in)
- if err != nil {
- return "", err
- }
- return createOptionsString(opts), nil
-}
-
-// startLine compansates for https://github.com/alecthomas/chroma/issues/30
-func hlLinesToRanges(startLine int, s string) ([][2]int, error) {
- var ranges [][2]int
- s = strings.TrimSpace(s)
-
- if s == "" {
- return ranges, nil
- }
-
- // Variants:
- // 1 2 3 4
- // 1-2 3-4
- // 1-2 3
- // 1 3-4
- // 1 3-4
- fields := strings.Split(s, " ")
- for _, field := range fields {
- field = strings.TrimSpace(field)
- if field == "" {
- continue
- }
- numbers := strings.Split(field, "-")
- var r [2]int
- first, err := strconv.Atoi(numbers[0])
- if err != nil {
- return ranges, err
- }
- first = first + startLine - 1
- r[0] = first
- if len(numbers) > 1 {
- second, err := strconv.Atoi(numbers[1])
- if err != nil {
- return ranges, err
- }
- second = second + startLine - 1
- r[1] = second
- } else {
- r[1] = first
- }
-
- ranges = append(ranges, r)
- }
- return ranges, nil
-
-}