hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

config.go (6741B)

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