github.com/dominikszabo/hugo-ds-clean@v0.47.1/helpers/pygments_test.go (about) 1 // Copyright 2015 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package helpers 15 16 import ( 17 "fmt" 18 "reflect" 19 "testing" 20 21 "github.com/alecthomas/chroma/formatters/html" 22 23 "github.com/spf13/viper" 24 "github.com/stretchr/testify/require" 25 ) 26 27 func TestParsePygmentsArgs(t *testing.T) { 28 assert := require.New(t) 29 30 for i, this := range []struct { 31 in string 32 pygmentsStyle string 33 pygmentsUseClasses bool 34 expect1 interface{} 35 }{ 36 {"", "foo", true, "encoding=utf8,noclasses=false,style=foo"}, 37 {"style=boo,noclasses=true", "foo", true, "encoding=utf8,noclasses=true,style=boo"}, 38 {"Style=boo, noClasses=true", "foo", true, "encoding=utf8,noclasses=true,style=boo"}, 39 {"noclasses=true", "foo", true, "encoding=utf8,noclasses=true,style=foo"}, 40 {"style=boo", "foo", true, "encoding=utf8,noclasses=false,style=boo"}, 41 {"boo=invalid", "foo", false, false}, 42 {"style", "foo", false, false}, 43 } { 44 45 v := viper.New() 46 v.Set("pygmentsStyle", this.pygmentsStyle) 47 v.Set("pygmentsUseClasses", this.pygmentsUseClasses) 48 spec, err := NewContentSpec(v) 49 assert.NoError(err) 50 51 result1, err := spec.createPygmentsOptionsString(this.in) 52 if b, ok := this.expect1.(bool); ok && !b { 53 if err == nil { 54 t.Errorf("[%d] parsePygmentArgs didn't return an expected error", i) 55 } 56 } else { 57 if err != nil { 58 t.Errorf("[%d] parsePygmentArgs failed: %s", i, err) 59 continue 60 } 61 if result1 != this.expect1 { 62 t.Errorf("[%d] parsePygmentArgs got %v but expected %v", i, result1, this.expect1) 63 } 64 65 } 66 } 67 } 68 69 func TestParseDefaultPygmentsArgs(t *testing.T) { 70 assert := require.New(t) 71 72 expect := "encoding=utf8,noclasses=false,style=foo" 73 74 for i, this := range []struct { 75 in string 76 pygmentsStyle interface{} 77 pygmentsUseClasses interface{} 78 pygmentsOptions string 79 }{ 80 {"", "foo", true, "style=override,noclasses=override"}, 81 {"", nil, nil, "style=foo,noclasses=false"}, 82 {"style=foo,noclasses=false", nil, nil, "style=override,noclasses=override"}, 83 {"style=foo,noclasses=false", "override", false, "style=override,noclasses=override"}, 84 } { 85 v := viper.New() 86 87 v.Set("pygmentsOptions", this.pygmentsOptions) 88 89 if s, ok := this.pygmentsStyle.(string); ok { 90 v.Set("pygmentsStyle", s) 91 } 92 93 if b, ok := this.pygmentsUseClasses.(bool); ok { 94 v.Set("pygmentsUseClasses", b) 95 } 96 97 spec, err := NewContentSpec(v) 98 assert.NoError(err) 99 100 result, err := spec.createPygmentsOptionsString(this.in) 101 if err != nil { 102 t.Errorf("[%d] parsePygmentArgs failed: %s", i, err) 103 continue 104 } 105 if result != expect { 106 t.Errorf("[%d] parsePygmentArgs got %v but expected %v", i, result, expect) 107 } 108 } 109 } 110 111 type chromaInfo struct { 112 classes bool 113 lineNumbers bool 114 lineNumbersInTable bool 115 highlightRangesLen int 116 highlightRangesStr string 117 baseLineNumber int 118 } 119 120 func formatterChromaInfo(f *html.Formatter) chromaInfo { 121 v := reflect.ValueOf(f).Elem() 122 c := chromaInfo{} 123 // Hack: 124 125 c.classes = f.Classes 126 c.lineNumbers = v.FieldByName("lineNumbers").Bool() 127 c.lineNumbersInTable = v.FieldByName("lineNumbersInTable").Bool() 128 c.baseLineNumber = int(v.FieldByName("baseLineNumber").Int()) 129 vv := v.FieldByName("highlightRanges") 130 c.highlightRangesLen = vv.Len() 131 c.highlightRangesStr = fmt.Sprint(vv) 132 133 return c 134 } 135 136 func TestChromaHTMLHighlight(t *testing.T) { 137 assert := require.New(t) 138 139 v := viper.New() 140 v.Set("pygmentsUseClasses", true) 141 spec, err := NewContentSpec(v) 142 assert.NoError(err) 143 144 result, err := spec.Highlight(`echo "Hello"`, "bash", "") 145 assert.NoError(err) 146 147 assert.Contains(result, `<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">echo</span> <span class="s2">"Hello"</span></code></pre></div>`) 148 149 } 150 151 func TestChromaHTMLFormatterFromOptions(t *testing.T) { 152 assert := require.New(t) 153 154 for i, this := range []struct { 155 in string 156 pygmentsStyle interface{} 157 pygmentsUseClasses interface{} 158 pygmentsOptions string 159 assert func(c chromaInfo) 160 }{ 161 {"", "monokai", true, "style=manni,noclasses=true", func(c chromaInfo) { 162 assert.True(c.classes) 163 assert.False(c.lineNumbers) 164 assert.Equal(0, c.highlightRangesLen) 165 166 }}, 167 {"", nil, nil, "style=monokai,noclasses=false", func(c chromaInfo) { 168 assert.True(c.classes) 169 }}, 170 {"linenos=sure,hl_lines=1 2 3", nil, nil, "style=monokai,noclasses=false", func(c chromaInfo) { 171 assert.True(c.classes) 172 assert.True(c.lineNumbers) 173 assert.Equal(3, c.highlightRangesLen) 174 assert.Equal("[[1 1] [2 2] [3 3]]", c.highlightRangesStr) 175 assert.Equal(1, c.baseLineNumber) 176 }}, 177 {"linenos=inline,hl_lines=1,linenostart=4", nil, nil, "style=monokai,noclasses=false", func(c chromaInfo) { 178 assert.True(c.classes) 179 assert.True(c.lineNumbers) 180 assert.False(c.lineNumbersInTable) 181 assert.Equal(1, c.highlightRangesLen) 182 // This compansates for https://github.com/alecthomas/chroma/issues/30 183 assert.Equal("[[4 4]]", c.highlightRangesStr) 184 assert.Equal(4, c.baseLineNumber) 185 }}, 186 {"linenos=table", nil, nil, "style=monokai", func(c chromaInfo) { 187 assert.True(c.lineNumbers) 188 assert.True(c.lineNumbersInTable) 189 }}, 190 {"style=monokai,noclasses=false", nil, nil, "style=manni,noclasses=true", func(c chromaInfo) { 191 assert.True(c.classes) 192 }}, 193 {"style=monokai,noclasses=true", "friendly", false, "style=manni,noclasses=false", func(c chromaInfo) { 194 assert.False(c.classes) 195 }}, 196 } { 197 v := viper.New() 198 199 v.Set("pygmentsOptions", this.pygmentsOptions) 200 201 if s, ok := this.pygmentsStyle.(string); ok { 202 v.Set("pygmentsStyle", s) 203 } 204 205 if b, ok := this.pygmentsUseClasses.(bool); ok { 206 v.Set("pygmentsUseClasses", b) 207 } 208 209 spec, err := NewContentSpec(v) 210 assert.NoError(err) 211 212 opts, err := spec.parsePygmentsOpts(this.in) 213 if err != nil { 214 t.Fatalf("[%d] parsePygmentsOpts failed: %s", i, err) 215 } 216 217 chromaFormatter, err := spec.chromaFormatterFromOptions(opts) 218 if err != nil { 219 t.Fatalf("[%d] chromaFormatterFromOptions failed: %s", i, err) 220 } 221 222 this.assert(formatterChromaInfo(chromaFormatter.(*html.Formatter))) 223 } 224 } 225 226 func TestHlLinesToRanges(t *testing.T) { 227 var zero [][2]int 228 229 for _, this := range []struct { 230 in string 231 startLine int 232 expected interface{} 233 }{ 234 {"", 1, zero}, 235 {"1 4", 1, [][2]int{{1, 1}, {4, 4}}}, 236 {"1 4", 2, [][2]int{{2, 2}, {5, 5}}}, 237 {"1-4 5-8", 1, [][2]int{{1, 4}, {5, 8}}}, 238 {" 1 4 ", 1, [][2]int{{1, 1}, {4, 4}}}, 239 {"1-4 5-8 ", 1, [][2]int{{1, 4}, {5, 8}}}, 240 {"1-4 5", 1, [][2]int{{1, 4}, {5, 5}}}, 241 {"4 5-9", 1, [][2]int{{4, 4}, {5, 9}}}, 242 {" 1 -4 5 - 8 ", 1, true}, 243 {"a b", 1, true}, 244 } { 245 got, err := hlLinesToRanges(this.startLine, this.in) 246 247 if expectErr, ok := this.expected.(bool); ok && expectErr { 248 if err == nil { 249 t.Fatal("No error") 250 } 251 } else if err != nil { 252 t.Fatalf("Got error: %s", err) 253 } else if !reflect.DeepEqual(this.expected, got) { 254 t.Fatalf("Expected\n%v but got\n%v", this.expected, got) 255 } 256 } 257 } 258 259 func BenchmarkChromaHighlight(b *testing.B) { 260 assert := require.New(b) 261 v := viper.New() 262 263 v.Set("pygmentsstyle", "trac") 264 v.Set("pygmentsuseclasses", false) 265 v.Set("pygmentsuseclassic", false) 266 267 code := `// GetTitleFunc returns a func that can be used to transform a string to 268 // title case. 269 // 270 // The supported styles are 271 // 272 // - "Go" (strings.Title) 273 // - "AP" (see https://www.apstylebook.com/) 274 // - "Chicago" (see http://www.chicagomanualofstyle.org/home.html) 275 // 276 // If an unknown or empty style is provided, AP style is what you get. 277 func GetTitleFunc(style string) func(s string) string { 278 switch strings.ToLower(style) { 279 case "go": 280 return strings.Title 281 case "chicago": 282 tc := transform.NewTitleConverter(transform.ChicagoStyle) 283 return tc.Title 284 default: 285 tc := transform.NewTitleConverter(transform.APStyle) 286 return tc.Title 287 } 288 } 289 ` 290 291 spec, err := NewContentSpec(v) 292 assert.NoError(err) 293 294 for i := 0; i < b.N; i++ { 295 _, err := spec.Highlight(code, "go", "linenos=inline,hl_lines=8 15-17") 296 if err != nil { 297 b.Fatal(err) 298 } 299 } 300 }