mirror of
https://github.com/gohugoio/hugo.git
synced 2025-03-21 02:02:26 +00:00
parent
be540f5b8f
commit
be24457acf
5 changed files with 149 additions and 27 deletions
|
@ -44,9 +44,6 @@ Highlighting is carried out via the in-built shortcode `highlight`. `highlight`
|
||||||
closing shortcode.
|
closing shortcode.
|
||||||
|
|
||||||
### Example
|
### 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 */>}}
|
{{</* 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"></div></span>
|
<span style="color: #f92672"></div></span>
|
||||||
<span style="color: #f92672"></section></span>
|
<span style="color: #f92672"></section></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
|
### 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.
|
* 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
|
## Client-side
|
||||||
|
|
||||||
|
|
|
@ -17,15 +17,15 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/spf13/hugo/hugofs"
|
||||||
|
jww "github.com/spf13/jwalterweatherman"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/hugo/hugofs"
|
|
||||||
jww "github.com/spf13/jwalterweatherman"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const pygmentsBin = "pygmentize"
|
const pygmentsBin = "pygmentize"
|
||||||
|
@ -40,30 +40,30 @@ func HasPygments() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight takes some code and returns highlighted code.
|
// Highlight takes some code and returns highlighted code.
|
||||||
func Highlight(code string, lexer string) string {
|
func Highlight(code, lang, optsStr string) string {
|
||||||
|
|
||||||
if !HasPygments() {
|
if !HasPygments() {
|
||||||
jww.WARN.Println("Highlighting requires Pygments to be installed and in the path")
|
jww.WARN.Println("Highlighting requires Pygments to be installed and in the path")
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
fs := hugofs.OsFs
|
options, err := parsePygmentsOpts(optsStr)
|
||||||
|
|
||||||
style := viper.GetString("PygmentsStyle")
|
if err != nil {
|
||||||
|
jww.ERROR.Print(err.Error())
|
||||||
noclasses := "true"
|
return code
|
||||||
if viper.GetBool("PygmentsUseClasses") {
|
|
||||||
noclasses = "false"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to read from cache first
|
// Try to read from cache first
|
||||||
hash := sha1.New()
|
hash := sha1.New()
|
||||||
io.WriteString(hash, lexer)
|
|
||||||
io.WriteString(hash, code)
|
io.WriteString(hash, code)
|
||||||
io.WriteString(hash, style)
|
io.WriteString(hash, lang)
|
||||||
io.WriteString(hash, noclasses)
|
io.WriteString(hash, options)
|
||||||
|
|
||||||
cachefile := filepath.Join(viper.GetString("CacheDir"), fmt.Sprintf("pygments-%x", hash.Sum(nil)))
|
cachefile := filepath.Join(viper.GetString("CacheDir"), fmt.Sprintf("pygments-%x", hash.Sum(nil)))
|
||||||
|
|
||||||
|
fs := hugofs.OsFs
|
||||||
|
|
||||||
exists, err := Exists(cachefile, fs)
|
exists, err := Exists(cachefile, fs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jww.ERROR.Print(err.Error())
|
jww.ERROR.Print(err.Error())
|
||||||
|
@ -89,8 +89,7 @@ func Highlight(code string, lexer string) string {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
var stderr bytes.Buffer
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
cmd := exec.Command(pygmentsBin, "-l"+lexer, "-fhtml", "-O",
|
cmd := exec.Command(pygmentsBin, "-l"+lang, "-fhtml", "-O", options)
|
||||||
fmt.Sprintf("style=%s,noclasses=%s,encoding=utf8", style, noclasses))
|
|
||||||
cmd.Stdin = strings.NewReader(code)
|
cmd.Stdin = strings.NewReader(code)
|
||||||
cmd.Stdout = &out
|
cmd.Stdout = &out
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
|
@ -107,3 +106,68 @@ func Highlight(code string, lexer string) string {
|
||||||
|
|
||||||
return out.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
|
||||||
|
}
|
||||||
|
|
42
helpers/pygments_test.go
Normal file
42
helpers/pygments_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,7 +21,13 @@ type Tmpl struct {
|
||||||
func (t *GoHTMLTemplate) EmbedShortcodes() {
|
func (t *GoHTMLTemplate) EmbedShortcodes() {
|
||||||
t.AddInternalShortcode("ref.html", `{{ .Get 0 | ref .Page }}`)
|
t.AddInternalShortcode("ref.html", `{{ .Get 0 | ref .Page }}`)
|
||||||
t.AddInternalShortcode("relref.html", `{{ .Get 0 | relref .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("test.html", `This is a simple Test`)
|
||||||
t.AddInternalShortcode("figure.html", `<!-- image -->
|
t.AddInternalShortcode("figure.html", `<!-- image -->
|
||||||
<figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
|
<figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
|
||||||
|
|
|
@ -875,7 +875,7 @@ func ReturnWhenSet(a, k interface{}) interface{} {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func Highlight(in interface{}, lang string) template.HTML {
|
func Highlight(in interface{}, lang, opts string) template.HTML {
|
||||||
var str string
|
var str string
|
||||||
av := reflect.ValueOf(in)
|
av := reflect.ValueOf(in)
|
||||||
switch av.Kind() {
|
switch av.Kind() {
|
||||||
|
@ -883,7 +883,7 @@ func Highlight(in interface{}, lang string) template.HTML {
|
||||||
str = av.String()
|
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>")
|
var markdownTrimPrefix = []byte("<p>")
|
||||||
|
|
Loading…
Reference in a new issue