github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/tpl/strings/strings.go (about) 1 // Copyright 2017 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 strings provides template functions for manipulating strings. 15 package strings 16 17 import ( 18 "errors" 19 "html/template" 20 "regexp" 21 "strings" 22 "unicode/utf8" 23 24 "github.com/gohugoio/hugo/deps" 25 "github.com/gohugoio/hugo/helpers" 26 27 _errors "github.com/pkg/errors" 28 "github.com/spf13/cast" 29 ) 30 31 // New returns a new instance of the strings-namespaced template functions. 32 func New(d *deps.Deps) *Namespace { 33 titleCaseStyle := d.Cfg.GetString("titleCaseStyle") 34 titleFunc := helpers.GetTitleFunc(titleCaseStyle) 35 return &Namespace{deps: d, titleFunc: titleFunc} 36 } 37 38 // Namespace provides template functions for the "strings" namespace. 39 // Most functions mimic the Go stdlib, but the order of the parameters may be 40 // different to ease their use in the Go template system. 41 type Namespace struct { 42 titleFunc func(s string) string 43 deps *deps.Deps 44 } 45 46 // CountRunes returns the number of runes in s, excluding whitespace. 47 func (ns *Namespace) CountRunes(s interface{}) (int, error) { 48 ss, err := cast.ToStringE(s) 49 if err != nil { 50 return 0, _errors.Wrap(err, "Failed to convert content to string") 51 } 52 53 counter := 0 54 for _, r := range helpers.StripHTML(ss) { 55 if !helpers.IsWhitespace(r) { 56 counter++ 57 } 58 } 59 60 return counter, nil 61 } 62 63 // RuneCount returns the number of runes in s. 64 func (ns *Namespace) RuneCount(s interface{}) (int, error) { 65 ss, err := cast.ToStringE(s) 66 if err != nil { 67 return 0, _errors.Wrap(err, "Failed to convert content to string") 68 } 69 return utf8.RuneCountInString(ss), nil 70 } 71 72 // CountWords returns the approximate word count in s. 73 func (ns *Namespace) CountWords(s interface{}) (int, error) { 74 ss, err := cast.ToStringE(s) 75 if err != nil { 76 return 0, _errors.Wrap(err, "Failed to convert content to string") 77 } 78 79 isCJKLanguage, err := regexp.MatchString(`\p{Han}|\p{Hangul}|\p{Hiragana}|\p{Katakana}`, ss) 80 if err != nil { 81 return 0, _errors.Wrap(err, "Failed to match regex pattern against string") 82 } 83 84 if !isCJKLanguage { 85 return len(strings.Fields(helpers.StripHTML((ss)))), nil 86 } 87 88 counter := 0 89 for _, word := range strings.Fields(helpers.StripHTML(ss)) { 90 runeCount := utf8.RuneCountInString(word) 91 if len(word) == runeCount { 92 counter++ 93 } else { 94 counter += runeCount 95 } 96 } 97 98 return counter, nil 99 } 100 101 // Count counts the number of non-overlapping instances of substr in s. 102 // If substr is an empty string, Count returns 1 + the number of Unicode code points in s. 103 func (ns *Namespace) Count(substr, s interface{}) (int, error) { 104 substrs, err := cast.ToStringE(substr) 105 if err != nil { 106 return 0, _errors.Wrap(err, "Failed to convert substr to string") 107 } 108 ss, err := cast.ToStringE(s) 109 if err != nil { 110 return 0, _errors.Wrap(err, "Failed to convert s to string") 111 } 112 return strings.Count(ss, substrs), nil 113 } 114 115 // Chomp returns a copy of s with all trailing newline characters removed. 116 func (ns *Namespace) Chomp(s interface{}) (interface{}, error) { 117 ss, err := cast.ToStringE(s) 118 if err != nil { 119 return "", err 120 } 121 122 res := strings.TrimRight(ss, "\r\n") 123 switch s.(type) { 124 case template.HTML: 125 return template.HTML(res), nil 126 default: 127 return res, nil 128 } 129 } 130 131 // Contains reports whether substr is in s. 132 func (ns *Namespace) Contains(s, substr interface{}) (bool, error) { 133 ss, err := cast.ToStringE(s) 134 if err != nil { 135 return false, err 136 } 137 138 su, err := cast.ToStringE(substr) 139 if err != nil { 140 return false, err 141 } 142 143 return strings.Contains(ss, su), nil 144 } 145 146 // ContainsAny reports whether any Unicode code points in chars are within s. 147 func (ns *Namespace) ContainsAny(s, chars interface{}) (bool, error) { 148 ss, err := cast.ToStringE(s) 149 if err != nil { 150 return false, err 151 } 152 153 sc, err := cast.ToStringE(chars) 154 if err != nil { 155 return false, err 156 } 157 158 return strings.ContainsAny(ss, sc), nil 159 } 160 161 // HasPrefix tests whether the input s begins with prefix. 162 func (ns *Namespace) HasPrefix(s, prefix interface{}) (bool, error) { 163 ss, err := cast.ToStringE(s) 164 if err != nil { 165 return false, err 166 } 167 168 sx, err := cast.ToStringE(prefix) 169 if err != nil { 170 return false, err 171 } 172 173 return strings.HasPrefix(ss, sx), nil 174 } 175 176 // HasSuffix tests whether the input s begins with suffix. 177 func (ns *Namespace) HasSuffix(s, suffix interface{}) (bool, error) { 178 ss, err := cast.ToStringE(s) 179 if err != nil { 180 return false, err 181 } 182 183 sx, err := cast.ToStringE(suffix) 184 if err != nil { 185 return false, err 186 } 187 188 return strings.HasSuffix(ss, sx), nil 189 } 190 191 // Replace returns a copy of the string s with all occurrences of old replaced 192 // with new. The number of replacements can be limited with an optional fourth 193 // parameter. 194 func (ns *Namespace) Replace(s, old, new interface{}, limit ...interface{}) (string, error) { 195 ss, err := cast.ToStringE(s) 196 if err != nil { 197 return "", err 198 } 199 200 so, err := cast.ToStringE(old) 201 if err != nil { 202 return "", err 203 } 204 205 sn, err := cast.ToStringE(new) 206 if err != nil { 207 return "", err 208 } 209 210 if len(limit) == 0 { 211 return strings.ReplaceAll(ss, so, sn), nil 212 } 213 214 lim, err := cast.ToIntE(limit[0]) 215 if err != nil { 216 return "", err 217 } 218 219 return strings.Replace(ss, so, sn, lim), nil 220 } 221 222 // SliceString slices a string by specifying a half-open range with 223 // two indices, start and end. 1 and 4 creates a slice including elements 1 through 3. 224 // The end index can be omitted, it defaults to the string's length. 225 func (ns *Namespace) SliceString(a interface{}, startEnd ...interface{}) (string, error) { 226 aStr, err := cast.ToStringE(a) 227 if err != nil { 228 return "", err 229 } 230 231 var argStart, argEnd int 232 233 argNum := len(startEnd) 234 235 if argNum > 0 { 236 if argStart, err = cast.ToIntE(startEnd[0]); err != nil { 237 return "", errors.New("start argument must be integer") 238 } 239 } 240 if argNum > 1 { 241 if argEnd, err = cast.ToIntE(startEnd[1]); err != nil { 242 return "", errors.New("end argument must be integer") 243 } 244 } 245 246 if argNum > 2 { 247 return "", errors.New("too many arguments") 248 } 249 250 asRunes := []rune(aStr) 251 252 if argNum > 0 && (argStart < 0 || argStart >= len(asRunes)) { 253 return "", errors.New("slice bounds out of range") 254 } 255 256 if argNum == 2 { 257 if argEnd < 0 || argEnd > len(asRunes) { 258 return "", errors.New("slice bounds out of range") 259 } 260 return string(asRunes[argStart:argEnd]), nil 261 } else if argNum == 1 { 262 return string(asRunes[argStart:]), nil 263 } else { 264 return string(asRunes[:]), nil 265 } 266 } 267 268 // Split slices an input string into all substrings separated by delimiter. 269 func (ns *Namespace) Split(a interface{}, delimiter string) ([]string, error) { 270 aStr, err := cast.ToStringE(a) 271 if err != nil { 272 return []string{}, err 273 } 274 275 return strings.Split(aStr, delimiter), nil 276 } 277 278 // Substr extracts parts of a string, beginning at the character at the specified 279 // position, and returns the specified number of characters. 280 // 281 // It normally takes two parameters: start and length. 282 // It can also take one parameter: start, i.e. length is omitted, in which case 283 // the substring starting from start until the end of the string will be returned. 284 // 285 // To extract characters from the end of the string, use a negative start number. 286 // 287 // In addition, borrowing from the extended behavior described at http://php.net/substr, 288 // if length is given and is negative, then that many characters will be omitted from 289 // the end of string. 290 func (ns *Namespace) Substr(a interface{}, nums ...interface{}) (string, error) { 291 s, err := cast.ToStringE(a) 292 if err != nil { 293 return "", err 294 } 295 296 asRunes := []rune(s) 297 rlen := len(asRunes) 298 299 var start, length int 300 301 switch len(nums) { 302 case 0: 303 return "", errors.New("too few arguments") 304 case 1: 305 if start, err = cast.ToIntE(nums[0]); err != nil { 306 return "", errors.New("start argument must be an integer") 307 } 308 length = rlen 309 case 2: 310 if start, err = cast.ToIntE(nums[0]); err != nil { 311 return "", errors.New("start argument must be an integer") 312 } 313 if length, err = cast.ToIntE(nums[1]); err != nil { 314 return "", errors.New("length argument must be an integer") 315 } 316 default: 317 return "", errors.New("too many arguments") 318 } 319 320 if rlen == 0 { 321 return "", nil 322 } 323 324 if start < 0 { 325 start += rlen 326 } 327 328 // start was originally negative beyond rlen 329 if start < 0 { 330 start = 0 331 } 332 333 if start > rlen-1 { 334 return "", nil 335 } 336 337 end := rlen 338 339 switch { 340 case length == 0: 341 return "", nil 342 case length < 0: 343 end += length 344 case length > 0: 345 end = start + length 346 } 347 348 if start >= end { 349 return "", nil 350 } 351 352 if end < 0 { 353 return "", nil 354 } 355 356 if end > rlen { 357 end = rlen 358 } 359 360 return string(asRunes[start:end]), nil 361 } 362 363 // Title returns a copy of the input s with all Unicode letters that begin words 364 // mapped to their title case. 365 func (ns *Namespace) Title(s interface{}) (string, error) { 366 ss, err := cast.ToStringE(s) 367 if err != nil { 368 return "", err 369 } 370 371 return ns.titleFunc(ss), nil 372 } 373 374 // FirstUpper returns a string with the first character as upper case. 375 func (ns *Namespace) FirstUpper(s interface{}) (string, error) { 376 ss, err := cast.ToStringE(s) 377 if err != nil { 378 return "", err 379 } 380 381 return helpers.FirstUpper(ss), nil 382 } 383 384 // ToLower returns a copy of the input s with all Unicode letters mapped to their 385 // lower case. 386 func (ns *Namespace) ToLower(s interface{}) (string, error) { 387 ss, err := cast.ToStringE(s) 388 if err != nil { 389 return "", err 390 } 391 392 return strings.ToLower(ss), nil 393 } 394 395 // ToUpper returns a copy of the input s with all Unicode letters mapped to their 396 // upper case. 397 func (ns *Namespace) ToUpper(s interface{}) (string, error) { 398 ss, err := cast.ToStringE(s) 399 if err != nil { 400 return "", err 401 } 402 403 return strings.ToUpper(ss), nil 404 } 405 406 // Trim returns a string with all leading and trailing characters defined 407 // contained in cutset removed. 408 func (ns *Namespace) Trim(s, cutset interface{}) (string, error) { 409 ss, err := cast.ToStringE(s) 410 if err != nil { 411 return "", err 412 } 413 414 sc, err := cast.ToStringE(cutset) 415 if err != nil { 416 return "", err 417 } 418 419 return strings.Trim(ss, sc), nil 420 } 421 422 // TrimLeft returns a slice of the string s with all leading characters 423 // contained in cutset removed. 424 func (ns *Namespace) TrimLeft(cutset, s interface{}) (string, error) { 425 ss, err := cast.ToStringE(s) 426 if err != nil { 427 return "", err 428 } 429 430 sc, err := cast.ToStringE(cutset) 431 if err != nil { 432 return "", err 433 } 434 435 return strings.TrimLeft(ss, sc), nil 436 } 437 438 // TrimPrefix returns s without the provided leading prefix string. If s doesn't 439 // start with prefix, s is returned unchanged. 440 func (ns *Namespace) TrimPrefix(prefix, s interface{}) (string, error) { 441 ss, err := cast.ToStringE(s) 442 if err != nil { 443 return "", err 444 } 445 446 sx, err := cast.ToStringE(prefix) 447 if err != nil { 448 return "", err 449 } 450 451 return strings.TrimPrefix(ss, sx), nil 452 } 453 454 // TrimRight returns a slice of the string s with all trailing characters 455 // contained in cutset removed. 456 func (ns *Namespace) TrimRight(cutset, s interface{}) (string, error) { 457 ss, err := cast.ToStringE(s) 458 if err != nil { 459 return "", err 460 } 461 462 sc, err := cast.ToStringE(cutset) 463 if err != nil { 464 return "", err 465 } 466 467 return strings.TrimRight(ss, sc), nil 468 } 469 470 // TrimSuffix returns s without the provided trailing suffix string. If s 471 // doesn't end with suffix, s is returned unchanged. 472 func (ns *Namespace) TrimSuffix(suffix, s interface{}) (string, error) { 473 ss, err := cast.ToStringE(s) 474 if err != nil { 475 return "", err 476 } 477 478 sx, err := cast.ToStringE(suffix) 479 if err != nil { 480 return "", err 481 } 482 483 return strings.TrimSuffix(ss, sx), nil 484 } 485 486 // Repeat returns a new string consisting of count copies of the string s. 487 func (ns *Namespace) Repeat(n, s interface{}) (string, error) { 488 ss, err := cast.ToStringE(s) 489 if err != nil { 490 return "", err 491 } 492 493 sn, err := cast.ToIntE(n) 494 if err != nil { 495 return "", err 496 } 497 498 if sn < 0 { 499 return "", errors.New("strings: negative Repeat count") 500 } 501 502 return strings.Repeat(ss, sn), nil 503 }