summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/content/extras/highlighting.md26
-rw-r--r--helpers/pygments.go96
-rw-r--r--helpers/pygments_test.go42
-rw-r--r--tpl/template_embedded.go8
-rw-r--r--tpl/template_funcs.go4
5 files changed, 149 insertions, 27 deletions
diff --git a/docs/content/extras/highlighting.md b/docs/content/extras/highlighting.md
index 37b9fc394..8296861d4 100644
--- a/docs/content/extras/highlighting.md
+++ b/docs/content/extras/highlighting.md
@@ -44,9 +44,6 @@ Highlighting is carried out via the in-built shortcode `highlight`. `highlight`
closing shortcode.
### Example
-If you want to highlight code, you need to either fence the code with ``` according to GitHub Flavored Markdown or preceed each line with 4 spaces to identify each line as a line of code.
-
-Not doing either will result in the text being rendered as HTML. This will prevent Pygments highlighting from working.
```
{{</* highlight html */>}}
@@ -72,15 +69,28 @@ Not doing either will result in the text being rendered as HTML. This will preve
<span style="color: #f92672">&lt;/div&gt;</span>
<span style="color: #f92672">&lt;/section&gt;</span>
+### Options
+
+Options to control highlighting can be added as a quoted, comma separated key-value list as the second argument in the shortcode. The example below will highlight as language `go` with inline line numbers, with line number 2 and 3 highlighted.
+
+```
+{{</* highlight go "linenos=inline,hl_lines=2 3" */>}}
+var a string
+var b string
+var c string
+var d string
+{{</* / highlight */>}}
+```
+
+Supported keywords: `style`, `encoding`, `noclasses`, `hl_lines`, `linenos`. Note that `style` and `noclasses` will override the similar setting in the global config.
+
+The keywords are the same you would using with Pygments from the command line, see the [Pygments doc](http://pygments.org/docs/) for more info.
+
### Disclaimers
- * **Warning:** Pygments is relatively slow. Expect much longer build times when using server-side highlighting.
+ * Pygments is relatively slow, but Hugo will cache the results to disk.
* Languages available depends on your Pygments installation.
- * We have sought to have the simplest interface possible, which consequently
-limits configuration. An ambitious user is encouraged to extend the current
-functionality to offer more customization.
-
## Client-side
diff --git a/helpers/pygments.go b/helpers/pygments.go
index 8223759c5..74327c6f5 100644
--- a/helpers/pygments.go
+++ b/helpers/pygments.go
@@ -17,15 +17,15 @@ import (
"bytes"
"crypto/sha1"
"fmt"
+ "github.com/spf13/hugo/hugofs"
+ jww "github.com/spf13/jwalterweatherman"
+ "github.com/spf13/viper"
"io"
"io/ioutil"
"os/exec"
"path/filepath"
+ "sort"
"strings"
-
- "github.com/spf13/hugo/hugofs"
- jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
)
const pygmentsBin = "pygmentize"
@@ -40,30 +40,30 @@ func HasPygments() bool {
}
// Highlight takes some code and returns highlighted code.
-func Highlight(code string, lexer string) string {
+func Highlight(code, lang, optsStr string) string {
if !HasPygments() {
jww.WARN.Println("Highlighting requires Pygments to be installed and in the path")
return code
}
- fs := hugofs.OsFs
-
- style := viper.GetString("PygmentsStyle")
+ options, err := parsePygmentsOpts(optsStr)
- noclasses := "true"
- if viper.GetBool("PygmentsUseClasses") {
- noclasses = "false"
+ if err != nil {
+ jww.ERROR.Print(err.Error())
+ return code
}
// Try to read from cache first
hash := sha1.New()
- io.WriteString(hash, lexer)
io.WriteString(hash, code)
- io.WriteString(hash, style)
- io.WriteString(hash, noclasses)
+ io.WriteString(hash, lang)
+ io.WriteString(hash, options)
cachefile := filepath.Join(viper.GetString("CacheDir"), fmt.Sprintf("pygments-%x", hash.Sum(nil)))
+
+ fs := hugofs.OsFs
+
exists, err := Exists(cachefile, fs)
if err != nil {
jww.ERROR.Print(err.Error())
@@ -89,8 +89,7 @@ func Highlight(code string, lexer string) string {
var out bytes.Buffer
var stderr bytes.Buffer
- cmd := exec.Command(pygmentsBin, "-l"+lexer, "-fhtml", "-O",
- fmt.Sprintf("style=%s,noclasses=%s,encoding=utf8", style, noclasses))
+ cmd := exec.Command(pygmentsBin, "-l"+lang, "-fhtml", "-O", options)
cmd.Stdin = strings.NewReader(code)
cmd.Stdout = &out
cmd.Stderr = &stderr
@@ -107,3 +106,68 @@ func Highlight(code string, lexer string) string {
return out.String()
}
+
+var pygmentsKeywords = make(map[string]bool)
+
+func init() {
+ pygmentsKeywords["style"] = true
+ pygmentsKeywords["encoding"] = true
+ pygmentsKeywords["noclasses"] = true
+ pygmentsKeywords["hl_lines"] = true
+ pygmentsKeywords["linenos"] = true
+}
+
+func parsePygmentsOpts(in string) (string, error) {
+
+ in = strings.Trim(in, " ")
+
+ style := viper.GetString("PygmentsStyle")
+
+ noclasses := "true"
+ if viper.GetBool("PygmentsUseClasses") {
+ noclasses = "false"
+ }
+
+ if len(in) == 0 {
+ return fmt.Sprintf("style=%s,noclasses=%s,encoding=utf8", style, noclasses), nil
+ }
+
+ options := make(map[string]string)
+
+ o := strings.Split(in, ",")
+ for _, v := range o {
+ keyVal := strings.Split(v, "=")
+ key := strings.ToLower(strings.Trim(keyVal[0], " "))
+ if len(keyVal) != 2 || !pygmentsKeywords[key] {
+ return "", fmt.Errorf("invalid Pygments option: %s", key)
+ }
+ options[key] = keyVal[1]
+ }
+
+ if _, ok := options["style"]; !ok {
+ options["style"] = style
+ }
+
+ if _, ok := options["noclasses"]; !ok {
+ options["noclasses"] = noclasses
+ }
+
+ if _, ok := options["encoding"]; !ok {
+ options["encoding"] = "utf8"
+ }
+
+ 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, nil
+}
diff --git a/helpers/pygments_test.go b/helpers/pygments_test.go
new file mode 100644
index 000000000..be0c1a7dc
--- /dev/null
+++ b/helpers/pygments_test.go
@@ -0,0 +1,42 @@
+package helpers
+
+import (
+ "github.com/spf13/viper"
+ "testing"
+)
+
+func TestParsePygmentsArgs(t *testing.T) {
+ for i, this := range []struct {
+ in string
+ pygmentsStyle string
+ pygmentsUseClasses bool
+ expect1 interface{}
+ }{
+ {"", "foo", true, "style=foo,noclasses=false,encoding=utf8"},
+ {"style=boo,noclasses=true", "foo", true, "encoding=utf8,noclasses=true,style=boo"},
+ {"Style=boo, noClasses=true", "foo", true, "encoding=utf8,noclasses=true,style=boo"},
+ {"noclasses=true", "foo", true, "encoding=utf8,noclasses=true,style=foo"},
+ {"style=boo", "foo", true, "encoding=utf8,noclasses=false,style=boo"},
+ {"boo=invalid", "foo", false, false},
+ {"style", "foo", false, false},
+ } {
+ viper.Set("PygmentsStyle", this.pygmentsStyle)
+ viper.Set("PygmentsUseClasses", this.pygmentsUseClasses)
+
+ result1, err := parsePygmentsOpts(this.in)
+ if b, ok := this.expect1.(bool); ok && !b {
+ if err == nil {
+ t.Errorf("[%d] parsePygmentArgs didn't return an expected error", i)
+ }
+ } else {
+ if err != nil {
+ t.Errorf("[%d] parsePygmentArgs failed: %s", i, err)
+ continue
+ }
+ if result1 != this.expect1 {
+ t.Errorf("[%d] parsePygmentArgs got %v but expected %v", i, result1, this.expect1)
+ }
+
+ }
+ }
+}
diff --git a/tpl/template_embedded.go b/tpl/template_embedded.go
index 94d54cab4..48de62cb6 100644
--- a/tpl/template_embedded.go
+++ b/tpl/template_embedded.go
@@ -21,7 +21,13 @@ type Tmpl struct {
func (t *GoHTMLTemplate) EmbedShortcodes() {
t.AddInternalShortcode("ref.html", `{{ .Get 0 | ref .Page }}`)
t.AddInternalShortcode("relref.html", `{{ .Get 0 | relref .Page }}`)
- t.AddInternalShortcode("highlight.html", `{{ .Get 0 | highlight .Inner }}`)
+ t.AddInternalShortcode("highlight.html", `
+ {{ if len .Params | eq 2 }}
+ {{ highlight .Inner (.Get 0) (.Get 1) }}
+ {{ else }}
+ {{ highlight .Inner (.Get 0) "" }}
+ {{ end }}
+ `)
t.AddInternalShortcode("test.html", `This is a simple Test`)
t.AddInternalShortcode("figure.html", `<!-- image -->
<figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
diff --git a/tpl/template_funcs.go b/tpl/template_funcs.go
index 996456f9f..ea069b2af 100644
--- a/tpl/template_funcs.go
+++ b/tpl/template_funcs.go
@@ -875,7 +875,7 @@ func ReturnWhenSet(a, k interface{}) interface{} {
return ""
}
-func Highlight(in interface{}, lang string) template.HTML {
+func Highlight(in interface{}, lang, opts string) template.HTML {
var str string
av := reflect.ValueOf(in)
switch av.Kind() {
@@ -883,7 +883,7 @@ func Highlight(in interface{}, lang string) template.HTML {
str = av.String()
}
- return template.HTML(helpers.Highlight(html.UnescapeString(str), lang))
+ return template.HTML(helpers.Highlight(html.UnescapeString(str), lang, opts))
}
var markdownTrimPrefix = []byte("<p>")