// Copyright 2024 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package hstrings import ( "fmt" "regexp" "slices" "sort" "strings" "github.com/bep/helpers/maphelpers" "github.com/gohugoio/hugo/compare" ) var _ compare.Eqer = StringEqualFold("") // StringEqualFold is a string that implements the compare.Eqer interface and considers // two strings equal if they are equal when folded to lower case. // The compare.Eqer interface is used in Hugo to compare values in templates (e.g. using the eq template function). type StringEqualFold string func (s StringEqualFold) EqualFold(s2 string) bool { return strings.EqualFold(string(s), s2) } func (s StringEqualFold) String() string { return string(s) } func (s StringEqualFold) Eq(s2 any) bool { switch ss := s2.(type) { case string: return s.EqualFold(ss) case fmt.Stringer: return s.EqualFold(ss.String()) } return false } // EqualAny returns whether a string is equal to any of the given strings. func EqualAny(a string, b ...string) bool { return slices.Contains(b, a) } var reCache = *maphelpers.NewConcurrentMap[string, *regexp.Regexp]() // GetOrCompileRegexp retrieves a regexp object from the cache based upon the pattern. // If the pattern is not found in the cache, the pattern is compiled and added to // the cache. func GetOrCompileRegexp(pattern string) (re *regexp.Regexp, err error) { return reCache.GetOrCreate(pattern, func() (*regexp.Regexp, error) { return regexp.Compile(pattern) }, ) } // HasAnyPrefix checks if the string s has any of the prefixes given. func HasAnyPrefix(s string, prefixes ...string) bool { for _, p := range prefixes { if strings.HasPrefix(s, p) { return true } } return false } func HasUppercase(s string) bool { for _, r := range s { if 'A' <= r && r <= 'Z' { return true } } return false } // InSlice checks if a string is an element of a slice of strings // and returns a boolean value. func InSlice(arr []string, el string) bool { return slices.Contains(arr, el) } // InSlicEqualFold checks if a string is an element of a slice of strings // and returns a boolean value. // It uses strings.EqualFold to compare. func InSlicEqualFold(arr []string, el string) bool { for _, v := range arr { if strings.EqualFold(v, el) { return true } } return false } // ToString converts the given value to a string. // Note that this is a more strict version compared to cast.ToString, // as it will not try to convert numeric values to strings, // but only accept strings or fmt.Stringer. func ToString(v any) (string, bool) { switch vv := v.(type) { case string: return vv, true case fmt.Stringer: return vv.String(), true } return "", false } // UniqueStrings returns a new slice with any duplicates removed. func UniqueStrings(s []string) []string { unique := make([]string, 0, len(s)) for i, val := range s { var seen bool for j := range i { if s[j] == val { seen = true break } } if !seen { unique = append(unique, val) } } return unique } // UniqueStringsReuse returns a slice with any duplicates removed. // It will modify the input slice. func UniqueStringsReuse(s []string) []string { result := s[:0] for i, val := range s { var seen bool for j := range i { if s[j] == val { seen = true break } } if !seen { result = append(result, val) } } return result } // UniqueStringsSorted returns a sorted slice with any duplicates removed. // It will modify the input slice. func UniqueStringsSorted(s []string) []string { if len(s) == 0 { return nil } ss := sort.StringSlice(s) ss.Sort() i := 0 for j := 1; j < len(s); j++ { if !ss.Less(i, j) { continue } i++ s[i] = s[j] } return s[:i+1] }