github.com/fighterlyt/hugo@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">&#34;Hello&#34;</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  }