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