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 }