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  }