github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/markup/highlight/config.go (about)

     1  // Copyright 2019 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 highlight provides code highlighting.
    15  package highlight
    16  
    17  import (
    18  	"fmt"
    19  	"strconv"
    20  	"strings"
    21  
    22  	"github.com/alecthomas/chroma/formatters/html"
    23  	"github.com/spf13/cast"
    24  
    25  	"github.com/gohugoio/hugo/config"
    26  
    27  	"github.com/mitchellh/mapstructure"
    28  )
    29  
    30  var DefaultConfig = Config{
    31  	// The highlighter style to use.
    32  	// See https://xyproto.github.io/splash/docs/all.html
    33  	Style:              "monokai",
    34  	LineNoStart:        1,
    35  	CodeFences:         true,
    36  	NoClasses:          true,
    37  	LineNumbersInTable: true,
    38  	TabWidth:           4,
    39  }
    40  
    41  //
    42  type Config struct {
    43  	Style string
    44  
    45  	CodeFences bool
    46  
    47  	// Use inline CSS styles.
    48  	NoClasses bool
    49  
    50  	// No highlighting.
    51  	NoHl bool
    52  
    53  	// When set, line numbers will be printed.
    54  	LineNos            bool
    55  	LineNumbersInTable bool
    56  
    57  	// When set, add links to line numbers
    58  	AnchorLineNos bool
    59  	LineAnchors   string
    60  
    61  	// Start the line numbers from this value (default is 1).
    62  	LineNoStart int
    63  
    64  	// A space separated list of line numbers, e.g. “3-8 10-20”.
    65  	Hl_Lines string
    66  
    67  	// A parsed and ready to use list of line ranges.
    68  	HL_lines_parsed [][2]int
    69  
    70  	// TabWidth sets the number of characters for a tab. Defaults to 4.
    71  	TabWidth int
    72  
    73  	GuessSyntax bool
    74  }
    75  
    76  func (cfg Config) ToHTMLOptions() []html.Option {
    77  	var lineAnchors string
    78  	if cfg.LineAnchors != "" {
    79  		lineAnchors = cfg.LineAnchors + "-"
    80  	}
    81  	options := []html.Option{
    82  		html.TabWidth(cfg.TabWidth),
    83  		html.WithLineNumbers(cfg.LineNos),
    84  		html.BaseLineNumber(cfg.LineNoStart),
    85  		html.LineNumbersInTable(cfg.LineNumbersInTable),
    86  		html.WithClasses(!cfg.NoClasses),
    87  		html.LinkableLineNumbers(cfg.AnchorLineNos, lineAnchors),
    88  	}
    89  
    90  	if cfg.Hl_Lines != "" || cfg.HL_lines_parsed != nil {
    91  		var ranges [][2]int
    92  		if cfg.HL_lines_parsed != nil {
    93  			ranges = cfg.HL_lines_parsed
    94  		} else {
    95  			var err error
    96  			ranges, err = hlLinesToRanges(cfg.LineNoStart, cfg.Hl_Lines)
    97  			if err != nil {
    98  				ranges = nil
    99  			}
   100  		}
   101  
   102  		if ranges != nil {
   103  			options = append(options, html.HighlightLines(ranges))
   104  		}
   105  	}
   106  
   107  	return options
   108  }
   109  
   110  func applyOptions(opts interface{}, cfg *Config) error {
   111  	if opts == nil {
   112  		return nil
   113  	}
   114  	switch vv := opts.(type) {
   115  	case map[string]interface{}:
   116  		return applyOptionsFromMap(vv, cfg)
   117  	case string:
   118  		return applyOptionsFromString(vv, cfg)
   119  	}
   120  	return nil
   121  }
   122  
   123  func applyOptionsFromString(opts string, cfg *Config) error {
   124  	optsm, err := parseHightlightOptions(opts)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	return mapstructure.WeakDecode(optsm, cfg)
   129  }
   130  
   131  func applyOptionsFromMap(optsm map[string]interface{}, cfg *Config) error {
   132  	normalizeHighlightOptions(optsm)
   133  	return mapstructure.WeakDecode(optsm, cfg)
   134  }
   135  
   136  // ApplyLegacyConfig applies legacy config from back when we had
   137  // Pygments.
   138  func ApplyLegacyConfig(cfg config.Provider, conf *Config) error {
   139  	if conf.Style == DefaultConfig.Style {
   140  		if s := cfg.GetString("pygmentsStyle"); s != "" {
   141  			conf.Style = s
   142  		}
   143  	}
   144  
   145  	if conf.NoClasses == DefaultConfig.NoClasses && cfg.IsSet("pygmentsUseClasses") {
   146  		conf.NoClasses = !cfg.GetBool("pygmentsUseClasses")
   147  	}
   148  
   149  	if conf.CodeFences == DefaultConfig.CodeFences && cfg.IsSet("pygmentsCodeFences") {
   150  		conf.CodeFences = cfg.GetBool("pygmentsCodeFences")
   151  	}
   152  
   153  	if conf.GuessSyntax == DefaultConfig.GuessSyntax && cfg.IsSet("pygmentsCodefencesGuessSyntax") {
   154  		conf.GuessSyntax = cfg.GetBool("pygmentsCodefencesGuessSyntax")
   155  	}
   156  
   157  	if cfg.IsSet("pygmentsOptions") {
   158  		if err := applyOptionsFromString(cfg.GetString("pygmentsOptions"), conf); err != nil {
   159  			return err
   160  		}
   161  	}
   162  
   163  	return nil
   164  }
   165  
   166  func parseHightlightOptions(in string) (map[string]interface{}, error) {
   167  	in = strings.Trim(in, " ")
   168  	opts := make(map[string]interface{})
   169  
   170  	if in == "" {
   171  		return opts, nil
   172  	}
   173  
   174  	for _, v := range strings.Split(in, ",") {
   175  		keyVal := strings.Split(v, "=")
   176  		key := strings.ToLower(strings.Trim(keyVal[0], " "))
   177  		if len(keyVal) != 2 {
   178  			return opts, fmt.Errorf("invalid Highlight option: %s", key)
   179  		}
   180  		opts[key] = keyVal[1]
   181  
   182  	}
   183  
   184  	normalizeHighlightOptions(opts)
   185  
   186  	return opts, nil
   187  }
   188  
   189  func normalizeHighlightOptions(m map[string]interface{}) {
   190  	if m == nil {
   191  		return
   192  	}
   193  
   194  	const (
   195  		lineNosKey    = "linenos"
   196  		hlLinesKey    = "hl_lines"
   197  		linosStartKey = "linenostart"
   198  		noHlKey       = "nohl"
   199  	)
   200  
   201  	baseLineNumber := 1
   202  	if v, ok := m[linosStartKey]; ok {
   203  		baseLineNumber = cast.ToInt(v)
   204  	}
   205  
   206  	for k, v := range m {
   207  		switch k {
   208  		case noHlKey:
   209  			m[noHlKey] = cast.ToBool(v)
   210  		case lineNosKey:
   211  			if v == "table" || v == "inline" {
   212  				m["lineNumbersInTable"] = v == "table"
   213  			}
   214  			if vs, ok := v.(string); ok {
   215  				m[k] = vs != "false"
   216  			}
   217  
   218  		case hlLinesKey:
   219  			if hlRanges, ok := v.([][2]int); ok {
   220  				for i := range hlRanges {
   221  					hlRanges[i][0] += baseLineNumber
   222  					hlRanges[i][1] += baseLineNumber
   223  				}
   224  				delete(m, k)
   225  				m[k+"_parsed"] = hlRanges
   226  			}
   227  		}
   228  	}
   229  }
   230  
   231  // startLine compensates for https://github.com/alecthomas/chroma/issues/30
   232  func hlLinesToRanges(startLine int, s string) ([][2]int, error) {
   233  	var ranges [][2]int
   234  	s = strings.TrimSpace(s)
   235  
   236  	if s == "" {
   237  		return ranges, nil
   238  	}
   239  
   240  	// Variants:
   241  	// 1 2 3 4
   242  	// 1-2 3-4
   243  	// 1-2 3
   244  	// 1 3-4
   245  	// 1    3-4
   246  	fields := strings.Split(s, " ")
   247  	for _, field := range fields {
   248  		field = strings.TrimSpace(field)
   249  		if field == "" {
   250  			continue
   251  		}
   252  		numbers := strings.Split(field, "-")
   253  		var r [2]int
   254  		first, err := strconv.Atoi(numbers[0])
   255  		if err != nil {
   256  			return ranges, err
   257  		}
   258  		first = first + startLine - 1
   259  		r[0] = first
   260  		if len(numbers) > 1 {
   261  			second, err := strconv.Atoi(numbers[1])
   262  			if err != nil {
   263  				return ranges, err
   264  			}
   265  			second = second + startLine - 1
   266  			r[1] = second
   267  		} else {
   268  			r[1] = first
   269  		}
   270  
   271  		ranges = append(ranges, r)
   272  	}
   273  	return ranges, nil
   274  }