diff --git a/CEL.md b/CEL.md new file mode 100644 index 000000000..a13b5eb45 --- /dev/null +++ b/CEL.md @@ -0,0 +1,1469 @@ +# CEL Expressions + +CEL expressions use the [Common Expression Language (CEL)](https://cel.dev/). + +> **Tip:** The [CEL playground](https://playcel.undistro.io/) lets you test CEL expressions. + +## Types + +| Type | Description | +|---|---| +| `int` | 64-bit signed integers | +| `uint` | 64-bit unsigned integers | +| `double` | 64-bit IEEE floating-point numbers | +| `bool` | Booleans (`true` or `false`) | +| `string` | Strings of Unicode code points | +| `bytes` | Byte sequences | +| `list` | Lists of values | +| `map` | Associative arrays with `int`, `uint`, `bool`, or `string` keys | +| `null_type` | The value `null` | +| `type` | Values representing the types above | + +--- + +## Standard Operators + +### Arithmetic Operators + +| Operator | Description | Example | +|---|---|---| +| `+` | Addition (also string/list concatenation) | `2 + 3` → `5`
`"hello" + " world"` → `"hello world"`
`[1, 2] + [3, 4]` → `[1, 2, 3, 4]` | +| `-` | Subtraction (also negation) | `5 - 3` → `2`
`-5` → `-5` | +| `*` | Multiplication | `3 * 4` → `12` | +| `/` | Division | `10 / 2` → `5` | +| `%` | Remainder (modulo) | `10 % 3` → `1` | + +### Comparison Operators + +| Operator | Description | Example | +|---|---|---| +| `==` | Equal | `5 == 5` → `true` | +| `!=` | Not equal | `5 != 3` → `true` | +| `<` | Less than | `3 < 5` → `true` | +| `<=` | Less than or equal | `5 <= 5` → `true` | +| `>` | Greater than | `5 > 3` → `true` | +| `>=` | Greater than or equal | `5 >= 5` → `true` | + +### Logical Operators + +| Operator | Description | Example | +|---|---|---| +| `&&` | Logical AND | `true && false` → `false` | +| `\|\|` | Logical OR | `true \|\| false` → `true` | +| `!` | Logical NOT | `!true` → `false` | +| `? :` | Ternary conditional | `true ? "yes" : "no"` → `"yes"` | + +--- + +## Type Conversion Functions + +| Function | Description | Example | +|---|---|---| +| `bool()` | Convert to boolean | `bool("true")` → `true` | +| `bytes()` | Convert to bytes | `bytes("hello")` → `b'hello'` | +| `double()` | Convert to double | `double(5)` → `5.0` | +| `duration()` | Convert to duration | `duration("1h")` → 1 hour duration | +| `int()` | Convert to integer | `int(5.7)` → `5` | +| `string()` | Convert to string | `string(123)` → `"123"` | +| `timestamp()` | Convert to timestamp | `timestamp("2023-01-01T00:00:00Z")` | +| `uint()` | Convert to unsigned integer | `uint(5)` → `5u` | +| `type()` | Get the type of a value | `type(5)` → `int` | +| `dyn()` | Create a dynamic value | `dyn({"key": "value"})` | + +--- + +## Built-in Functions + +### Type Checking + +``` +type(5) // "int" +type("hello") // "string" +type([1, 2, 3]) // "list" +type({"key": "value"}) // "map" +``` + +--- + +## Handling null types and missing keys + +When dealing with CEL objects, a key might not exist or a middle key in a chain might be missing. + +``` +// Assume obj = {'a': {'b': 'c'}} +obj.a.b // "c" +obj.a.d // Error: attribute 'd' doesn't exist +obj.a.?d.orValue("fallback") // "fallback value" +``` + +See [or](#or) and [orValue](#orvalue) below for details. + +--- + +## matchQuery + +`matchQuery` matches a given resource against a search query. + +``` +matchQuery(r, s) +// r = resource +// s = search query + +matchQuery(.config, "type=Kubernetes::Pod") +matchQuery(.config, "type=Kubernetes::Pod tags.cluster=homelab") +``` + +--- + +## matchLabel + +`matchLabel` matches a map's key against one or more patterns. Useful for matching Kubernetes labels. + +``` +matchLabel(labels, key, patterns) +// labels = map of labels +// key = the label key to check +// patterns = comma-separated patterns to match against +``` + +**Pattern Syntax:** +- Use `*` for wildcards (e.g., `us-*` matches `us-east-1`, `us-west-2`) +- Use `!` for exclusion (e.g., `!production` matches any value except `production`) +- Use `!*` to match when the label doesn't exist +- Multiple patterns are evaluated as OR conditions + +``` +matchLabel(config.labels, "region", "us-*") // true if region starts with "us-" +matchLabel(config.labels, "env", "prod,staging") // true if env is "prod" OR "staging" +matchLabel(config.labels, "env", "!production") // true if env is NOT "production" +matchLabel(config.labels, "optional", "!*") // true if "optional" label doesn't exist +matchLabel(config.tags, "cluster", "*-prod,*-staging") +``` + +--- + +## aws + +### aws.arnToMap + +Takes in an AWS ARN, parses it, and returns a map. + +``` +aws.arnToMap("arn:aws:sns:eu-west-1:123:MMS-Topic") +// map[string]string{ +// "service": string, +// "region": string, +// "account": string, +// "resource": string, +// } +``` + +### aws.fromAWSMap + +`aws.fromAWSMap` takes a list of `map[string]string` and merges them into a single map. The input map has the field "Name". + +``` +aws.fromAWSMap(x).hello == "world" // true +// Where x = [ +// { Name: 'hello', Value: 'world' }, +// { Name: 'John', Value: 'Doe' }, +// ] +``` + +--- + +## base64 + +### base64.encode + +Encodes the given byte slice to a Base64 encoded string. + +``` +base64.encode("hello") // "aGVsbG8=" +``` + +### base64.decode + +Decodes the given base64 encoded string back to its original form. + +``` +base64.decode("aGVsbG8=") // b'hello' +``` + +--- + +## collections + +### .keys + +Returns a list of keys from a map. + +``` +{"first": "John", "last": "Doe"}.keys() // ["first", "last"] +``` + +### .merge + +Merges a second map into the first. + +``` +{"first": "John"}.merge({"last": "Doe"}) // {"first": "John", "last": "Doe"} +``` + +### .omit + +Removes a list of keys from a map. + +``` +{"first": "John", "last": "Doe"}.omit(["first"]) // {"last": "Doe"} +``` + +### .sort + +Returns a sorted list. + +``` +[3, 2, 1].sort() // [1, 2, 3] +['c', 'b', 'a'].sort() // ['a', 'b', 'c'] +``` + +### .distinct + +Returns a new list with duplicate elements removed. + +``` +[1, 2, 2, 3, 3, 3].distinct() // [1, 2, 3] +["a", "b", "a", "c"].distinct() // ["a", "b", "c"] +``` + +### .flatten + +Recursively flattens nested lists. + +``` +[[1, 2], [3, 4]].flatten() // [1, 2, 3, 4] +[1, [2, [3, 4]]].flatten() // [1, 2, 3, 4] +``` + +### .reverse + +Returns a new list with elements in reverse order. + +``` +[1, 2, 3, 4].reverse() // [4, 3, 2, 1] +["a", "b", "c"].reverse() // ["c", "b", "a"] +``` + +### range + +Generates a list of integers. + +``` +range(5) // [0, 1, 2, 3, 4] +range(2, 5) // [2, 3, 4] +range(0, 10, 2) // [0, 2, 4, 6, 8] +``` + +### .uniq + +Returns a list of unique items. + +``` +[1,2,3,3,3].uniq().sum() // 10 (note: .sum() not available, illustrative) +["a", "b", "b"].uniq() // ["a", "b"] +``` + +### .values + +Returns a list of values from a map. + +``` +{'a': 1, 'b': 2}.values() // [1, 2] +``` + +### keyValToMap + +Converts a string in `key=value,key2=value2` format into a map. + +``` +keyValToMap("a=b,c=d") // {"a": "b", "c": "d"} +keyValToMap("env=prod,region=us-east-1") // {"env": "prod", "region": "us-east-1"} +``` + +### mapToKeyVal + +Converts a map into a string in `key=value,key2=value2` format. + +``` +{"a": "b", "c": "d"}.mapToKeyVal() // "a=b,c=d" +``` + +### all + +Tests whether a predicate holds for **all** elements of a list or keys of a map. + +``` +[1, 2, 3].all(e, e > 0) // true +{"a": "apple", "b": "banana"}.all(k, k.startsWith("a")) // false +``` + +### exists + +Checks if there is at least one element in a list that satisfies a condition. + +``` +[1, 2, 3].exists(e, e == 2) // true +``` + +### exists_one + +Checks if there is exactly one element in a list that satisfies a condition. + +``` +[1, 2, 3].exists_one(e, e > 1) // false +[1, 2, 3].exists_one(e, e == 2) // true +``` + +### filter + +Creates a new list containing only elements that satisfy a condition. + +``` +[1, 2, 3, 4].filter(e, e > 2) // [3, 4] +``` + +### fold + +Combines all elements of a collection using a binary function. + +``` +// For lists: +[1, 2, 3].fold(e, acc, acc + e) // 6 + +// For maps: +{"a": "apple", "b": "banana"}.fold(k, v, acc, acc + v) // "applebanana" +``` + +### has + +Tests whether a field is available in a message or map. + +``` +has(person.name) // true if 'name' is present, false otherwise +``` + +### in + +Membership test operator. + +``` +"apple" in ["apple", "banana"] // true +3 in [1, 2, 4] // false +``` + +### map + +Creates a new list by transforming each element. + +``` +[1, 2, 3].map(e, e * 2) // [2, 4, 6] +[1, 2, 3].map(x, x > 1, x + 1) // [3, 4] +``` + +### or + +If the left-hand side is none-type, return the right-hand side optional value. + +``` +obj.?field.or(m[?key]) +l[?index].or(obj.?field.subfield).or(obj.?other) +``` + +### orValue + +Returns the value if present, otherwise returns a default. + +``` +{'a': 'x', 'b': 'y', 'c': 'z'}.?c.orValue('empty') // "z" +{'a': 'x', 'b': 'y'}.?c.orValue('empty') // "empty" +[1, 2, 3][?2].orValue(5) // 3 +[1, 2][?2].orValue(5) // 5 +``` + +### size + +Returns the number of elements in a collection or characters in a string. + +``` +"apple".size() // 5 +b"abc".size() // 3 +["apple", "banana", "cherry"].size() // 3 +{"a": 1, "b": 2}.size() // 2 +``` + +### slice + +Returns a new sub-list using the given indices. + +``` +[1, 2, 3, 4].slice(1, 3) // [2, 3] +[1, 2, 3, 4].slice(2, 4) // [3, 4] +``` + +--- + +## sets + +### sets.contains + +Returns whether the first list contains all elements of the second list. + +``` +sets.contains([], []) // true +sets.contains([], [1]) // false +sets.contains([1, 2, 3, 4], [2, 3]) // true +``` + +### sets.equivalent + +Returns whether two lists are set-equivalent. + +``` +sets.equivalent([], []) // true +sets.equivalent([1], [1, 1]) // true +sets.equivalent([1, 2, 3], [3u, 2.0, 1]) // true +``` + +### sets.intersects + +Returns whether the two lists share at least one common element. + +``` +sets.intersects([1], []) // false +sets.intersects([1], [1, 2]) // true +``` + +--- + +## csv + +### CSV + +Converts a CSV formatted array into a two-dimensional array. + +``` +CSV(["Alice,30", "Bob,31"])[0][0] // "Alice" +``` + +--- + +## crypto + +### crypto.SHA1 | SHA256 | SHA384 | SHA512 + +Computes a SHA hash of the input data. + +``` +crypto.SHA1("hello") // "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d" +crypto.SHA256("hello") // "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" +``` + +--- + +## dates + +### timestamp + +Represents a point in time. + +``` +timestamp("2023-01-01T00:00:00Z") +timestamp("2023-07-04T12:00:00Z") +``` + +### .getDate + +Extracts the date part from a timestamp. + +``` +"2023-01-01T12:34:56Z".getDate() // "2023-01-01" +``` + +### Date Part Accessors + +| Function | Description | Range | +|---|---|---| +| `.getDayOfMonth()` | Day of the month | 1–31 | +| `.getDayOfWeek()` | Day of the week (Sunday=0) | 0–6 | +| `.getDayOfYear()` | Day of the year | 1–366 | +| `.getFullYear()` | Full 4-digit year | | +| `.getHours()` | Hour | 0–23 | +| `.getMilliseconds()` | Milliseconds | 0–999 | +| `.getMinutes()` | Minutes | 0–59 | +| `.getMonth()` | Month (January=0) | 0–11 | +| `.getSeconds()` | Seconds | 0–59 | + +### duration + +Parses a string into a duration. + +``` +duration("5h") // 5 hours +duration("30m") // 30 minutes +duration("7d") // 7 days +``` + +### time.ZoneName + +Returns the name of the local system's time zone. + +``` +time.ZoneName() // "PST" +``` + +### time.ZoneOffset + +Returns the offset of the local system's time zone in minutes. + +``` +time.ZoneOffset() // -480 (for PST) +``` + +### time.Parse + +Parses a string into a time object using a specified layout. + +``` +time.Parse("2006-01-02", "2023-09-26") +time.Parse("02-01-2006", "26-09-2023") +time.Parse("15:04 02-01-2006", "14:30 26-09-2023") +``` + +### time.ParseLocal + +Parses a string into a time object using the local time zone. + +``` +time.ParseLocal("2006-01-02 15:04", "2023-09-26 14:30") +``` + +### time.ParseInLocation + +Parses a string into a time object for a specific time zone. + +``` +time.ParseInLocation("2006-01-02", "America/New_York", "2023-09-26") +time.ParseInLocation("02-01-2006", "Europe/London", "26-09-2023") +time.ParseInLocation("15:04 02-01-2006", "Asia/Tokyo", "14:30 26-09-2023") +``` + +### time.Now + +Returns the current time. + +``` +time.Now() +``` + +### time.ParseDuration + +Parses a string into a duration with support for days. + +``` +time.ParseDuration("1h30m") // 1 hour 30 minutes +time.ParseDuration("7d") // 7 days +time.ParseDuration("30d12h") // 30 days and 12 hours +time.ParseDuration("-2h45m") // negative duration +``` + +### time.Since + +Calculates the duration elapsed since a given time. + +``` +time.Since(time.Parse("2006-01-02", "2023-09-26")) +time.Since(time.Now()) // very small duration +``` + +### time.Until + +Calculates the duration remaining until a specified future time. + +``` +time.Until(time.Parse("2006-01-02", "2023-10-01")) +``` + +--- + +## encode + +### urlencode + +Encodes a string as URL-encoded. + +``` +urlencode("hello world ?") // "hello+world+%3F" +``` + +### urldecode + +Decodes a URL-encoded string. + +``` +urldecode("hello+world+%3F") // "hello world ?" +``` + +--- + +## filepath + +### filepath.Base + +Returns the last element of path. + +``` +filepath.Base("/home/user/projects/gencel") // "gencel" +``` + +### filepath.Clean + +Returns the shortest path equivalent by lexical processing. + +``` +filepath.Clean("/foo/bar/../baz") // "/foo/baz" +``` + +### filepath.Dir + +Returns all but the last element of path (the directory). + +``` +filepath.Dir("/home/user/projects/gencel") // "/home/user/projects" +``` + +### filepath.Ext + +Returns the file name extension. + +``` +filepath.Ext("/opt/image.jpg") // ".jpg" +``` + +### filepath.IsAbs + +Reports whether the path is absolute. + +``` +filepath.IsAbs("/home/user/projects/gencel") // true +filepath.IsAbs("projects/gencel") // false +``` + +### filepath.Join + +Joins path elements into a single path. + +``` +filepath.Join(["/home/user", "projects", "gencel"]) // "/home/user/projects/gencel" +``` + +### filepath.Match + +Reports whether a name matches the shell file name pattern. + +``` +filepath.Match("*.txt", "foo.json") // false +filepath.Match("*.txt", "foo.txt") // true +``` + +### filepath.Rel + +Returns a relative path from basepath to targpath. + +``` +filepath.Rel("/foo/bar", "/foo/bar/baz") // "baz" +``` + +### filepath.Split + +Splits path into directory and file name components. + +``` +filepath.Split("/foo/bar/baz") // ["/foo/bar/" "baz"] +``` + +--- + +## JSON + +### .JSON + +Parses a string into an object. + +``` +'{"name": "Alice", "age": 30}'.JSON() +``` + +### .JSONArray + +Parses a string into an array. + +``` +'[{"name": "Alice"}, {"name": "Bob"}]'.JSONArray() +``` + +### .toJSON + +Converts an object into a JSON formatted string. + +``` +[{ name: "John" }].toJSON() // '[{"name":"John"}]' +{'name': 'John'}.toJSON() // '{"name":"John"}' +1.toJSON() // "1" +``` + +### .toJSONPretty + +Converts data into a JSON string with indentation. + +``` +{'name': 'aditya'}.toJSONPretty('\t') +// { +// "name": "aditya" +// } +``` + +### jmespath + +Evaluates a [JMESPath](https://jmespath.org/) expression against an object. + +``` +jmespath("city", { name: "John", age: 30, city: "NY" }) // "NY" +``` + +### jsonpath + +Evaluates a [JSONPath](https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base) expression against an object. + +``` +jsonpath("$.name", { name: "John", age: 30 }) // "John" +jsonpath("$.items[0]", { items: ["apple", "banana"] }) // "apple" +jsonpath("$.addresses[-1:].city", { addresses: [{city:"NYC"},{city:"SF"}] }) // "SF" +jsonpath("$.user.email", '{"user": {"email": "john@example.com"}}') // "john@example.com" +``` + +### jq + +Applies a jq expression to filter or transform data. + +``` +jq(".name", { name: "John", age: 30 }) // "John" +jq("{name, age}", { name: "John", age: 30, city: "NY" }) // {"name":"John","age":30} +jq(".[] | select(.age > 25)", [{ name: "John", age: 30 }, { name: "Jane", age: 25 }]) +// [{"name": "John", "age": 30}] +``` + +--- + +## kubernetes + +### k8s.cpuAsMillicores + +Returns the millicores of a Kubernetes CPU resource string. + +``` +k8s.cpuAsMillicores("10m") // 10 +k8s.cpuAsMillicores("0.5") // 500 +k8s.cpuAsMillicores("1.234") // 1234 +``` + +### k8s.getHealth + +Retrieves the health status of a Kubernetes resource as a map. + +``` +k8s.getHealth(pod) // map with health info +k8s.getHealth(service) +k8s.getHealth(deployment) +``` + +### k8s.getStatus + +Retrieves the status of a Kubernetes resource as a string. + +``` +k8s.getStatus(pod) // "Running" +k8s.getStatus(service) // "Active" +k8s.getStatus(deployment) // "Deployed" +``` + +### k8s.getResourcesLimit + +Retrieves the CPU or memory limit of a Kubernetes pod. + +``` +k8s.getResourcesLimit(pod, "cpu") // 2 +k8s.getResourcesLimit(pod, "memory") // 200 +``` + +### k8s.getResourcesRequests + +Retrieves the CPU or memory requests of a Kubernetes pod. + +``` +k8s.getResourcesRequests(pod, "cpu") // 2 +k8s.getResourcesRequests(pod, "memory") // 200 +``` + +### k8s.isHealthy + +Determines if a Kubernetes resource is healthy. + +``` +k8s.isHealthy(pod) // true +k8s.isHealthy(service) // false +k8s.isHealthy(deployment) // true +``` + +### k8s.labels + +Returns a map of all labels for a Kubernetes object. The namespace is included for namespaced objects, and labels ending with `-hash` are excluded. + +``` +k8s.labels(pod) // {"namespace": "kube-system", "app": "kube-dns"} +``` + +### k8s.memoryAsBytes + +Converts a memory string to bytes. + +``` +k8s.memoryAsBytes("10Ki") // 10240 +k8s.memoryAsBytes("1.234Gi") // 1324997410 +``` + +### k8s.nodeProperties + +Returns a map of node properties (CPU, memory, ephemeral-storage, zone). + +``` +k8s.nodeProperties(node) +// {"cpu": 2, "memory": 200, "ephemeral-storage": 100000, "zone": "us-east-1a"} +``` + +### k8s.podProperties + +Returns a map of pod properties (image, cpu, memory, node, created-at, namespace). + +``` +k8s.podProperties(pod) +// {"image": "postgres:14", "node": "saka", ...} +``` + +--- + +## math + +### math.Add + +Sums a list of numbers. + +``` +math.Add([1, 2, 3, 4, 5]) // 15 +``` + +### math.Sub + +Subtracts the second number from the first. + +``` +math.Sub(5, 4) // 1 +``` + +### math.Mul + +Returns the product of a list of numbers. + +``` +math.Mul([1, 2, 3, 4, 5]) // 120 +``` + +### math.Div + +Divides the first number by the second. + +``` +math.Div(4, 2) // 2 +``` + +### math.Rem + +Returns the remainder of dividing the first number by the second. + +``` +math.Rem(4, 3) // 1 +``` + +### math.Pow + +Returns the result of raising the first number to the power of the second. + +``` +math.Pow(4, 2) // 16 +``` + +### math.Seq + +Generates a sequence of numbers. + +``` +math.Seq([1, 5]) // [1, 2, 3, 4, 5] +math.Seq([1, 6, 2]) // [1, 3, 5] +``` + +### math.Abs + +Returns the absolute value of a number. + +``` +math.Abs(-1) // 1 +``` + +### math.greatest + +Returns the greatest value in a list. + +``` +math.greatest([1, 2, 3, 4, 5]) // 5 +``` + +### math.least + +Returns the least value in a list. + +``` +math.least([1, 2, 3, 4, 5]) // 1 +``` + +### math.Ceil + +Returns the smallest integer greater than or equal to the input. + +``` +math.Ceil(2.3) // 3 +``` + +### math.Floor + +Returns the largest integer less than or equal to the input. + +``` +math.Floor(2.3) // 2 +``` + +### math.Round + +Returns the nearest integer to the input. + +``` +math.Round(2.3) // 2 +math.Round(2.7) // 3 +math.Round(2.5) // 3 +``` + +### math.Trunc + +Returns the integer part of a float, truncating towards zero. + +``` +math.Trunc(2.7) // 2 +math.Trunc(-2.7) // -2 +``` + +### math.Sign + +Returns the sign of a number: -1, 0, or 1. + +``` +math.Sign(-5) // -1 +math.Sign(0) // 0 +math.Sign(5) // 1 +``` + +### math.Sqrt + +Returns the square root of a number. + +``` +math.Sqrt(16) // 4 +math.Sqrt(2) // 1.4142135623730951 +``` + +### math.IsNaN + +Checks if a value is Not-a-Number. + +``` +math.IsNaN(0.0 / 0.0) // true +math.IsNaN(5.0) // false +``` + +### math.IsInf + +Checks if a value is infinite. + +``` +math.IsInf(1.0 / 0.0) // true +math.IsInf(5.0) // false +``` + +### math.IsFinite + +Checks if a value is finite. + +``` +math.IsFinite(5.0) // true +math.IsFinite(1.0 / 0.0) // false +``` + +### Bitwise Operations + +| Function | Description | Example | +|---|---|---| +| `math.bitOr(x, y)` | Bitwise OR | `math.bitOr(5, 3)` → `7` | +| `math.bitAnd(x, y)` | Bitwise AND | `math.bitAnd(5, 3)` → `1` | +| `math.bitXor(x, y)` | Bitwise XOR | `math.bitXor(5, 3)` → `6` | +| `math.bitNot(x)` | Bitwise NOT | `math.bitNot(5)` → `-6` | +| `math.bitShiftLeft(x, n)` | Left shift | `math.bitShiftLeft(5, 1)` → `10` | +| `math.bitShiftRight(x, n)` | Right shift | `math.bitShiftRight(10, 1)` → `5` | + +--- + +## random + +### random.ASCII + +Generates a random ASCII string of a specified length. + +``` +random.ASCII(5) +``` + +### random.Alpha + +Generates a random alphabetic string of a specified length. + +``` +random.Alpha(5) +``` + +### random.AlphaNum + +Generates a random alphanumeric string of a specified length. + +``` +random.AlphaNum(5) +``` + +### random.String + +Generates a random string of a specified length and optional character set. + +``` +random.String(5) +random.String(5, ["a", "d"]) // 5 chars between 'a' and 'd' +``` + +### random.Item + +Returns a random item from a list. + +``` +random.Item(["a", "b", "c"]) +``` + +### random.Number + +Returns a random integer within a specified range. + +``` +random.Number(1, 10) +``` + +### random.Float + +Returns a random float within a specified range. + +``` +random.Float(1, 10) +``` + +--- + +## regexp + +### regexp.Find + +Finds the first occurrence of a pattern within a string. + +``` +regexp.Find("llo", "hello") // "llo" +regexp.Find("\\d+", "abc123def") // "123" +regexp.Find("xyz", "hello") // "" +``` + +### regexp.FindAll + +Retrieves all occurrences of a pattern within a string. + +``` +regexp.FindAll("a.", -1, "banana") // ["ba", "na", "na"] +regexp.FindAll("\\d", 2, "12345") // ["1", "2"] +regexp.FindAll("z", -1, "hello") // [] +``` + +### regexp.Match + +Checks if a string matches a regular expression pattern. + +``` +regexp.Match("^h.llo", "hello") // true +regexp.Match("^b", "apple") // false +regexp.Match("\\d+", "abc123") // true +``` + +### regexp.QuoteMeta + +Quotes all regular expression metacharacters in a string. + +``` +regexp.QuoteMeta("a.b") // "a\\.b" +regexp.QuoteMeta("abc") // "abc" +``` + +### regexp.Replace + +Replaces occurrences of a pattern within a string. + +``` +regexp.Replace("a.", "x", "banana") // "bxnxna" +regexp.Replace("z", "x", "apple") // "apple" +regexp.Replace("\\d+", "num", "abc123") // "abcnum" +``` + +### regexp.ReplaceLiteral + +Replaces occurrences of a substring without regex interpretation. + +``` +regexp.ReplaceLiteral("apple", "orange", "apple pie") // "orange pie" +regexp.ReplaceLiteral("a.", "x", "a.b c.d") // "x b c.d" +``` + +### regexp.Split + +Splits a string into substrings separated by a pattern. + +``` +regexp.Split("a.", -1, "banana") // ["", "n", "n"] +regexp.Split("\\s", 2, "apple pie is delicious") // ["apple", "pie is delicious"] +regexp.Split("z", -1, "hello") // ["hello"] +``` + +--- + +## strings + +### .abbrev + +Abbreviates a string using ellipses. + +``` +"Now is the time for all good men".abbrev(5, 20) // "...s the time for..." +"KubernetesPod".abbrev(1, 5) // "Ku..." +"KubernetesPod".abbrev(6) // "Kub..." +``` + +### .camelCase + +Converts a string to camelCase format. + +``` +"hello world".camelCase() // "HelloWorld" +"hello_world".camelCase() // "HelloWorld" +"hello beautiful world!".camelCase() // "HelloBeautifulWorld" +``` + +### .charAt + +Returns the character at the given position. + +``` +"hello".charAt(4) // "o" +``` + +### .contains + +Checks if a string contains a given substring. + +``` +"apple".contains("app") // true +``` + +### .endsWith + +Determines if a string ends with a specified substring. + +``` +"hello".endsWith("lo") // true +``` + +### .format + +Creates a new string with printf-style substitutions. + +``` +"this is a string: %s\nand an integer: %d".format(["str", 42]) +// "this is a string: str\nand an integer: 42" +``` + +### .indent + +Indents each line of a string by the specified width and prefix. + +``` +"hello world".indent(4, "-") // "----hello world" +``` + +### .indexOf + +Returns the integer index of the first occurrence of a substring. + +``` +"hello mellow".indexOf("") // 0 +"hello mellow".indexOf("ello") // 1 +"hello mellow".indexOf("jello") // -1 +"hello mellow".indexOf("", 2) // 2 +``` + +### .join + +Concatenates elements of a string list. + +``` +["hello", "mellow"].join() // "hellomellow" +["hello", "mellow"].join(" ") // "hello mellow" +[].join("/") // "" +``` + +### .kebabCase + +Converts a string to kebab-case format. + +``` +"Hello World".kebabCase() // "hello-world" +"HelloWorld".kebabCase() // "hello-world" +"Hello Beautiful World!".kebabCase() // "hello-beautiful-world" +``` + +### .lastIndexOf + +Returns the index of the last occurrence of a substring. + +``` +"hello mellow".lastIndexOf("") // 12 +"hello mellow".lastIndexOf("ello") // 7 +"hello mellow".lastIndexOf("jello") // -1 +"hello mellow".lastIndexOf("ello", 6) // 1 +``` + +### .lowerAscii + +Returns a new string with all ASCII characters lower-cased. + +``` +"TacoCat".lowerAscii() // "tacocat" +"TacoCÆt Xii".lowerAscii() // "tacocÆt xii" +``` + +### .matches + +Determines if a string matches a regular expression pattern. + +``` +"apple".matches("^a.*e$") // true +"example@email.com".matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$") // true +"12345".matches("^\\d+$") // true +``` + +### .quote + +Makes a string safe to print by escaping special characters. + +``` +strings.quote('single-quote with "double quote"') +// '"single-quote with \"double quote\""' +``` + +### .repeat + +Repeats a string a given number of times. + +``` +"apple".repeat(3) // "appleappleapple" +``` + +### .replace + +Replaces occurrences of a substring with a replacement string. + +``` +"hello hello".replace("he", "we") // "wello wello" +"hello hello".replace("he", "we", 1) // "wello hello" +"hello hello".replace("he", "we", 0) // "hello hello" +``` + +### .replaceAll + +Replaces all occurrences of a substring. + +``` +"I have an apple".replaceAll("apple", "orange") // "I have an orange" +``` + +### .reverse + +Returns a string with characters in reverse order. + +``` +"gums".reverse() // "smug" +"John Smith".reverse() // "htimS nhoJ" +``` + +### .runeCount + +Counts the number of runes in a string. + +``` +"Hello World".runeCount() // 11 +``` + +### .shellQuote + +Quotes a string for safe use as a shell token. + +``` +"Hello World".shellQuote() // "'Hello World'" +"Hello$World".shellQuote() // "'Hello$World'" +``` + +### .size + +Returns the number of characters in a string or elements in a collection. + +``` +["apple", "banana", "cherry"].size() // 3 +"hello".size() // 5 +``` + +### .slug + +Converts a string into a URL-friendly slug. + +``` +"Hello World!".slug() // "hello-world" +"Hello, World!".slug() // "hello-world" +"Hello Beautiful World".slug() // "hello-beautiful-world" +``` + +### .snakeCase + +Converts a string to snake_case format. + +``` +"Hello World".snakeCase() // "hello_world" +"HelloWorld".snakeCase() // "hello_world" +"Hello Beautiful World!".snakeCase() // "hello_beautiful_world" +``` + +### .sort + +Sorts a string alphabetically. + +``` +"hello".sort() // "ehllo" +``` + +### .split + +Splits a string by a separator. + +``` +"hello hello hello".split(" ") // ["hello", "hello", "hello"] +"hello hello hello".split(" ", 2) // ["hello", "hello hello"] +"hello hello hello".split(" ", -1) // ["hello", "hello", "hello"] +``` + +### .squote + +Adds single quotes around a string. + +``` +"Hello World".squote() // "'Hello World'" +``` + +### .startsWith + +Determines if a string starts with a specified substring. + +``` +"hello".startsWith("he") // true +``` + +### .substring + +Returns a substring given a numeric range. + +``` +"tacocat".substring(4) // "cat" +"tacocat".substring(0, 4) // "taco" +``` + +### .title + +Converts the first character of each word to uppercase. + +``` +"hello world".title() // "Hello World" +``` + +### .trim + +Removes leading and trailing whitespace. + +``` +" \ttrim\n ".trim() // "trim" +``` + +### .trimPrefix + +Removes a prefix from a string. + +``` +"hello world".trimPrefix("hello ") // "world" +``` + +### .trimSuffix + +Removes a suffix from a string. + +``` +"hello world".trimSuffix(" world") // "hello" +``` + +### .upperAscii + +Returns a new string with all ASCII characters upper-cased. + +``` +"TacoCat".upperAscii() // "TACOCAT" +``` diff --git a/GO_TEMPLATE.md b/GO_TEMPLATE.md new file mode 100644 index 000000000..bffd8f260 --- /dev/null +++ b/GO_TEMPLATE.md @@ -0,0 +1,1395 @@ +# Go Templates + +`template` expressions use the [Go Text Template](https://pkg.go.dev/text/template) library with additional functions provided by the [gomplate](https://docs.gomplate.ca/) library. + +## Escaping + +If you need to pass a template through a Helm Chart and prevent Helm from processing it, escape it: + +``` +{{`{{ .secret }}`}} +``` + +Alternatively, [change the templating delimiters](#delimiters). + +## Multiline YAML + +When using a YAML multiline string, use `|` and not `>` (which strips newlines): + +```yaml +# Wrong - strips newlines +exec: + script: > + #! pwsh + Get-Items | ConvertTo-JSON + +# Correct - preserves newlines +exec: + script: | + #! pwsh + Get-Items | ConvertTo-JSON +``` + +## Delimiters + +The template delimiters can be changed from the defaults (`{{` and `}}`) using `gotemplate` comments: + +``` +# gotemplate: left-delim=$[[ right-delim=]] +$message = "$[[.config.name]]" +Write-Host "{{ $message }}" +``` + +--- + +## base64 + +### Encode + +Encodes data as a Base64 string (standard Base64 encoding, [RFC4648 §4](https://tools.ietf.org/html/rfc4648#section-4)). + +``` +{{ base64.Encode "hello world" }} // aGVsbG8gd29ybGQ= +{{ "hello world" | base64.Encode }} // aGVsbG8gd29ybGQ= +``` + +### Decode + +Decodes a Base64 string. Supports both standard and URL-safe encodings. + +``` +{{ base64.Decode "aGVsbG8gd29ybGQ=" }} // hello world +{{ "aGVsbG8gd29ybGQ=" | base64.Decode }} // hello world +``` + +--- + +## Collection + +### dict + +Creates a map with string keys from key/value pairs. + +``` +{{ coll.Dict "name" "Frank" "age" 42 | data.ToYAML }} +// age: 42 +// name: Frank + +{{ dict 1 2 3 | toJSON }} // {"1":2,"3":""} + +{{ define "T1" }}Hello {{ .thing }}!{{ end -}} +{{ template "T1" (dict "thing" "world")}} +{{ template "T1" (dict "thing" "everybody")}} +// Hello world! +// Hello everybody! +``` + +### slice + +Creates a slice (array). Useful when ranging over variables. + +``` +{{ range slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }} +// Hello, Bart +// Hello, Lisa +// Hello, Maggie +``` + +### has + +Reports whether a given object has a property with the given key, or whether a slice contains the given value. + +``` +{{ $l := slice "foo" "bar" "baz" }}there is {{ if has $l "bar" }}a{{else}}no{{end}} bar +// there is a bar + +{{ $o := dict "foo" "bar" "baz" "qux" }} +{{ if has $o "foo" }}{{ $o.foo }}{{ else }}THERE IS NO FOO{{ end }} // bar +``` + +### jmespath + +Evaluates a [JMESPath](https://jmespath.org/) expression against an object or JSON string. + +``` +{{ $data := dict "foo" 1 "bar" 2 "baz" 3 }} +{{ $data | jmespath "foo" }} // 1 + +{{ $data | toJSON | jmespath "foo" }} // 1 +``` + +### jsonpath + +Evaluates a [JSONPath](https://datatracker.ietf.org/doc/draft-ietf-jsonpath-base) expression against an object or JSON string. + +``` +{{ $data := dict "foo" 1 "bar" 2 "baz" 3 }} +{{ $data | jsonpath "$.foo" }} // 1 + +{{ $data | toJSON | jsonpath "$.foo" }} // 1 +``` + +### jq + +Filters input using the [jq query language](https://stedolan.github.io/jq/), implemented via [gojq](https://github.com/itchyny/gojq). + +Returns a single value for single results, or an array for multiple results. + +``` +{{ .books | jq `[.works[]|{"title":.title,"authors":[.authors[].name],"published":.first_publish_year}][0]` }} +// map[authors:[Lewis Carroll] published:1865 title:Alice's Adventures in Wonderland] +``` + +### Keys + +Returns a list of keys from one or more maps (ordered by map position then alphabetically). + +``` +{{ coll.Keys (dict "foo" 1 "bar" 2) }} // [bar foo] +``` + +### Values + +Returns a list of values from one or more maps (ordered by map position then key alphabetically). + +``` +{{ coll.Values (dict "foo" 1 "bar" 2) }} // [2 1] +``` + +### append + +Appends a value to the end of a list (produces a new list). + +``` +{{ slice 1 1 2 3 | append 5 }} // [1 1 2 3 5] +``` + +### prepend + +Prepends a value to the beginning of a list (produces a new list). + +``` +{{ slice 4 3 2 1 | prepend 5 }} // [5 4 3 2 1] +``` + +### uniq + +Removes duplicate values from a list, without changing order (produces a new list). + +``` +{{ slice 1 2 3 2 3 4 1 5 | uniq }} // [1 2 3 4 5] +``` + +### flatten + +Flattens a nested list. Defaults to complete flattening; can be limited with `depth`. + +``` +{{ "[[1,2],[],[3,4],[[[5],6],7]]" | jsonArray | flatten }} // [1 2 3 4 5 6 7] +{{ coll.Flatten 2 ("[[1,2],[],[3,4],[[[5],6],7]]" | jsonArray) }} // [1 2 3 4 [[5] 6] 7] +``` + +### reverse + +Reverses a list (produces a new list). + +``` +{{ slice 4 3 2 1 | reverse }} // [1 2 3 4] +``` + +### Sort + +Sorts a list using natural sort order. Maps and structs can be sorted by a named key. + +``` +{{ slice "foo" "bar" "baz" | coll.Sort }} // [bar baz foo] +{{ sort (slice 3 4 1 2 5) }} // [1 2 3 4 5] +``` + +### Merge + +Combines multiple maps. The first map is the base; subsequent maps provide overrides (left-to-right precedence). + +``` +{{ $default := dict "foo" 1 "bar" 2}} +{{ $config := dict "foo" 8 }} +{{ merge $config $default }} +// map[bar:2 foo:8] +``` + +### Pick + +Returns a new map containing only the specified keys. + +``` +{{ $data := dict "foo" 1 "bar" 2 "baz" 3 }} +{{ coll.Pick "foo" "baz" $data }} // map[baz:3 foo:1] +``` + +### Omit + +Returns a new map without the specified keys. + +``` +{{ $data := dict "foo" 1 "bar" 2 "baz" 3 }} +{{ coll.Omit "foo" "baz" $data }} // map[bar:2] +``` + +--- + +## Convert + +### bool + +Converts a true-ish string to a boolean. + +``` +{{ $FOO := true }} +{{ if $FOO }}foo{{ else }}bar{{ end }} // foo +``` + +### default + +Provides a default value for empty inputs. + +``` +{{ "" | default "foo" }} // foo +{{ "bar" | default "baz" }} // bar +``` + +### Dict + +Same as [`coll.Dict`](#dict) — creates a map with string keys. + +``` +{{ $dict := conv.Dict "name" "Frank" "age" 42 }} +{{ $dict | data.ToYAML }} +// age: 42 +// name: Frank +``` + +### slice + +Creates a slice. Same as [`coll.slice`](#slice). + +``` +{{ range slice "Bart" "Lisa" "Maggie" }}Hello, {{ . }}{{ end }} +``` + +### has + +Same as [`coll.has`](#has). + +### join + +Concatenates array elements into a string with a separator. + +``` +{{ $a := slice 1 2 3 }}{{ join $a "-" }} // 1-2-3 +``` + +### urlParse + +Parses a URL string. + +``` +{{ ($u := conv.URL "https://example.com:443/foo/bar").Host }} // example.com:443 +{{ (conv.URL "https://user:supersecret@example.com").Redacted }} // https://user:xxxxx@example.com +``` + +### ParseInt + +Parses a string as an `int64` with a given base. + +``` +{{ $val := conv.ParseInt "7C0" 16 32 }} // 1984 +``` + +### ParseFloat + +Parses a string as a `float64`. + +``` +{{ $pi := conv.ParseFloat "3.14159265359" 64 }} +``` + +### ParseUint + +Parses a string as a `uint64`. + +``` +{{ conv.ParseUint "FFFFFFFFFFFFFFFF" 16 64 }} // 18446744073709551615 +``` + +### ToBool + +Converts input to a boolean. `true` values: `1`, `"t"`, `"true"`, `"yes"` (any case). + +``` +{{ conv.ToBool "yes" }} // true +{{ conv.ToBool true }} // true +{{ conv.ToBool false }} // false +{{ conv.ToBool "blah" }} // false +``` + +### ToBools + +Converts a list of inputs to an array of booleans. + +``` +{{ conv.ToBools "yes" true "0x01" }} // [true true true] +``` + +### ToInt64 + +Converts input to an `int64`. + +``` +{{ conv.ToInt64 "9223372036854775807" }} // 9223372036854775807 +{{ conv.ToInt64 "0x42" }} // 66 +{{ conv.ToInt64 true }} // 1 +``` + +### ToInt + +Converts input to an `int`. + +``` +{{ conv.ToInt "0x42" }} // 66 +``` + +### ToInt64s / ToInts + +Converts multiple inputs to an array of `int64`s / `int`s. + +``` +{{ conv.ToInt64s true 0x42 "123,456.99" "1.2345e+3" }} // [1 66 123456 1234] +``` + +### ToFloat64 + +Converts input to a `float64`. + +``` +{{ conv.ToFloat64 "8.233e-1" }} // 0.8233 +{{ conv.ToFloat64 "9,000.09" }} // 9000.09 +``` + +### ToFloat64s + +Converts multiple inputs to an array of `float64`s. + +``` +{{ conv.ToFloat64s true 0x42 "123,456.99" "1.2345e+3" }} // [1 66 123456.99 1234.5] +``` + +### ToString + +Converts any input to a `string`. + +``` +{{ conv.ToString 0xFF }} // 255 +{{ dict "foo" "bar" | conv.ToString }} // map[foo:bar] +``` + +### ToStrings + +Converts multiple inputs to an array of `string`s. + +``` +{{ conv.ToStrings nil 42 true 0xF (slice 1 2 3) }} // [nil 42 true 15 [1 2 3]] +``` + +--- + +## Cryptography + +### crypto.SHA1 / SHA256 / SHA384 / SHA512 + +Computes a checksum as a hexadecimal string. + +``` +{{ crypto.SHA1 "foo" }} // f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 +{{ crypto.SHA512 "bar" }} // cc06808cbbee0510331aa97974132e8dc296aeb... +``` + +--- + +## Data + +### json + +Converts a JSON string into an object. + +``` +{{ ('{"hello":"world"}' | json).hello }} // world +``` + +### jsonArray + +Converts a JSON string into a slice. Only works for JSON Arrays. + +``` +{{ ('[ "you", "world" ]' | jsonArray) 1 }} // world +``` + +### yaml + +Converts a YAML string into an object. + +``` +{{ $FOO := "hello: world" }} +Hello {{ (yaml $FOO).hello }} // Hello world +``` + +### yamlArray + +Converts a YAML string into a slice. + +``` +{{ $FOO := "- hello\n- world" }} +{{ (yamlArray $FOO) 0 }} // hello +``` + +### toml + +Converts a TOML document into an object. + +``` +{{ $t := `[data] +hello = "world"` }} +Hello {{ (toml $t).data.hello }} // Hello world +``` + +### csv + +Converts a CSV-format string into a 2-dimensional string array. + +``` +{{ $c := `C,32 +Go,25 +COBOL,357` }} +{{ range ($c | csv) }} +{{ index . 0 }} has {{ index . 1 }} keywords. +{{ end }} +// C has 32 keywords. +// Go has 25 keywords. +// COBOL has 357 keywords. +``` + +### csvByRow + +Converts a CSV-format string into a slice of maps. + +``` +{{ $c := `lang,keywords +C,32 +Go,25` }} +{{ range ($c | csvByRow) }} +{{ .lang }} has {{ .keywords }} keywords. +{{ end }} +``` + +### csvByColumn + +Like `csvByRow`, but returns a columnar map. + +``` +{{ $langs := ($c | csvByColumn ";" "lang,keywords").lang }} +``` + +### toJSON + +Converts an object to a JSON document. + +``` +{{ (`{"foo":{"hello":"world"}}` | json).foo | toJSON }} // {"hello":"world"} +``` + +### toJSONPretty + +Converts an object to a pretty-printed JSON document. + +``` +{{ `{"hello":"world"}` | data.JSON | data.ToJSONPretty " " }} +// { +// "hello": "world" +// } +``` + +### toYAML + +Converts an object to a YAML document. + +``` +{{ (`{"foo":{"hello":"world"}}` | data.JSON).foo | data.ToYAML }} // hello: world +``` + +### toTOML + +Converts an object to a TOML document. + +``` +{{ `{"foo":"bar"}` | data.JSON | data.ToTOML }} // foo = "bar" +``` + +### toCSV + +Converts a `[][]string` to a CSV document. + +``` +{{ $rows := (jsonArray `[["first","second"],["1","2"],["3","4"]]`) -}} +{{ data.ToCSV ";" $rows }} +``` + +--- + +## filepath + +### Base + +Returns the last element of path. + +``` +{{ filepath.Base "/tmp/foo" }} // foo +``` + +### Clean + +Returns the shortest equivalent path by lexical processing. + +``` +{{ filepath.Clean "/tmp//foo/../" }} // /tmp +``` + +### Dir + +Returns all but the last element of path. + +``` +{{ filepath.Dir "/tmp/foo" }} // /tmp +``` + +### Ext + +Returns the file name extension. + +``` +{{ filepath.Ext "/tmp/foo.csv" }} // .csv +``` + +### IsAbs + +Reports whether the path is absolute. + +``` +{{ filepath.IsAbs "/tmp/foo.csv" }} // true +``` + +### Join + +Joins path elements into a single path. + +``` +{{ filepath.Join "/tmp" "foo" "bar" }} // /tmp/foo/bar +``` + +### Match + +Reports whether name matches the shell file name pattern. + +``` +{{ filepath.Match "*.csv" "foo.csv" }} // true +``` + +### Rel + +Returns a relative path from basepath to targetpath. + +``` +{{ filepath.Rel "/a" "/a/b/c" }} // b/c +``` + +### Split + +Splits path into directory and file name. + +``` +{{ $p := filepath.Split "/tmp/foo" }}{{ index $p 0 }} / {{ index $p 1 }} +// /tmp/ / foo +``` + +### ToSlash / FromSlash + +Converts path separators. + +``` +{{ filepath.ToSlash "/foo/bar" }} // /foo/bar +{{ filepath.FromSlash "/foo/bar" }} // /foo/bar +``` + +### VolumeName + +Returns the leading volume name (Windows). + +``` +{{ filepath.VolumeName "C:/foo/bar" }} // C: +``` + +--- + +## math + +### Abs + +Returns the absolute value. + +``` +{{ math.Abs -3.5 }} // 3.5 +{{ math.Abs -42 }} // 42 +``` + +### Add + +Adds all given operands. + +``` +{{ math.Add 1 2 3 4 }} // 10 +{{ math.Add 1.5 2 3 }} // 6.5 +``` + +### Ceil + +Returns the least integer ≥ the given value. + +``` +{{ math.Ceil 5.1 }} // 6 +{{ math.Ceil 42 }} // 42 +``` + +### Div + +Divides the first number by the second (result is `float64`). + +``` +{{ math.Div 8 2 }} // 4 +{{ math.Div 3 2 }} // 1.5 +``` + +### Floor + +Returns the greatest integer ≤ the given value. + +``` +{{ math.Floor 5.1 }} // 4 +{{ math.Floor 42 }} // 42 +``` + +### IsFloat + +Returns whether the given number is a floating-point literal. + +``` +{{ math.IsFloat 1.0 }} // true +{{ math.IsFloat 42 }} // false +``` + +### IsInt + +Returns whether the given number is an integer. + +``` +{{ math.IsInt 42 }} // true +{{ math.IsInt 3.14 }} // false +``` + +### IsNum + +Returns whether the given input is a number. + +``` +{{ math.IsNum "foo" }} // false +{{ math.IsNum 0xDeadBeef }} // true +``` + +### Max + +Returns the largest number provided. + +``` +{{ math.Max 0 8.0 4.5 "-1.5e-11" }} // 8 +``` + +### Min + +Returns the smallest number provided. + +``` +{{ math.Min 0 8 4.5 "-1.5e-11" }} // -1.5e-11 +``` + +### Mul + +Multiplies all given operands. + +``` +{{ math.Mul 8 8 2 }} // 128 +``` + +### Pow + +Calculates an exponent. + +``` +{{ math.Pow 10 2 }} // 100 +{{ math.Pow 2 32 }} // 4294967296 +{{ math.Pow 1.5 2 }} // 2.25 +``` + +### Rem + +Returns the remainder from integer division. + +``` +{{ math.Rem 5 3 }} // 2 +{{ math.Rem -5 3 }} // -2 +``` + +### Round + +Returns the nearest integer, rounding half away from zero. + +``` +{{ math.Round 5.1 }} // 5 +{{ math.Round 42.9 }} // 43 +{{ math.Round 6.5 }} // 7 +``` + +### Seq + +Returns a sequence from `start` to `end` in steps of `step`. + +``` +{{ range (math.Seq 5) }}{{.}} {{end}} // 1 2 3 4 5 +{{ conv.Join (math.Seq 10 -3 2) ", " }} // 10, 8, 6, 4, 2, 0, -2 +``` + +### Sub + +Subtracts the second from the first operand. + +``` +{{ math.Sub 3 1 }} // 2 +``` + +--- + +## Path + +### Base + +Returns the last element of path. + +``` +{{ path.Base "/tmp/foo" }} // foo +``` + +### Clean + +Returns the shortest equivalent path. + +``` +{{ path.Clean "/tmp//foo/../" }} // /tmp +``` + +### Dir + +Returns the directory component of path. + +``` +{{ path.Dir "/tmp/foo" }} // /tmp +``` + +### Ext + +Returns the file name extension. + +``` +{{ path.Ext "/tmp/foo.csv" }} // .csv +``` + +### IsAbs + +Reports whether the path is absolute. + +``` +{{ path.IsAbs "/tmp/foo.csv" }} // true +{{ path.IsAbs "../foo.csv" }} // false +``` + +### Join + +Joins path elements. + +``` +{{ path.Join "/tmp" "foo" "bar" }} // /tmp/foo/bar +``` + +### Match + +Reports whether name matches the shell file name pattern. + +``` +{{ path.Match "*.csv" "foo.csv" }} // true +``` + +### Split + +Splits path into directory and file name. + +``` +{{ index (path.Split "/tmp/foo") 0 }} // /tmp/ +``` + +--- + +## Random + +### ASCII + +Generates a random string from the printable 7-bit ASCII set. + +``` +{{ random.ASCII 8 }} // _woJ%D&K +``` + +### Alpha + +Generates a random alphabetical string. + +``` +{{ random.Alpha 42 }} // oAqHKxHiytYicMxTMGHnUnAfltPVZDhFkVkgDvatJK +``` + +### AlphaNum + +Generates a random alphanumeric string. + +``` +{{ random.AlphaNum 16 }} // 4olRl9mRmVp1nqSm +``` + +### String + +Generates a random string of a desired length with an optional character set. + +``` +{{ random.String 8 }} // FODZ01u_ +{{ random.String 16 `[[:xdigit:]]` }} // B9e0527C3e45E1f3 +{{ random.String 8 "c" "m" }} // ffmidgjc +``` + +### Item + +Picks an element at random from a slice. + +``` +{{ random.Item (slice "red" "green" "blue") }} // blue +``` + +### Number + +Picks a random integer (default: 0–100). + +``` +{{ random.Number }} // 55 +{{ random.Number -10 10 }} // -3 +{{ random.Number 5 }} // 2 +``` + +### Float + +Picks a random floating-point number. + +``` +{{ random.Float }} // 0.2029946480303966 +{{ random.Float 100 }} // 71.28595374161743 +{{ random.Float -100 200 }} // 105.59119437834909 +``` + +--- + +## regexp + +### Find + +Returns the leftmost match in `input` for the given regular expression. + +``` +{{ regexp.Find "[a-z]{3}" "foobar" }} // foo +{{ "will not match" | regexp.Find "[0-9]" }} // (empty) +``` + +### FindAll + +Returns all successive matches of a regular expression. + +``` +{{ regexp.FindAll "[a-z]{3}" "foobar" | toJSON }} // ["foo","bar"] +{{ "foo bar baz qux" | regexp.FindAll "[a-z]{3}" 3 | toJSON }} // ["foo","bar","baz"] +``` + +### Match + +Returns `true` if the regular expression matches the input. + +``` +{{ "hairyhenderson" | regexp.Match `^h` }} // true +``` + +### QuoteMeta + +Escapes all regular expression metacharacters in the input. + +``` +{{ `{hello}` | regexp.QuoteMeta }} // \{hello\} +``` + +### Replace + +Replaces matches of a regular expression with a replacement string (with `$` variable expansion). + +``` +{{ regexp.Replace "(foo)bar" "$1" "foobar" }} // foo +{{ regexp.Replace "(?P[a-zA-Z]+) (?P[a-zA-Z]+)" "${last}, ${first}" "Alan Turing" }} // Turing, Alan +``` + +### ReplaceLiteral + +Replaces matches with a literal replacement string (no `$` expansion). + +``` +{{ regexp.ReplaceLiteral "(foo)bar" "$1" "foobar" }} // $1 +``` + +### Split + +Splits a string into sub-strings separated by the expression. + +``` +{{ regexp.Split `[\s,.]` "foo bar,baz.qux" | toJSON }} // ["foo","bar","baz","qux"] +``` + +--- + +## Strings + +### Abbrev + +Abbreviates a string using `...`. + +``` +{{ "foobarbazquxquux" | strings.Abbrev 9 }} // foobar... +{{ "foobarbazquxquux" | strings.Abbrev 6 9 }} // ...baz... +``` + +### Contains + +Reports whether a substring is contained within a string. + +``` +{{ "foo" | strings.Contains "f" }} // true +``` + +### HasPrefix + +Tests whether a string begins with a certain prefix. + +``` +{{ "http://example.com" | strings.HasPrefix "http://" }} // true +``` + +### HasSuffix + +Tests whether a string ends with a certain suffix. + +``` +{{ if not ("http://example.com" | strings.HasSuffix ":80") }}:80{{ end }} +// :80 +``` + +### Indent + +Indents each line of a string. + +``` +{{ `{"bar": {"baz": 2}}` | json | toYAML | strings.Indent " " }} +// bar: +// baz: 2 +``` + +### Split + +Creates a slice by splitting a string on a delimiter. + +``` +{{ "Bart,Lisa,Maggie" | strings.Split "," }} // [Bart Lisa Maggie] + +{{ range ("Bart,Lisa,Maggie" | strings.Split ",") }}Hello, {{.}}{{ end }} +// Hello, Bart +// Hello, Lisa +// Hello, Maggie + +{{ index ("Bart,Lisa,Maggie" | strings.Split ",") 0 }} // Bart +``` + +### SplitN + +Creates a slice by splitting a string on a delimiter with a count limit. + +``` +{{ range ("foo:bar:baz" | strings.SplitN ":" 2) }}{{.}}{{ end }} +// foo +// bar:baz +``` + +### Quote + +Surrounds a string with double-quote characters. + +``` +{{ "in" | quote }} // "in" +{{ strings.Quote 500 }} // "500" +``` + +### Repeat + +Returns `count` copies of the input string. + +``` +{{ "hello " | strings.Repeat 5 }} // hello hello hello hello hello +``` + +### ReplaceAll + +Replaces all occurrences of a substring. + +``` +{{ strings.ReplaceAll "." "-" "172.21.1.42" }} // 172-21-1-42 +{{ "172.21.1.42" | strings.ReplaceAll "." "-" }} // 172-21-1-42 +``` + +### Slug + +Creates a URL-friendly slug from a string. + +``` +{{ "Hello, world!" | strings.Slug }} // hello-world +``` + +### shellQuote + +Quotes a string for safe use in a POSIX shell. + +``` +{{ slice "one word" "foo='bar baz'" | shellQuote }} +// 'one word' 'foo='"'"'bar baz'"'"'' +``` + +### squote + +Surrounds a string with single-quote characters. + +``` +{{ "in" | squote }} // 'in' +{{ "it's a banana" | squote }} // 'it''s a banana' +``` + +### Title + +Converts a string to title-case. + +``` +{{ strings.Title "hello, world!" }} // Hello, World! +``` + +### ToLower + +Converts to lower-case. + +``` +{{ strings.ToLower "HELLO, WORLD!" }} // hello, world! +``` + +### ToUpper + +Converts to upper-case. + +``` +{{ strings.ToUpper "hello, world!" }} // HELLO, WORLD! +``` + +### Trim + +Removes the given characters from the beginning and end of a string. + +``` +{{ "_-foo-_" | strings.Trim "_-" }} // foo +``` + +### TrimPrefix + +Removes a leading prefix from a string. + +``` +{{ "hello, world" | strings.TrimPrefix "hello, " }} // world +``` + +### TrimSpace + +Removes leading and trailing whitespace. + +``` +{{ " \n\t foo" | strings.TrimSpace }} // foo +``` + +### TrimSuffix + +Removes a trailing suffix from a string. + +``` +{{ "hello, world" | strings.TrimSuffix "world" }} // hello, +``` + +### Trunc + +Truncates a string to the given length. + +``` +{{ "hello, world" | strings.Trunc 5 }} // hello +``` + +### CamelCase + +Converts a sentence to CamelCase. + +``` +{{ "Hello, World!" | strings.CamelCase }} // HelloWorld +{{ "hello jello" | strings.CamelCase }} // helloJello +``` + +### SnakeCase + +Converts a sentence to snake_case. + +``` +{{ "Hello, World!" | strings.SnakeCase }} // Hello_world +{{ "hello jello" | strings.SnakeCase }} // hello_jello +``` + +### KebabCase + +Converts a sentence to kebab-case. + +``` +{{ "Hello, World!" | strings.KebabCase }} // Hello-world +{{ "hello jello" | strings.KebabCase }} // hello-jello +``` + +### WordWrap + +Inserts line breaks so that lines are at most `width` characters wide. + +``` +{{ "Hello, World!" | strings.WordWrap 7 }} +// Hello, +// World! +``` + +### RuneCount + +Returns the number of Unicode code-points in the input. + +``` +{{ strings.RuneCount "Ω" }} // 1 +``` + +### contains (alias) + +Reports whether the second string is contained within the first. + +``` +{{ if contains "foo" "f" }}yes{{ else }}no{{ end }} // yes +``` + +### HasPrefix (alias) + +Tests whether the string begins with a certain substring. + +``` +{{ if hasPrefix "http://example.com" "https" }}foo{{ else }}bar{{ end }} // bar +``` + +### HasSuffix (alias) + +Tests whether the string ends with a certain substring. + +``` +{{ if not (hasSuffix "http://example.com" ":80") }}:80{{ end }} // :80 +``` + +### split (alias) + +Same as `strings.Split`. + +``` +{{ range split "Bart,Lisa,Maggie" "," }}Hello, {{ . }}{{ end }} +``` + +### splitN (alias) + +Same as `strings.SplitN`. + +``` +{{ range splitN "foo:bar:baz" ":" 2 }}{{ . }}{{ end }} +// foo +// bar:baz +``` + +### Trim (alias) + +``` +{{ trim " world " " " }} // world +``` + +--- + +## Test + +### Fail + +Causes template generation to fail immediately, with an optional message. + +``` +{{ fail }} +{{ test.Fail "something is wrong!" }} +``` + +### IsKind + +Reports whether the argument is of the given Kind. + +``` +{{ $data := "hello world" }} +{{ if isKind "string" $data }}{{ $data }} is a string{{ end }} +// hello world is a string +``` + +### Kind + +Reports the _kind_ of the given argument. + +``` +{{ kind "hello world" }} // string +{{ dict "key1" true | test.Kind }} // map +``` + +### ternary + +Returns one of two values depending on whether the third is truthy. + +``` +{{ ternary "FOO" "BAR" false }} // BAR +{{ ternary "FOO" "BAR" "yes" }} // FOO +``` + +--- + +## Time + +### Now + +Returns the current local time as `time.Time`. + +``` +{{ (time.Now).UTC.Format "Day 2 of month 1 in year 2006 (timezone MST)" }} +// Day 14 of month 10 in year 2017 (timezone UTC) + +{{ ((time.Now).AddDate 0 1 0).Format "Mon Jan 2 15:04:05 MST 2006" }} +// Tue Nov 14 09:57:02 EST 2017 +``` + +### Parse + +Parses a timestamp string using a given layout. + +``` +{{ (time.Parse "2006-01-02" "1993-10-23").Format "Monday January 2, 2006 MST" }} +// Saturday October 23, 1993 UTC +``` + +### ParseDuration + +Parses a duration string. Valid units: `ns`, `us`, `ms`, `s`, `m`, `h`. + +``` +{{ ((time.Now).Add (time.ParseDuration "2h30m")).Format time.Kitchen }} // 3:13AM +``` + +### ParseLocal + +Same as `time.Parse`, but interprets the time in the local time zone. + +``` +{{ (time.ParseLocal time.Kitchen "6:00AM").Format "15:04 MST" }} // 06:00 EST +``` + +### ParseInLocation + +Same as `time.Parse`, but interprets the time in the given location's time zone. + +``` +{{ (time.ParseInLocation time.Kitchen "Africa/Luanda" "6:00AM").Format "15:04 MST" }} // 06:00 LMT +``` + +### Since + +Returns the time elapsed since a given time. + +``` +{{ $t := time.Parse time.RFC3339 "1970-01-01T00:00:00Z" }} +{{ time.Since $t }} // 423365h0m24.353828924s +``` + +### Unix + +Returns the `time.Time` corresponding to a Unix timestamp (seconds since epoch). + +``` +{{ (time.Unix 42).UTC.Format time.Stamp }} // Jan 1, 00:00:42 +``` + +### Until + +Returns the duration until a given time. + +``` +{{ $t := time.Parse time.RFC3339 "2020-01-01T00:00:00Z" }} +{{ time.Until $t }} // 14922h56m46.578625891s +``` + +### ZoneName + +Returns the local system's time zone name. + +``` +{{ time.ZoneName }} // UTC +``` + +### ZoneOffset + +Returns the local system's time zone offset in seconds. + +``` +{{ time.ZoneOffset }} // 0 +``` diff --git a/README.md b/README.md index 9dc4186df..2d459f1a1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,154 @@ # Gomplate +Flanksource Gomplate is a fork of [hairyhenderson/gomplate](https://github.com/hairyhenderson/gomplate) that provides a unified templating library supporting multiple expression languages. -Flanksource Gomplate is a fork of the [https://github.com/hairyhenderson/gomplate](https://github.com/hairyhenderson/gomplate) tool and adds +## Features -* A standard library interface for running templates using: - * Go text/template - * CEL (with celext, kubernetes extensions, and many gomplate functions remapped) - * Javascript (otto) +- **Go Text/Template** – Full [Go `text/template`](https://pkg.go.dev/text/template) support with an extended function library from gomplate (base64, collections, crypto, data formats, filepath, math, random, regexp, strings, time, and more) +- **CEL (Common Expression Language)** – [CEL](https://cel.dev/) support with: + - Standard CEL operators and built-ins + - [celext](https://github.com/google/cel-go/tree/master/ext) extensions (strings, encoders, lists, math, sets) + - Kubernetes-specific helpers (`k8s.*`) + - AWS helpers (`aws.*`) + - Many gomplate functions remapped into CEL (`base64`, `math`, `random`, `regexp`, `filepath`, `crypto`, `sets`, etc.) + - Optional-type support (`obj.?field.orValue("default")`) +- **JavaScript (otto)** – Lightweight JS evaluation via [otto](https://github.com/robertkrimen/otto) +- **Struct templating** – Walk Go structs/maps and apply templates to string fields in-place + +## Quick Reference + +| Language | Example | Documentation | +|---|---|---| +| Go Template | `{{ .name \| strings.ToUpper }}` | [GO_TEMPLATE.md](GO_TEMPLATE.md) | +| CEL | `name.upperAscii()` | [CEL.md](CEL.md) | + +### CEL Quick Reference + +``` +# Arithmetic +2 + 3 // 5 +"hello" + " world" // "hello world" +[1,2] + [3,4] // [1,2,3,4] + +# Comparison / Logic +x > 5 && y != null +true ? "yes" : "no" + +# String methods +"hello world".upperAscii() // "HELLO WORLD" +"hello world".split(" ") // ["hello", "world"] +"hello".startsWith("he") // true +"hello world".contains("world") // true + +# Collections +[1,2,3].filter(e, e > 1) // [2, 3] +[1,2,3].map(e, e * 2) // [2, 4, 6] +[1,2,3].all(e, e > 0) // true +{"a":1,"b":2}.keys() // ["a","b"] + +# Optional / null-safety +obj.?field.orValue("default") +obj.?a.?b.orValue("fallback") + +# Kubernetes +k8s.isHealthy(pod) +k8s.cpuAsMillicores("500m") // 500 +k8s.memoryAsBytes("1Gi") + +# Math +math.Add([1,2,3]) // 6 +math.greatest([1,2,3]) // 3 + +# Time +time.Now() +time.Since(timestamp("2023-01-01T00:00:00Z")) +``` + +See [CEL.md](CEL.md) for full reference. + +### Go Template Quick Reference + +``` +# Variables and output +{{ .name }} +{{ $x := "hello" }}{{ $x | strings.ToUpper }} + +# Control flow +{{ if eq .status "ok" }}OK{{ else }}FAIL{{ end }} +{{ range .items }}{{ . }}{{ end }} + +# String functions +{{ "hello world" | strings.ToUpper }} // HELLO WORLD +{{ "hello world" | strings.Split " " }} // [hello world] +{{ "hello" | strings.Repeat 3 }} // hellohellohello + +# Data +{{ '{"key":"val"}' | json }} // map access +{{ .obj | toJSON }} +{{ .obj | toYAML }} + +# Collections +{{ coll.Dict "a" 1 "b" 2 | toJSON }} // {"a":1,"b":2} +{{ slice 1 2 3 | coll.Reverse | toJSON }} // [3,2,1] +{{ coll.Sort (slice "b" "a" "c") | toJSON }} // ["a","b","c"] + +# Math +{{ math.Add 1 2 3 }} // 6 +{{ math.Max 1 5 3 }} // 5 + +# base64 +{{ base64.Encode "hello" }} // aGVsbG8= +{{ "aGVsbG8=" | base64.Decode }} // hello + +# Crypto +{{ crypto.SHA256 "hello" }} + +# Delimiters (change if conflict with Helm) +# gotemplate: left-delim=$[[ right-delim=]] +$[[ .name ]] +``` + +See [GO_TEMPLATE.md](GO_TEMPLATE.md) for full reference. + +## Installation + +```go +import "github.com/flanksource/gomplate/v3" +``` + +## Usage + +### Go Template + +```go +result, err := gomplate.RunTemplate(map[string]any{ + "name": "world", +}, gomplate.Template{ + Template: `Hello, {{ .name }}!`, +}) +``` + +### CEL + +```go +result, err := gomplate.RunTemplate(map[string]any{ + "name": "world", +}, gomplate.Template{ + Expression: `"Hello, " + name + "!"`, +}) +``` + +### Struct Templating + +```go +type Config struct { + Message string + Count int +} + +cfg := Config{Message: "Hello, {{ .name }}!"} + +err := gomplate.Walk(map[string]any{"name": "world"}, &cfg) +// cfg.Message == "Hello, world!" +```