mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 18:53:35 +00:00
rebase: update kubernetes to 1.28.0 in main
updating kubernetes to 1.28.0 in the main repo. Signed-off-by: Madhu Rajanna <madhupr007@gmail.com>
This commit is contained in:
committed by
mergify[bot]
parent
b2fdc269c3
commit
ff3e84ad67
778
vendor/github.com/google/cel-go/ext/strings.go
generated
vendored
778
vendor/github.com/google/cel-go/ext/strings.go
generated
vendored
@ -19,32 +19,92 @@ package ext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultLocale = "en-US"
|
||||
defaultPrecision = 6
|
||||
)
|
||||
|
||||
// Strings returns a cel.EnvOption to configure extended functions for string manipulation.
|
||||
// As a general note, all indices are zero-based.
|
||||
//
|
||||
// CharAt
|
||||
// # CharAt
|
||||
//
|
||||
// Returns the character at the given position. If the position is negative, or greater than
|
||||
// the length of the string, the function will produce an error:
|
||||
//
|
||||
// <string>.charAt(<int>) -> <string>
|
||||
// <string>.charAt(<int>) -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'hello'.charAt(4) // return 'o'
|
||||
// 'hello'.charAt(5) // return ''
|
||||
// 'hello'.charAt(-1) // error
|
||||
// 'hello'.charAt(4) // return 'o'
|
||||
// 'hello'.charAt(5) // return ''
|
||||
// 'hello'.charAt(-1) // error
|
||||
//
|
||||
// IndexOf
|
||||
// # Format
|
||||
//
|
||||
// Introduced at version: 1
|
||||
//
|
||||
// Returns a new string with substitutions being performed, printf-style.
|
||||
// The valid formatting clauses are:
|
||||
//
|
||||
// `%s` - substitutes a string. This can also be used on bools, lists, maps, bytes,
|
||||
// Duration and Timestamp, in addition to all numerical types (int, uint, and double).
|
||||
// Note that the dot/period decimal separator will always be used when printing a list
|
||||
// or map that contains a double, and that null can be passed (which results in the
|
||||
// string "null") in addition to types.
|
||||
// `%d` - substitutes an integer.
|
||||
// `%f` - substitutes a double with fixed-point precision. The default precision is 6, but
|
||||
// this can be adjusted. The strings `Infinity`, `-Infinity`, and `NaN` are also valid input
|
||||
// for this clause.
|
||||
// `%e` - substitutes a double in scientific notation. The default precision is 6, but this
|
||||
// can be adjusted.
|
||||
// `%b` - substitutes an integer with its equivalent binary string. Can also be used on bools.
|
||||
// `%x` - substitutes an integer with its equivalent in hexadecimal, or if given a string or
|
||||
// bytes, will output each character's equivalent in hexadecimal.
|
||||
// `%X` - same as above, but with A-F capitalized.
|
||||
// `%o` - substitutes an integer with its equivalent in octal.
|
||||
//
|
||||
// <string>.format(<list>) -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// "this is a string: %s\nand an integer: %d".format(["str", 42]) // returns "this is a string: str\nand an integer: 42"
|
||||
// "a double substituted with %%s: %s".format([64.2]) // returns "a double substituted with %s: 64.2"
|
||||
// "string type: %s".format([type(string)]) // returns "string type: string"
|
||||
// "timestamp: %s".format([timestamp("2023-02-03T23:31:20+00:00")]) // returns "timestamp: 2023-02-03T23:31:20Z"
|
||||
// "duration: %s".format([duration("1h45m47s")]) // returns "duration: 6347s"
|
||||
// "%f".format([3.14]) // returns "3.140000"
|
||||
// "scientific notation: %e".format([2.71828]) // returns "scientific notation: 2.718280\u202f\u00d7\u202f10\u2070\u2070"
|
||||
// "5 in binary: %b".format([5]), // returns "5 in binary; 101"
|
||||
// "26 in hex: %x".format([26]), // returns "26 in hex: 1a"
|
||||
// "26 in hex (uppercase): %X".format([26]) // returns "26 in hex (uppercase): 1A"
|
||||
// "30 in octal: %o".format([30]) // returns "30 in octal: 36"
|
||||
// "a map inside a list: %s".format([[1, 2, 3, {"a": "x", "b": "y", "c": "z"}]]) // returns "a map inside a list: [1, 2, 3, {"a":"x", "b":"y", "c":"d"}]"
|
||||
// "true bool: %s - false bool: %s\nbinary bool: %b".format([true, false, true]) // returns "true bool: true - false bool: false\nbinary bool: 1"
|
||||
//
|
||||
// Passing an incorrect type (an integer to `%s`) is considered an error, as well as attempting
|
||||
// to use more formatting clauses than there are arguments (`%d %d %d` while passing two ints, for instance).
|
||||
// If compile-time checking is enabled, and the formatting string is a constant, and the argument list is a literal,
|
||||
// then letting any arguments go unused/unformatted is also considered an error.
|
||||
//
|
||||
// # IndexOf
|
||||
//
|
||||
// Returns the integer index of the first occurrence of the search string. If the search string is
|
||||
// not found the function returns -1.
|
||||
@ -52,19 +112,19 @@ import (
|
||||
// The function also accepts an optional position from which to begin the substring search. If the
|
||||
// substring is the empty string, the index where the search starts is returned (zero or custom).
|
||||
//
|
||||
// <string>.indexOf(<string>) -> <int>
|
||||
// <string>.indexOf(<string>, <int>) -> <int>
|
||||
// <string>.indexOf(<string>) -> <int>
|
||||
// <string>.indexOf(<string>, <int>) -> <int>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'hello mellow'.indexOf('') // returns 0
|
||||
// 'hello mellow'.indexOf('ello') // returns 1
|
||||
// 'hello mellow'.indexOf('jello') // returns -1
|
||||
// 'hello mellow'.indexOf('', 2) // returns 2
|
||||
// 'hello mellow'.indexOf('ello', 2) // returns 7
|
||||
// 'hello mellow'.indexOf('ello', 20) // error
|
||||
// 'hello mellow'.indexOf('') // returns 0
|
||||
// 'hello mellow'.indexOf('ello') // returns 1
|
||||
// 'hello mellow'.indexOf('jello') // returns -1
|
||||
// 'hello mellow'.indexOf('', 2) // returns 2
|
||||
// 'hello mellow'.indexOf('ello', 2) // returns 7
|
||||
// 'hello mellow'.indexOf('ello', 20) // error
|
||||
//
|
||||
// Join
|
||||
// # Join
|
||||
//
|
||||
// Returns a new string where the elements of string list are concatenated.
|
||||
//
|
||||
@ -75,12 +135,12 @@ import (
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ['hello', 'mellow'].join() // returns 'hellomellow'
|
||||
// ['hello', 'mellow'].join(' ') // returns 'hello mellow'
|
||||
// [].join() // returns ''
|
||||
// [].join('/') // returns ''
|
||||
// ['hello', 'mellow'].join() // returns 'hellomellow'
|
||||
// ['hello', 'mellow'].join(' ') // returns 'hello mellow'
|
||||
// [].join() // returns ''
|
||||
// [].join('/') // returns ''
|
||||
//
|
||||
// LastIndexOf
|
||||
// # LastIndexOf
|
||||
//
|
||||
// Returns the integer index at the start of the last occurrence of the search string. If the
|
||||
// search string is not found the function returns -1.
|
||||
@ -89,31 +149,45 @@ import (
|
||||
// considered as the beginning of the substring match. If the substring is the empty string,
|
||||
// the index where the search starts is returned (string length or custom).
|
||||
//
|
||||
// <string>.lastIndexOf(<string>) -> <int>
|
||||
// <string>.lastIndexOf(<string>, <int>) -> <int>
|
||||
// <string>.lastIndexOf(<string>) -> <int>
|
||||
// <string>.lastIndexOf(<string>, <int>) -> <int>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'hello mellow'.lastIndexOf('') // returns 12
|
||||
// 'hello mellow'.lastIndexOf('ello') // returns 7
|
||||
// 'hello mellow'.lastIndexOf('jello') // returns -1
|
||||
// 'hello mellow'.lastIndexOf('ello', 6) // returns 1
|
||||
// 'hello mellow'.lastIndexOf('ello', -1) // error
|
||||
// 'hello mellow'.lastIndexOf('') // returns 12
|
||||
// 'hello mellow'.lastIndexOf('ello') // returns 7
|
||||
// 'hello mellow'.lastIndexOf('jello') // returns -1
|
||||
// 'hello mellow'.lastIndexOf('ello', 6) // returns 1
|
||||
// 'hello mellow'.lastIndexOf('ello', -1) // error
|
||||
//
|
||||
// LowerAscii
|
||||
// # LowerAscii
|
||||
//
|
||||
// Returns a new string where all ASCII characters are lower-cased.
|
||||
//
|
||||
// This function does not perform Unicode case-mapping for characters outside the ASCII range.
|
||||
//
|
||||
// <string>.lowerAscii() -> <string>
|
||||
// <string>.lowerAscii() -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'TacoCat'.lowerAscii() // returns 'tacocat'
|
||||
// 'TacoCÆt Xii'.lowerAscii() // returns 'tacocÆt xii'
|
||||
// 'TacoCat'.lowerAscii() // returns 'tacocat'
|
||||
// 'TacoCÆt Xii'.lowerAscii() // returns 'tacocÆt xii'
|
||||
//
|
||||
// Replace
|
||||
// # Quote
|
||||
//
|
||||
// Introduced in version: 1
|
||||
//
|
||||
// Takes the given string and makes it safe to print (without any formatting due to escape sequences).
|
||||
// If any invalid UTF-8 characters are encountered, they are replaced with \uFFFD.
|
||||
//
|
||||
// strings.quote(<string>)
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// strings.quote('single-quote with "double quote"') // returns '"single-quote with \"double quote\""'
|
||||
// strings.quote("two escape sequences \a\n") // returns '"two escape sequences \\a\\n"'
|
||||
//
|
||||
// # Replace
|
||||
//
|
||||
// Returns a new string based on the target, which replaces the occurrences of a search string
|
||||
// with a replacement string if present. The function accepts an optional limit on the number of
|
||||
@ -122,17 +196,17 @@ import (
|
||||
// When the replacement limit is 0, the result is the original string. When the limit is a negative
|
||||
// number, the function behaves the same as replace all.
|
||||
//
|
||||
// <string>.replace(<string>, <string>) -> <string>
|
||||
// <string>.replace(<string>, <string>, <int>) -> <string>
|
||||
// <string>.replace(<string>, <string>) -> <string>
|
||||
// <string>.replace(<string>, <string>, <int>) -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'hello hello'.replace('he', 'we') // returns 'wello wello'
|
||||
// 'hello hello'.replace('he', 'we', -1) // returns 'wello wello'
|
||||
// 'hello hello'.replace('he', 'we', 1) // returns 'wello hello'
|
||||
// 'hello hello'.replace('he', 'we', 0) // returns 'hello hello'
|
||||
// 'hello hello'.replace('he', 'we') // returns 'wello wello'
|
||||
// 'hello hello'.replace('he', 'we', -1) // returns 'wello wello'
|
||||
// 'hello hello'.replace('he', 'we', 1) // returns 'wello hello'
|
||||
// 'hello hello'.replace('he', 'we', 0) // returns 'hello hello'
|
||||
//
|
||||
// Split
|
||||
// # Split
|
||||
//
|
||||
// Returns a list of strings split from the input by the given separator. The function accepts
|
||||
// an optional argument specifying a limit on the number of substrings produced by the split.
|
||||
@ -141,18 +215,18 @@ import (
|
||||
// target string to split. When the limit is a negative number, the function behaves the same as
|
||||
// split all.
|
||||
//
|
||||
// <string>.split(<string>) -> <list<string>>
|
||||
// <string>.split(<string>, <int>) -> <list<string>>
|
||||
// <string>.split(<string>) -> <list<string>>
|
||||
// <string>.split(<string>, <int>) -> <list<string>>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'hello hello hello'.split(' ') // returns ['hello', 'hello', 'hello']
|
||||
// 'hello hello hello'.split(' ', 0) // returns []
|
||||
// 'hello hello hello'.split(' ', 1) // returns ['hello hello hello']
|
||||
// 'hello hello hello'.split(' ', 2) // returns ['hello', 'hello hello']
|
||||
// 'hello hello hello'.split(' ', -1) // returns ['hello', 'hello', 'hello']
|
||||
// 'hello hello hello'.split(' ') // returns ['hello', 'hello', 'hello']
|
||||
// 'hello hello hello'.split(' ', 0) // returns []
|
||||
// 'hello hello hello'.split(' ', 1) // returns ['hello hello hello']
|
||||
// 'hello hello hello'.split(' ', 2) // returns ['hello', 'hello hello']
|
||||
// 'hello hello hello'.split(' ', -1) // returns ['hello', 'hello', 'hello']
|
||||
//
|
||||
// Substring
|
||||
// # Substring
|
||||
//
|
||||
// Returns the substring given a numeric range corresponding to character positions. Optionally
|
||||
// may omit the trailing range for a substring from a given character position until the end of
|
||||
@ -162,48 +236,102 @@ import (
|
||||
// error to specify an end range that is lower than the start range, or for either the start or end
|
||||
// index to be negative or exceed the string length.
|
||||
//
|
||||
// <string>.substring(<int>) -> <string>
|
||||
// <string>.substring(<int>, <int>) -> <string>
|
||||
// <string>.substring(<int>) -> <string>
|
||||
// <string>.substring(<int>, <int>) -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'tacocat'.substring(4) // returns 'cat'
|
||||
// 'tacocat'.substring(0, 4) // returns 'taco'
|
||||
// 'tacocat'.substring(-1) // error
|
||||
// 'tacocat'.substring(2, 1) // error
|
||||
// 'tacocat'.substring(4) // returns 'cat'
|
||||
// 'tacocat'.substring(0, 4) // returns 'taco'
|
||||
// 'tacocat'.substring(-1) // error
|
||||
// 'tacocat'.substring(2, 1) // error
|
||||
//
|
||||
// Trim
|
||||
// # Trim
|
||||
//
|
||||
// Returns a new string which removes the leading and trailing whitespace in the target string.
|
||||
// The trim function uses the Unicode definition of whitespace which does not include the
|
||||
// zero-width spaces. See: https://en.wikipedia.org/wiki/Whitespace_character#Unicode
|
||||
//
|
||||
// <string>.trim() -> <string>
|
||||
// <string>.trim() -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ' \ttrim\n '.trim() // returns 'trim'
|
||||
// ' \ttrim\n '.trim() // returns 'trim'
|
||||
//
|
||||
// UpperAscii
|
||||
// # UpperAscii
|
||||
//
|
||||
// Returns a new string where all ASCII characters are upper-cased.
|
||||
//
|
||||
// This function does not perform Unicode case-mapping for characters outside the ASCII range.
|
||||
//
|
||||
// <string>.upperAscii() -> <string>
|
||||
// <string>.upperAscii() -> <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// 'TacoCat'.upperAscii() // returns 'TACOCAT'
|
||||
// 'TacoCÆt Xii'.upperAscii() // returns 'TACOCÆT XII'
|
||||
func Strings() cel.EnvOption {
|
||||
return cel.Lib(stringLib{})
|
||||
// 'TacoCat'.upperAscii() // returns 'TACOCAT'
|
||||
// 'TacoCÆt Xii'.upperAscii() // returns 'TACOCÆT XII'
|
||||
func Strings(options ...StringsOption) cel.EnvOption {
|
||||
s := &stringLib{version: math.MaxUint32}
|
||||
for _, o := range options {
|
||||
s = o(s)
|
||||
}
|
||||
return cel.Lib(s)
|
||||
}
|
||||
|
||||
type stringLib struct{}
|
||||
type stringLib struct {
|
||||
locale string
|
||||
version uint32
|
||||
}
|
||||
|
||||
func (stringLib) CompileOptions() []cel.EnvOption {
|
||||
return []cel.EnvOption{
|
||||
// LibraryName implements the SingletonLibrary interface method.
|
||||
func (*stringLib) LibraryName() string {
|
||||
return "cel.lib.ext.strings"
|
||||
}
|
||||
|
||||
// StringsOption is a functional interface for configuring the strings library.
|
||||
type StringsOption func(*stringLib) *stringLib
|
||||
|
||||
// StringsLocale configures the library with the given locale. The locale tag will
|
||||
// be checked for validity at the time that EnvOptions are configured. If this option
|
||||
// is not passed, string.format will behave as if en_US was passed as the locale.
|
||||
func StringsLocale(locale string) StringsOption {
|
||||
return func(sl *stringLib) *stringLib {
|
||||
sl.locale = locale
|
||||
return sl
|
||||
}
|
||||
}
|
||||
|
||||
// StringsVersion configures the version of the string library. The version limits which
|
||||
// functions are available. Only functions introduced below or equal to the given
|
||||
// version included in the library. See the library documentation to determine
|
||||
// which version a function was introduced at. If the documentation does not
|
||||
// state which version a function was introduced at, it can be assumed to be
|
||||
// introduced at version 0, when the library was first created.
|
||||
// If this option is not set, all functions are available.
|
||||
func StringsVersion(version uint32) func(lib *stringLib) *stringLib {
|
||||
return func(sl *stringLib) *stringLib {
|
||||
sl.version = version
|
||||
return sl
|
||||
}
|
||||
}
|
||||
|
||||
// CompileOptions implements the Library interface method.
|
||||
func (sl *stringLib) CompileOptions() []cel.EnvOption {
|
||||
formatLocale := "en_US"
|
||||
if sl.locale != "" {
|
||||
// ensure locale is properly-formed if set
|
||||
_, err := language.Parse(sl.locale)
|
||||
if err != nil {
|
||||
return []cel.EnvOption{
|
||||
func(e *cel.Env) (*cel.Env, error) {
|
||||
return nil, fmt.Errorf("failed to parse locale: %w", err)
|
||||
},
|
||||
}
|
||||
}
|
||||
formatLocale = sl.locale
|
||||
}
|
||||
|
||||
opts := []cel.EnvOption{
|
||||
cel.Function("charAt",
|
||||
cel.MemberOverload("string_char_at_int", []*cel.Type{cel.StringType, cel.IntType}, cel.StringType,
|
||||
cel.BinaryBinding(func(str, ind ref.Val) ref.Val {
|
||||
@ -303,28 +431,64 @@ func (stringLib) CompileOptions() []cel.EnvOption {
|
||||
s := str.(types.String)
|
||||
return stringOrError(upperASCII(string(s)))
|
||||
}))),
|
||||
cel.Function("join",
|
||||
cel.MemberOverload("list_join", []*cel.Type{cel.ListType(cel.StringType)}, cel.StringType,
|
||||
cel.UnaryBinding(func(list ref.Val) ref.Val {
|
||||
l, err := list.ConvertToNative(stringListType)
|
||||
if err != nil {
|
||||
return types.NewErr(err.Error())
|
||||
}
|
||||
return stringOrError(join(l.([]string)))
|
||||
})),
|
||||
cel.MemberOverload("list_join_string", []*cel.Type{cel.ListType(cel.StringType), cel.StringType}, cel.StringType,
|
||||
cel.BinaryBinding(func(list, delim ref.Val) ref.Val {
|
||||
l, err := list.ConvertToNative(stringListType)
|
||||
if err != nil {
|
||||
return types.NewErr(err.Error())
|
||||
}
|
||||
d := delim.(types.String)
|
||||
return stringOrError(joinSeparator(l.([]string), string(d)))
|
||||
}))),
|
||||
}
|
||||
if sl.version >= 1 {
|
||||
opts = append(opts, cel.Function("format",
|
||||
cel.MemberOverload("string_format", []*cel.Type{cel.StringType, cel.ListType(cel.DynType)}, cel.StringType,
|
||||
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
|
||||
s := string(args[0].(types.String))
|
||||
formatArgs := args[1].(traits.Lister)
|
||||
return stringOrError(interpreter.ParseFormatString(s, &stringFormatter{}, &stringArgList{formatArgs}, formatLocale))
|
||||
}))),
|
||||
cel.Function("strings.quote", cel.Overload("strings_quote", []*cel.Type{cel.StringType}, cel.StringType,
|
||||
cel.UnaryBinding(func(str ref.Val) ref.Val {
|
||||
s := str.(types.String)
|
||||
return stringOrError(quote(string(s)))
|
||||
}))))
|
||||
|
||||
}
|
||||
if sl.version >= 2 {
|
||||
opts = append(opts,
|
||||
cel.Function("join",
|
||||
cel.MemberOverload("list_join", []*cel.Type{cel.ListType(cel.StringType)}, cel.StringType,
|
||||
cel.UnaryBinding(func(list ref.Val) ref.Val {
|
||||
l := list.(traits.Lister)
|
||||
return stringOrError(joinValSeparator(l, ""))
|
||||
})),
|
||||
cel.MemberOverload("list_join_string", []*cel.Type{cel.ListType(cel.StringType), cel.StringType}, cel.StringType,
|
||||
cel.BinaryBinding(func(list, delim ref.Val) ref.Val {
|
||||
l := list.(traits.Lister)
|
||||
d := delim.(types.String)
|
||||
return stringOrError(joinValSeparator(l, string(d)))
|
||||
}))),
|
||||
)
|
||||
} else {
|
||||
opts = append(opts,
|
||||
cel.Function("join",
|
||||
cel.MemberOverload("list_join", []*cel.Type{cel.ListType(cel.StringType)}, cel.StringType,
|
||||
cel.UnaryBinding(func(list ref.Val) ref.Val {
|
||||
l, err := list.ConvertToNative(stringListType)
|
||||
if err != nil {
|
||||
return types.NewErr(err.Error())
|
||||
}
|
||||
return stringOrError(join(l.([]string)))
|
||||
})),
|
||||
cel.MemberOverload("list_join_string", []*cel.Type{cel.ListType(cel.StringType), cel.StringType}, cel.StringType,
|
||||
cel.BinaryBinding(func(list, delim ref.Val) ref.Val {
|
||||
l, err := list.ConvertToNative(stringListType)
|
||||
if err != nil {
|
||||
return types.NewErr(err.Error())
|
||||
}
|
||||
d := delim.(types.String)
|
||||
return stringOrError(joinSeparator(l.([]string), string(d)))
|
||||
}))),
|
||||
)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func (stringLib) ProgramOptions() []cel.ProgramOption {
|
||||
// ProgramOptions implements the Library interface method.
|
||||
func (*stringLib) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
@ -478,6 +642,452 @@ func join(strs []string) (string, error) {
|
||||
return strings.Join(strs, ""), nil
|
||||
}
|
||||
|
||||
func joinValSeparator(strs traits.Lister, separator string) (string, error) {
|
||||
sz := strs.Size().(types.Int)
|
||||
var sb strings.Builder
|
||||
for i := types.Int(0); i < sz; i++ {
|
||||
if i != 0 {
|
||||
sb.WriteString(separator)
|
||||
}
|
||||
elem := strs.Get(i)
|
||||
str, ok := elem.(types.String)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("join: invalid input: %v", elem)
|
||||
}
|
||||
sb.WriteString(string(str))
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
type clauseImpl func(ref.Val, string) (string, error)
|
||||
|
||||
func clauseForType(argType ref.Type) (clauseImpl, error) {
|
||||
switch argType {
|
||||
case types.IntType, types.UintType:
|
||||
return formatDecimal, nil
|
||||
case types.StringType, types.BytesType, types.BoolType, types.NullType, types.TypeType:
|
||||
return FormatString, nil
|
||||
case types.TimestampType, types.DurationType:
|
||||
// special case to ensure timestamps/durations get printed as CEL literals
|
||||
return func(arg ref.Val, locale string) (string, error) {
|
||||
argStrVal := arg.ConvertToType(types.StringType)
|
||||
argStr := argStrVal.Value().(string)
|
||||
if arg.Type() == types.TimestampType {
|
||||
return fmt.Sprintf("timestamp(%q)", argStr), nil
|
||||
}
|
||||
if arg.Type() == types.DurationType {
|
||||
return fmt.Sprintf("duration(%q)", argStr), nil
|
||||
}
|
||||
return "", fmt.Errorf("cannot convert argument of type %s to timestamp/duration", arg.Type().TypeName())
|
||||
}, nil
|
||||
case types.ListType:
|
||||
return formatList, nil
|
||||
case types.MapType:
|
||||
return formatMap, nil
|
||||
case types.DoubleType:
|
||||
// avoid formatFixed so we can output a period as the decimal separator in order
|
||||
// to always be a valid CEL literal
|
||||
return func(arg ref.Val, locale string) (string, error) {
|
||||
argDouble, ok := arg.Value().(float64)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("couldn't convert %s to float64", arg.Type().TypeName())
|
||||
}
|
||||
fmtStr := fmt.Sprintf("%%.%df", defaultPrecision)
|
||||
return fmt.Sprintf(fmtStr, argDouble), nil
|
||||
}, nil
|
||||
case types.TypeType:
|
||||
return func(arg ref.Val, locale string) (string, error) {
|
||||
return fmt.Sprintf("type(%s)", arg.Value().(string)), nil
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("no formatting function for %s", argType.TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
func formatList(arg ref.Val, locale string) (string, error) {
|
||||
argList := arg.(traits.Lister)
|
||||
argIterator := argList.Iterator()
|
||||
var listStrBuilder strings.Builder
|
||||
_, err := listStrBuilder.WriteRune('[')
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error writing to list string: %w", err)
|
||||
}
|
||||
for argIterator.HasNext() == types.True {
|
||||
member := argIterator.Next()
|
||||
memberFormat, err := clauseForType(member.Type())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
unquotedStr, err := memberFormat(member, locale)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str := quoteForCEL(member, unquotedStr)
|
||||
_, err = listStrBuilder.WriteString(str)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error writing to list string: %w", err)
|
||||
}
|
||||
if argIterator.HasNext() == types.True {
|
||||
_, err = listStrBuilder.WriteString(", ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error writing to list string: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = listStrBuilder.WriteRune(']')
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error writing to list string: %w", err)
|
||||
}
|
||||
return listStrBuilder.String(), nil
|
||||
}
|
||||
|
||||
func formatMap(arg ref.Val, locale string) (string, error) {
|
||||
argMap := arg.(traits.Mapper)
|
||||
argIterator := argMap.Iterator()
|
||||
type mapPair struct {
|
||||
key string
|
||||
value string
|
||||
}
|
||||
argPairs := make([]mapPair, argMap.Size().Value().(int64))
|
||||
i := 0
|
||||
for argIterator.HasNext() == types.True {
|
||||
key := argIterator.Next()
|
||||
var keyFormat clauseImpl
|
||||
switch key.Type() {
|
||||
case types.StringType, types.BoolType:
|
||||
keyFormat = FormatString
|
||||
case types.IntType, types.UintType:
|
||||
keyFormat = formatDecimal
|
||||
default:
|
||||
return "", fmt.Errorf("no formatting function for map key of type %s", key.Type().TypeName())
|
||||
}
|
||||
unquotedKeyStr, err := keyFormat(key, locale)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
keyStr := quoteForCEL(key, unquotedKeyStr)
|
||||
value, found := argMap.Find(key)
|
||||
if !found {
|
||||
return "", fmt.Errorf("could not find key: %q", key)
|
||||
}
|
||||
valueFormat, err := clauseForType(value.Type())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
unquotedValueStr, err := valueFormat(value, locale)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
valueStr := quoteForCEL(value, unquotedValueStr)
|
||||
argPairs[i] = mapPair{keyStr, valueStr}
|
||||
i++
|
||||
}
|
||||
sort.SliceStable(argPairs, func(x, y int) bool {
|
||||
return argPairs[x].key < argPairs[y].key
|
||||
})
|
||||
var mapStrBuilder strings.Builder
|
||||
_, err := mapStrBuilder.WriteRune('{')
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error writing to map string: %w", err)
|
||||
}
|
||||
for i, entry := range argPairs {
|
||||
_, err = mapStrBuilder.WriteString(fmt.Sprintf("%s:%s", entry.key, entry.value))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error writing to map string: %w", err)
|
||||
}
|
||||
if i < len(argPairs)-1 {
|
||||
_, err = mapStrBuilder.WriteString(", ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error writing to map string: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = mapStrBuilder.WriteRune('}')
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error writing to map string: %w", err)
|
||||
}
|
||||
return mapStrBuilder.String(), nil
|
||||
}
|
||||
|
||||
// quoteForCEL takes a formatted, unquoted value and quotes it in a manner
|
||||
// suitable for embedding directly in CEL.
|
||||
func quoteForCEL(refVal ref.Val, unquotedValue string) string {
|
||||
switch refVal.Type() {
|
||||
case types.StringType:
|
||||
return fmt.Sprintf("%q", unquotedValue)
|
||||
case types.BytesType:
|
||||
return fmt.Sprintf("b%q", unquotedValue)
|
||||
case types.DoubleType:
|
||||
// special case to handle infinity/NaN
|
||||
num := refVal.Value().(float64)
|
||||
if math.IsInf(num, 1) || math.IsInf(num, -1) || math.IsNaN(num) {
|
||||
return fmt.Sprintf("%q", unquotedValue)
|
||||
}
|
||||
return unquotedValue
|
||||
default:
|
||||
return unquotedValue
|
||||
}
|
||||
}
|
||||
|
||||
// FormatString returns the string representation of a CEL value.
|
||||
// It is used to implement the %s specifier in the (string).format() extension
|
||||
// function.
|
||||
func FormatString(arg ref.Val, locale string) (string, error) {
|
||||
switch arg.Type() {
|
||||
case types.ListType:
|
||||
return formatList(arg, locale)
|
||||
case types.MapType:
|
||||
return formatMap(arg, locale)
|
||||
case types.IntType, types.UintType, types.DoubleType,
|
||||
types.BoolType, types.StringType, types.TimestampType, types.BytesType, types.DurationType, types.TypeType:
|
||||
argStrVal := arg.ConvertToType(types.StringType)
|
||||
argStr, ok := argStrVal.Value().(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not convert argument %q to string", argStrVal)
|
||||
}
|
||||
return argStr, nil
|
||||
case types.NullType:
|
||||
return "null", nil
|
||||
default:
|
||||
return "", fmt.Errorf("string clause can only be used on strings, bools, bytes, ints, doubles, maps, lists, types, durations, and timestamps, was given %s", arg.Type().TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
func formatDecimal(arg ref.Val, locale string) (string, error) {
|
||||
switch arg.Type() {
|
||||
case types.IntType:
|
||||
argInt, ok := arg.ConvertToType(types.IntType).Value().(int64)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not convert \"%s\" to int64", arg.Value())
|
||||
}
|
||||
return fmt.Sprintf("%d", argInt), nil
|
||||
case types.UintType:
|
||||
argInt, ok := arg.ConvertToType(types.UintType).Value().(uint64)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not convert \"%s\" to uint64", arg.Value())
|
||||
}
|
||||
return fmt.Sprintf("%d", argInt), nil
|
||||
default:
|
||||
return "", fmt.Errorf("decimal clause can only be used on integers, was given %s", arg.Type().TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
func matchLanguage(locale string) (language.Tag, error) {
|
||||
matcher, err := makeMatcher(locale)
|
||||
if err != nil {
|
||||
return language.Und, err
|
||||
}
|
||||
tag, _ := language.MatchStrings(matcher, locale)
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
func makeMatcher(locale string) (language.Matcher, error) {
|
||||
tags := make([]language.Tag, 0)
|
||||
tag, err := language.Parse(locale)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
return language.NewMatcher(tags), nil
|
||||
}
|
||||
|
||||
// quote implements a string quoting function. The string will be wrapped in
|
||||
// double quotes, and all valid CEL escape sequences will be escaped to show up
|
||||
// literally if printed. If the input contains any invalid UTF-8, the invalid runes
|
||||
// will be replaced with utf8.RuneError.
|
||||
func quote(s string) (string, error) {
|
||||
var quotedStrBuilder strings.Builder
|
||||
for _, c := range sanitize(s) {
|
||||
switch c {
|
||||
case '\a':
|
||||
quotedStrBuilder.WriteString("\\a")
|
||||
case '\b':
|
||||
quotedStrBuilder.WriteString("\\b")
|
||||
case '\f':
|
||||
quotedStrBuilder.WriteString("\\f")
|
||||
case '\n':
|
||||
quotedStrBuilder.WriteString("\\n")
|
||||
case '\r':
|
||||
quotedStrBuilder.WriteString("\\r")
|
||||
case '\t':
|
||||
quotedStrBuilder.WriteString("\\t")
|
||||
case '\v':
|
||||
quotedStrBuilder.WriteString("\\v")
|
||||
case '\\':
|
||||
quotedStrBuilder.WriteString("\\\\")
|
||||
case '"':
|
||||
quotedStrBuilder.WriteString("\\\"")
|
||||
default:
|
||||
quotedStrBuilder.WriteRune(c)
|
||||
}
|
||||
}
|
||||
escapedStr := quotedStrBuilder.String()
|
||||
return "\"" + escapedStr + "\"", nil
|
||||
}
|
||||
|
||||
// sanitize replaces all invalid runes in the given string with utf8.RuneError.
|
||||
func sanitize(s string) string {
|
||||
var sanitizedStringBuilder strings.Builder
|
||||
for _, r := range s {
|
||||
if !utf8.ValidRune(r) {
|
||||
sanitizedStringBuilder.WriteRune(utf8.RuneError)
|
||||
} else {
|
||||
sanitizedStringBuilder.WriteRune(r)
|
||||
}
|
||||
}
|
||||
return sanitizedStringBuilder.String()
|
||||
}
|
||||
|
||||
type stringFormatter struct{}
|
||||
|
||||
func (c *stringFormatter) String(arg ref.Val, locale string) (string, error) {
|
||||
return FormatString(arg, locale)
|
||||
}
|
||||
|
||||
func (c *stringFormatter) Decimal(arg ref.Val, locale string) (string, error) {
|
||||
return formatDecimal(arg, locale)
|
||||
}
|
||||
|
||||
func (c *stringFormatter) Fixed(precision *int) func(ref.Val, string) (string, error) {
|
||||
if precision == nil {
|
||||
precision = new(int)
|
||||
*precision = defaultPrecision
|
||||
}
|
||||
return func(arg ref.Val, locale string) (string, error) {
|
||||
strException := false
|
||||
if arg.Type() == types.StringType {
|
||||
argStr := arg.Value().(string)
|
||||
if argStr == "NaN" || argStr == "Infinity" || argStr == "-Infinity" {
|
||||
strException = true
|
||||
}
|
||||
}
|
||||
if arg.Type() != types.DoubleType && !strException {
|
||||
return "", fmt.Errorf("fixed-point clause can only be used on doubles, was given %s", arg.Type().TypeName())
|
||||
}
|
||||
argFloatVal := arg.ConvertToType(types.DoubleType)
|
||||
argFloat, ok := argFloatVal.Value().(float64)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not convert \"%s\" to float64", argFloatVal.Value())
|
||||
}
|
||||
fmtStr := fmt.Sprintf("%%.%df", *precision)
|
||||
|
||||
matchedLocale, err := matchLanguage(locale)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error matching locale: %w", err)
|
||||
}
|
||||
return message.NewPrinter(matchedLocale).Sprintf(fmtStr, argFloat), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *stringFormatter) Scientific(precision *int) func(ref.Val, string) (string, error) {
|
||||
if precision == nil {
|
||||
precision = new(int)
|
||||
*precision = defaultPrecision
|
||||
}
|
||||
return func(arg ref.Val, locale string) (string, error) {
|
||||
strException := false
|
||||
if arg.Type() == types.StringType {
|
||||
argStr := arg.Value().(string)
|
||||
if argStr == "NaN" || argStr == "Infinity" || argStr == "-Infinity" {
|
||||
strException = true
|
||||
}
|
||||
}
|
||||
if arg.Type() != types.DoubleType && !strException {
|
||||
return "", fmt.Errorf("scientific clause can only be used on doubles, was given %s", arg.Type().TypeName())
|
||||
}
|
||||
argFloatVal := arg.ConvertToType(types.DoubleType)
|
||||
argFloat, ok := argFloatVal.Value().(float64)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not convert \"%s\" to float64", argFloatVal.Value())
|
||||
}
|
||||
matchedLocale, err := matchLanguage(locale)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error matching locale: %w", err)
|
||||
}
|
||||
fmtStr := fmt.Sprintf("%%%de", *precision)
|
||||
return message.NewPrinter(matchedLocale).Sprintf(fmtStr, argFloat), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *stringFormatter) Binary(arg ref.Val, locale string) (string, error) {
|
||||
switch arg.Type() {
|
||||
case types.IntType:
|
||||
argInt := arg.Value().(int64)
|
||||
// locale is intentionally unused as integers formatted as binary
|
||||
// strings are locale-independent
|
||||
return fmt.Sprintf("%b", argInt), nil
|
||||
case types.UintType:
|
||||
argInt := arg.Value().(uint64)
|
||||
return fmt.Sprintf("%b", argInt), nil
|
||||
case types.BoolType:
|
||||
argBool := arg.Value().(bool)
|
||||
if argBool {
|
||||
return "1", nil
|
||||
}
|
||||
return "0", nil
|
||||
default:
|
||||
return "", fmt.Errorf("only integers and bools can be formatted as binary, was given %s", arg.Type().TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *stringFormatter) Hex(useUpper bool) func(ref.Val, string) (string, error) {
|
||||
return func(arg ref.Val, locale string) (string, error) {
|
||||
fmtStr := "%x"
|
||||
if useUpper {
|
||||
fmtStr = "%X"
|
||||
}
|
||||
switch arg.Type() {
|
||||
case types.StringType, types.BytesType:
|
||||
if arg.Type() == types.BytesType {
|
||||
return fmt.Sprintf(fmtStr, arg.Value().([]byte)), nil
|
||||
}
|
||||
return fmt.Sprintf(fmtStr, arg.Value().(string)), nil
|
||||
case types.IntType:
|
||||
argInt, ok := arg.Value().(int64)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not convert \"%s\" to int64", arg.Value())
|
||||
}
|
||||
return fmt.Sprintf(fmtStr, argInt), nil
|
||||
case types.UintType:
|
||||
argInt, ok := arg.Value().(uint64)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not convert \"%s\" to uint64", arg.Value())
|
||||
}
|
||||
return fmt.Sprintf(fmtStr, argInt), nil
|
||||
default:
|
||||
return "", fmt.Errorf("only integers, byte buffers, and strings can be formatted as hex, was given %s", arg.Type().TypeName())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *stringFormatter) Octal(arg ref.Val, locale string) (string, error) {
|
||||
switch arg.Type() {
|
||||
case types.IntType:
|
||||
argInt := arg.Value().(int64)
|
||||
return fmt.Sprintf("%o", argInt), nil
|
||||
case types.UintType:
|
||||
argInt := arg.Value().(uint64)
|
||||
return fmt.Sprintf("%o", argInt), nil
|
||||
default:
|
||||
return "", fmt.Errorf("octal clause can only be used on integers, was given %s", arg.Type().TypeName())
|
||||
}
|
||||
}
|
||||
|
||||
type stringArgList struct {
|
||||
args traits.Lister
|
||||
}
|
||||
|
||||
func (c *stringArgList) Arg(index int64) (ref.Val, error) {
|
||||
if index >= c.args.Size().Value().(int64) {
|
||||
return nil, fmt.Errorf("index %d out of range", index)
|
||||
}
|
||||
return c.args.Get(types.Int(index)), nil
|
||||
}
|
||||
|
||||
func (c *stringArgList) ArgSize() int64 {
|
||||
return c.args.Size().Value().(int64)
|
||||
}
|
||||
|
||||
var (
|
||||
stringListType = reflect.TypeOf([]string{})
|
||||
)
|
||||
|
Reference in New Issue
Block a user