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 }