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