From 3d70c4cae0ae32ba460b17b6a642105c428f2e02 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 20 Mar 2026 07:20:22 +0000
Subject: [PATCH] Add comprehensive documentation: README, CEL.md, and
GO_TEMPLATE.md
Co-authored-by: moshloop <1489660+moshloop@users.noreply.github.com>
---
CEL.md | 1469 ++++++++++++++++++++++++++++++++++++++++++++++++
GO_TEMPLATE.md | 1395 +++++++++++++++++++++++++++++++++++++++++++++
README.md | 154 ++++-
3 files changed, 3013 insertions(+), 5 deletions(-)
create mode 100644 CEL.md
create mode 100644 GO_TEMPLATE.md
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!"
+```