diff --git a/docs/template-functions.md b/docs/template-functions.md index 75c0e7c4..7c9593e6 100644 --- a/docs/template-functions.md +++ b/docs/template-functions.md @@ -1,90 +1,128 @@ -# String Functions +# Template functions + +## Table of Contents + +- [String Functions](#string-functions) +- [String List Functions](#string-list-functions) +- [Integer Math Functions](#integer-math-functions) +- [Integer List Functions](#integer-list-functions) +- [Date Functions](#date-functions) +- [Default Functions](#default-functions) +- [Encoding Functions](#encoding-functions) +- [Lists and List Functions](#lists-and-list-functions) +- [Dictionaries and Dict Functions](#dictionaries-and-dict-functions) +- [Type Conversion Functions](#type-conversion-functions) +- [Path and Filepath Functions](#path-and-filepath-functions) +- [Flow Control Functions](#flow-control-functions) +- [UUID Functions](#uuid-functions) +- [Reflection Functions](#reflection-functions) +- [Cryptographic and Security Functions](#cryptographic-and-security-functions) +- [URL Functions](#url-functions) + +## String Functions Sprig has a number of string manipulation functions. -## trim - -The `trim` function removes space from either side of a string: + + + + + -## trimAll - -Remove given characters from the front or back of a string: + + + + -## trimSuffix - -Trim just the suffix from a string: + + + + -## trimPrefix - -Trim just the prefix from a string: + + + + -## upper - -Convert the entire string to uppercase: + + + + -## lower - -Convert the entire string to lowercase: + + + + -## title - -Convert to title case: + + + + -## repeat - -Repeat a string multiple times: + + + + -## substr - -Get a substring from a string. It takes three parameters: + + + + -## trunc - -Truncate a string (and add no suffix) + + + + -## contains - -Test to see if one string is contained inside of another: + + + + -## hasPrefix and hasSuffix - -The `hasPrefix` and `hasSuffix` functions test whether a string has a given + + + + -## quote and squote - -These functions wrap a string in double quotes (`quote`) or single quotes + + + + -## cat - -The `cat` function concatenates multiple strings together into one, separating + + + + -## indent - -The `indent` function indents every line in a given string to the specified + + + + -## nindent - -The `nindent` function is the same as the indent function, but prepends a new + + + + -## replace - -Perform simple string replacement. + + + + -## plural - -Pluralize a string. + + + + -## regexMatch, mustRegexMatch - -Returns true if the input string contains any match of the regular expression. + + + + -## regexFindAll, mustRegexFindAll - -Returns a slice of all matches of the regular expression in the input string. + + + + -## regexFind, mustRegexFind - -Return the first (left most) match of the regular expression in the input string + + + + -## regexReplaceAll, mustRegexReplaceAll - -Returns a copy of the input string, replacing matches of the Regexp with the replacement string replacement. + + + + -## regexReplaceAllLiteral, mustRegexReplaceAllLiteral - -Returns a copy of the input string, replacing matches of the Regexp with the replacement string replacement + + + + -## regexSplit, mustRegexSplit - -Slices the input string into substrings separated by the expression and returns a slice of the substrings between those expression matches. The last parameter `n` determines the number of substrings to return, where `-1` means return all matches + + + + -## regexQuoteMeta - -Returns a string that escapes all regular expression metacharacters inside the argument text; + + + + +
trimThe `trim` function removes space from either side of a string: ``` trim " hello " ``` The above produces `hello` +
trimAllRemove given characters from the front or back of a string: ``` trimAll "$" "$5.00" ``` The above returns `5.00` (as a string). +
trimSuffixTrim just the suffix from a string: ``` trimSuffix "-" "hello-" ``` The above returns `hello` +
trimPrefixTrim just the prefix from a string: ``` trimPrefix "-" "-hello" ``` The above returns `hello` +
upperConvert the entire string to uppercase: ``` upper "hello" ``` The above returns `HELLO` +
lowerConvert the entire string to lowercase: ``` lower "HELLO" ``` The above returns `hello` +
titleConvert to title case: ``` title "hello world" ``` The above returns `Hello World` +
repeatRepeat a string multiple times: ``` repeat 3 "hello" ``` The above returns `hellohellohello` +
substrGet a substring from a string. It takes three parameters: - start (int) - end (int) @@ -95,10 +133,12 @@ substr 0 5 "hello world" ``` The above returns `hello` +
truncTruncate a string (and add no suffix) ``` trunc 5 "hello world" @@ -111,20 +151,24 @@ trunc -5 "hello world" ``` The above produces `world`. +
containsTest to see if one string is contained inside of another: ``` contains "cat" "catch" ``` The above returns `true` because `catch` contains `cat`. +
hasPrefix and hasSuffixThe `hasPrefix` and `hasSuffix` functions test whether a string has a given prefix or suffix: ``` @@ -132,15 +176,19 @@ hasPrefix "cat" "catch" ``` The above returns `true` because `catch` has the prefix `cat`. +
quote and squoteThese functions wrap a string in double quotes (`quote`) or single quotes (`squote`). +
catThe `cat` function concatenates multiple strings together into one, separating them with spaces: ``` @@ -148,10 +196,12 @@ cat "hello" "beautiful" "world" ``` The above produces `hello beautiful world` +
indentThe `indent` function indents every line in a given string to the specified indent width. This is useful when aligning multi-line strings: ``` @@ -159,10 +209,12 @@ indent 4 $lots_of_text ``` The above will indent every line of text by 4 space characters. +
nindentThe `nindent` function is the same as the indent function, but prepends a new line to the beginning of the string. ``` @@ -171,10 +223,12 @@ nindent 4 $lots_of_text The above will indent every line of text by 4 space characters and add a new line to the beginning. +
replacePerform simple string replacement. It takes three arguments: @@ -187,10 +241,12 @@ It takes three arguments: ``` The above will produce `I-Am-Henry-VIII` +
pluralPluralize a string. ``` len $fish | plural "one anchovy" "many anchovies" @@ -210,10 +266,12 @@ NOTE: Sprig does not currently support languages with more complex pluralization rules. And `0` is considered a plural because the English language treats it as such (`zero anchovies`). The Sprig developers are working on a solution for better internationalization. +
regexMatch, mustRegexMatchReturns true if the input string contains any match of the regular expression. ``` regexMatch "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$" "test@acme.com" @@ -223,10 +281,12 @@ The above produces `true` `regexMatch` panics if there is a problem and `mustRegexMatch` returns an error to the template engine if there is a problem. +
regexFindAll, mustRegexFindAllReturns a slice of all matches of the regular expression in the input string. The last parameter n determines the number of substrings to return, where -1 means return all matches ``` @@ -237,10 +297,12 @@ The above produces `[2 4 6 8]` `regexFindAll` panics if there is a problem and `mustRegexFindAll` returns an error to the template engine if there is a problem. +
regexFind, mustRegexFindReturn the first (left most) match of the regular expression in the input string ``` regexFind "[a-zA-Z][1-9]" "abcd1234" @@ -250,10 +312,12 @@ The above produces `d1` `regexFind` panics if there is a problem and `mustRegexFind` returns an error to the template engine if there is a problem. +
regexReplaceAll, mustRegexReplaceAllReturns a copy of the input string, replacing matches of the Regexp with the replacement string replacement. Inside string replacement, $ signs are interpreted as in Expand, so for instance $1 represents the text of the first submatch ``` @@ -264,10 +328,12 @@ The above produces `-W-xxW-` `regexReplaceAll` panics if there is a problem and `mustRegexReplaceAll` returns an error to the template engine if there is a problem. +
regexReplaceAllLiteral, mustRegexReplaceAllLiteralReturns a copy of the input string, replacing matches of the Regexp with the replacement string replacement The replacement string is substituted directly, without using Expand ``` @@ -278,10 +344,12 @@ The above produces `-${1}-${1}-` `regexReplaceAllLiteral` panics if there is a problem and `mustRegexReplaceAllLiteral` returns an error to the template engine if there is a problem. +
regexSplit, mustRegexSplitSlices the input string into substrings separated by the expression and returns a slice of the substrings between those expression matches. The last parameter `n` determines the number of substrings to return, where `-1` means return all matches ``` regexSplit "z+" "pizza" -1 @@ -291,10 +359,12 @@ The above produces `[pi a]` `regexSplit` panics if there is a problem and `mustRegexSplit` returns an error to the template engine if there is a problem. +
regexQuoteMetaReturns a string that escapes all regular expression metacharacters inside the argument text; the returned string is a regular expression matching the literal text. ``` @@ -302,19 +372,22 @@ regexQuoteMeta "1.2.3" ``` The above produces `1\.2\.3` - -## See Also... +
The [Conversion Functions](conversion.md) contain functions for converting strings. The [String List Functions](string_slice.md) contains functions for working with an array of strings. -# String List Functions + +## String List Functions These function operate on or generate slices of strings. In Go, a slice is a growable array. In Sprig, it's a special case of a `list`. -## join - -Join a list of strings into a single string, with the given separator. + + + + + -## splitList and split - -Split a string into a list of strings: + + + + -## splitn - -`splitn` function splits a string into a `dict` with `n` keys. It is designed to make + + + + -## sortAlpha - -The `sortAlpha` function sorts a list of strings into alphabetical (lexicographical) + + + + +
joinJoin a list of strings into a single string, with the given separator. ``` list "hello" "world" | join "_" @@ -329,10 +402,12 @@ list 1 2 3 | join "+" ``` The above will produce `1+2+3` +
splitList and splitSplit a string into a list of strings: ``` splitList "$" "foo$bar$baz" @@ -354,10 +429,12 @@ $a._0 ``` The above produces `foo` +
splitn`splitn` function splits a string into a `dict` with `n` keys. It is designed to make it easy to use template dot notation for accessing members: ``` @@ -371,97 +448,132 @@ $a._0 ``` The above produces `foo` +
sortAlphaThe `sortAlpha` function sorts a list of strings into alphabetical (lexicographical) order. It does _not_ sort in place, but returns a sorted copy of the list, in keeping with the immutability of lists. -# Integer Math Functions +
+ +## Integer Math Functions The following math functions operate on `int64` values. -## add - -Sum numbers with `add`. Accepts two or more inputs. + + + + + -## add1 + + + + -To increment by 1, use `add1` + + + + -## sub + + + + -To subtract, use `sub` + + + + -## div - -Perform integer division with `div` - -## mod - -Modulo with `mod` - -## mul - -Multiply with `mul`. Accepts two or more inputs. + + + + -## max - -Return the largest of a series of integers: + + + + -## min - -Return the smallest of a series of integers. + + + + -## floor - -Returns the greatest float value less than or equal to input value + + + + -## ceil - -Returns the greatest float value greater than or equal to input value + + + + -## round - -Returns a float value with the remainder rounded to the given number to digits after the decimal point. + + + + -## randInt -Returns a random integer value from min (inclusive) to max (exclusive). + + + + +
addSum numbers with `add`. Accepts two or more inputs. ``` add 1 2 3 ``` +
add1To increment by 1, use `add1` +
subTo subtract, use `sub` +
divPerform integer division with `div` +
modModulo with `mod` +
mulMultiply with `mul`. Accepts two or more inputs. ``` mul 1 2 3 ``` +
maxReturn the largest of a series of integers: This will return `3`: ``` max 1 2 3 ``` +
minReturn the smallest of a series of integers. `min 1 2 3` will return `1` +
floorReturns the greatest float value less than or equal to input value `floor 123.9999` will return `123.0` +
ceilReturns the greatest float value greater than or equal to input value `ceil 123.001` will return `124.0` +
roundReturns a float value with the remainder rounded to the given number to digits after the decimal point. `round 123.555555 3` will return `123.556` +
randIntReturns a random integer value from min (inclusive) to max (exclusive). ``` randInt 12 30 ``` The above will produce a random number in the range [12,30]. -# Integer List Functions +
-## until +## Integer List Functions -The `until` function builds a range of integers. + + + + + -## untilStep - -Like `until`, `untilStep` generates a list of counting integers. But it allows + + + + -## seq - -Works like the bash `seq` command. + + + + +
untilThe `until` function builds a range of integers. ``` until 5 @@ -470,10 +582,12 @@ until 5 The above generates the list `[0, 1, 2, 3, 4]`. This is useful for looping with `range $i, $e := until 5`. +
untilStepLike `until`, `untilStep` generates a list of counting integers. But it allows you to define a start, stop, and step: ``` @@ -482,10 +596,12 @@ untilStep 3 6 2 The above will produce `[3 5]` by starting with 3, and adding 2 until it is equal or greater than 6. This is similar to Python's `range` function. +
seqWorks like the bash `seq` command. * 1 parameter (end) - will generate all counting integers between 1 and `end` inclusive. * 2 parameters (start, end) - will generate all counting integers between `start` and `end` inclusive incrementing or decrementing by 1. * 3 parameters (start, step, end) - will generate all counting integers between `start` and `end` inclusive incrementing or decrementing by `step`. @@ -498,15 +614,22 @@ seq 2 -2 => 2 1 0 -1 -2 seq 0 2 10 => 0 2 4 6 8 10 seq 0 -2 -5 => 0 -2 -4 ``` -# Date Functions +
-## now +## Date Functions -The current date/time. Use this in conjunction with other date functions. + + + + + -## ago - -The `ago` function returns duration from time.Now in seconds resolution. + + + + -## date - -The `date` function formats a date. + + + + -## dateInZone - -Same as `date`, but with a timezone. + + + + -## duration - -Formats a given amount of seconds as a `time.Duration`. + + + + -## durationRound - -Rounds a given duration to the most significant unit. Strings and `time.Duration` + + + + -## unixEpoch - -Returns the seconds since the unix epoch for a `time.Time`. + + + + -## dateModify, mustDateModify - -The `dateModify` takes a modification and a date and returns the timestamp. + + + + -## htmlDate - -The `htmlDate` function formats a date for inserting into an HTML date picker + + + + -## htmlDateInZone - -Same as htmlDate, but with a timezone. + + + + -## toDate, mustToDate - -`toDate` converts a string to a date. The first argument is the date layout and + + + + +
nowThe current date/time. Use this in conjunction with other date functions. +
agoThe `ago` function returns duration from time.Now in seconds resolution. ``` ago .CreatedAt @@ -517,10 +640,12 @@ returns in `time.Duration` String() format ``` 2h34m7s ``` +
dateThe `date` function formats a date. Format the date to YEAR-MONTH-DAY: @@ -538,28 +663,34 @@ Mon Jan 2 15:04:05 MST 2006 Write it in the format you want. Above, `2006-01-02` is the same date, but in the format we want. +
dateInZoneSame as `date`, but with a timezone. ``` dateInZone "2006-01-02" (now) "UTC" ``` +
durationFormats a given amount of seconds as a `time.Duration`. This returns 1m35s ``` duration "95" ``` +
durationRoundRounds a given duration to the most significant unit. Strings and `time.Duration` gets parsed as a duration, while a `time.Time` is calculated as the duration since. This return 2h @@ -573,18 +704,22 @@ This returns 3mo ``` durationRound "2400h10m5s" ``` +
unixEpochReturns the seconds since the unix epoch for a `time.Time`. ``` now | unixEpoch ``` +
dateModify, mustDateModifyThe `dateModify` takes a modification and a date and returns the timestamp. Subtract an hour and thirty minutes from the current time: @@ -593,27 +728,33 @@ now | date_modify "-1.5h" ``` If the modification format is wrong `dateModify` will return the date unmodified. `mustDateModify` will return an error otherwise. +
htmlDateThe `htmlDate` function formats a date for inserting into an HTML date picker input field. ``` now | htmlDate ``` +
htmlDateInZoneSame as htmlDate, but with a timezone. ``` htmlDateInZone (now) "UTC" ``` +
toDate, mustToDate`toDate` converts a string to a date. The first argument is the date layout and the second the date string. If the string can't be convert it returns the zero value. `mustToDate` will return an error in case the string cannot be converted. @@ -624,13 +765,18 @@ This is useful when you want to convert a string date to another format ``` toDate "2006-01-02" "2017-12-31" | date "02/01/2006" ``` -# Default Functions +
+ +## Default Functions Sprig provides tools for setting default values for templates. -## default - -To set a simple default value, use `default`: + + + + + -## empty - -The `empty` function returns `true` if the given value is considered empty, and + + + + -## coalesce - -The `coalesce` function takes a list of values and returns the first non-empty + + + + -## all - -The `all` function takes a list of values and returns true if all values are non-empty. + + + + -## any - -The `any` function takes a list of values and returns true if any value is non-empty. + + + + -## fromJSON, mustFromJSON - -`fromJSON` decodes a JSON document into a structure. If the input cannot be decoded as JSON the function will return an empty string. + + + + -## toJSON, mustToJSON - -The `toJSON` function encodes an item into a JSON string. If the item cannot be converted to JSON the function will return an empty string. + + + + -## toPrettyJSON, mustToPrettyJSON - -The `toPrettyJSON` function encodes an item into a pretty (indented) JSON string. + + + + -## toRawJSON, mustToRawJSON - -The `toRawJSON` function encodes an item into JSON string with HTML characters unescaped. + + + + -## ternary - -The `ternary` function takes two values, and a test value. If the test value is + + + + +
defaultTo set a simple default value, use `default`: ``` default "foo" .Bar @@ -650,10 +796,12 @@ The definition of "empty" depends on type: For structs, there is no definition of empty, so a struct will never return the default. +
emptyThe `empty` function returns `true` if the given value is considered empty, and `false` otherwise. The empty values are listed in the `default` section. ``` @@ -662,10 +810,12 @@ empty .Foo Note that in Go template conditionals, emptiness is calculated for you. Thus, you rarely need `if empty .Foo`. Instead, just use `if .Foo`. +
coalesceThe `coalesce` function takes a list of values and returns the first non-empty one. ``` @@ -683,10 +833,12 @@ coalesce .name .parent.name "Matt" The above will first check to see if `.name` is empty. If it is not, it will return that value. If it _is_ empty, `coalesce` will evaluate `.parent.name` for emptiness. Finally, if both `.name` and `.parent.name` are empty, it will return `Matt`. +
allThe `all` function takes a list of values and returns true if all values are non-empty. ``` all 0 1 2 @@ -701,10 +853,12 @@ all (eq .Request.TLS.Version 0x0304) (.Request.ProtoAtLeast 2 0) (eq .Request.Me ``` The above will check http.Request is POST with tls 1.3 and http/2. +
anyThe `any` function takes a list of values and returns true if any value is non-empty. ``` any 0 1 2 @@ -719,19 +873,23 @@ any (eq .Request.Method "GET") (eq .Request.Method "POST") (eq .Request.Method " ``` The above will check http.Request method is one of GET/POST/OPTIONS. +
fromJSON, mustFromJSON`fromJSON` decodes a JSON document into a structure. If the input cannot be decoded as JSON the function will return an empty string. `mustFromJSON` will return an error in case the JSON is invalid. ``` fromJSON "{\"foo\": 55}" ``` +
toJSON, mustToJSONThe `toJSON` function encodes an item into a JSON string. If the item cannot be converted to JSON the function will return an empty string. `mustToJSON` will return an error in case the item cannot be encoded in JSON. ``` @@ -739,30 +897,36 @@ toJSON .Item ``` The above returns JSON string representation of `.Item`. +
toPrettyJSON, mustToPrettyJSONThe `toPrettyJSON` function encodes an item into a pretty (indented) JSON string. ``` toPrettyJSON .Item ``` The above returns indented JSON string representation of `.Item`. +
toRawJSON, mustToRawJSONThe `toRawJSON` function encodes an item into JSON string with HTML characters unescaped. ``` toRawJSON .Item ``` The above returns unescaped JSON string representation of `.Item`. +
ternaryThe `ternary` function takes two values, and a test value. If the test value is true, the first value will be returned. If the test value is empty, the second value will be returned. This is similar to the c ternary operator. @@ -793,13 +957,29 @@ false | ternary "foo" "bar" ``` The above returns `"bar"`. -# Encoding Functions +
+ +## Encoding Functions Sprig has the following encoding and decoding functions: -- `b64enc`/`b64dec`: Encode or decode with Base64 -- `b32enc`/`b32dec`: Encode or decode with Base32 -# Lists and List Functions + + + + + + + + + + +
b64enc/b64decEncode or decode with Base64 +
b32enc/b32decEncode or decode with Base32 +
+ +## Lists and List Functions Sprig provides a simple `list` type that can contain arbitrary sequential lists of data. This is similar to arrays or slices, but lists are designed to be used @@ -813,45 +993,54 @@ $myList := list 1 2 3 4 5 The above creates a list of `[1 2 3 4 5]`. -## first, mustFirst - -To get the head item on a list, use `first`. + + + + + -## rest, mustRest - -To get the tail of the list (everything but the first item), use `rest`. + + + + -## last, mustLast - -To get the last item on a list, use `last`: + + + + -## initial, mustInitial - -This compliments `last` by returning all _but_ the last element. + + + + -## append, mustAppend - -Append a new item to an existing list, creating a new list. + + + + -## prepend, mustPrepend - -Push an element onto the front of a list, creating a new list. + + + + -## concat - -Concatenate arbitrary number of lists into one. + + + + -## reverse, mustReverse - -Produce a new list with the reversed elements of the given list. + + + + -## uniq, mustUniq - -Generate a list with all of the duplicates removed. + + + + -## without, mustWithout - -The `without` function filters items out of a list. + + + + -## has, mustHas - -Test to see if a list has a particular element. + + + + -## compact, mustCompact - -Accepts a list and removes entries with empty values. + + + + -## slice, mustSlice - -To get partial elements of a list, use `slice list [n] [m]`. It is + + + + -## chunk - -To split a list into chunks of given size, use `chunk size list`. This is useful for pagination. + + + + +
first, mustFirstTo get the head item on a list, use `first`. `first $myList` returns `1` `first` panics if there is a problem while `mustFirst` returns an error to the template engine if there is a problem. +
rest, mustRestTo get the tail of the list (everything but the first item), use `rest`. `rest $myList` returns `[2 3 4 5]` `rest` panics if there is a problem while `mustRest` returns an error to the template engine if there is a problem. +
last, mustLastTo get the last item on a list, use `last`: `last $myList` returns `5`. This is roughly analogous to reversing a list and then calling `first`. `last` panics if there is a problem while `mustLast` returns an error to the template engine if there is a problem. +
initial, mustInitialThis compliments `last` by returning all _but_ the last element. `initial $myList` returns `[1 2 3 4]`. `initial` panics if there is a problem while `mustInitial` returns an error to the template engine if there is a problem. +
append, mustAppendAppend a new item to an existing list, creating a new list. ``` $new = append $myList 6 @@ -861,10 +1050,12 @@ The above would set `$new` to `[1 2 3 4 5 6]`. `$myList` would remain unaltered. `append` panics if there is a problem while `mustAppend` returns an error to the template engine if there is a problem. +
prepend, mustPrependPush an element onto the front of a list, creating a new list. ``` prepend $myList 0 @@ -874,20 +1065,24 @@ The above would produce `[0 1 2 3 4 5]`. `$myList` would remain unaltered. `prepend` panics if there is a problem while `mustPrepend` returns an error to the template engine if there is a problem. +
concatConcatenate arbitrary number of lists into one. ``` concat $myList ( list 6 7 ) ( list 8 ) ``` The above would produce `[1 2 3 4 5 6 7 8]`. `$myList` would remain unaltered. +
reverse, mustReverseProduce a new list with the reversed elements of the given list. ``` reverse $myList @@ -897,10 +1092,12 @@ The above would generate the list `[5 4 3 2 1]`. `reverse` panics if there is a problem while `mustReverse` returns an error to the template engine if there is a problem. +
uniq, mustUniqGenerate a list with all of the duplicates removed. ``` list 1 1 1 2 | uniq @@ -910,10 +1107,12 @@ The above would produce `[1 2]` `uniq` panics if there is a problem while `mustUniq` returns an error to the template engine if there is a problem. +
without, mustWithoutThe `without` function filters items out of a list. ``` without $myList 3 @@ -931,10 +1130,12 @@ That would produce `[2 4]` `without` panics if there is a problem while `mustWithout` returns an error to the template engine if there is a problem. +
has, mustHasTest to see if a list has a particular element. ``` has 4 $myList @@ -944,10 +1145,12 @@ The above would return `true`, while `has "hello" $myList` would return false. `has` panics if there is a problem while `mustHas` returns an error to the template engine if there is a problem. +
compact, mustCompactAccepts a list and removes entries with empty values. ``` $list := list 1 "a" "foo" "" @@ -958,10 +1161,12 @@ $copy := compact $list `compact` panics if there is a problem and `mustCompact` returns an error to the template engine if there is a problem. +
slice, mustSliceTo get partial elements of a list, use `slice list [n] [m]`. It is equivalent of `list[n:m]`. - `slice $myList` returns `[1 2 3 4 5]`. It is same as `myList[:]`. @@ -971,23 +1176,29 @@ equivalent of `list[n:m]`. `slice` panics if there is a problem while `mustSlice` returns an error to the template engine if there is a problem. +
chunkTo split a list into chunks of given size, use `chunk size list`. This is useful for pagination. ``` chunk 3 (list 1 2 3 4 5 6 7 8) ``` This produces list of lists `[ [ 1 2 3 ] [ 4 5 6 ] [ 7 8 ] ]`. +
-## A Note on List Internals +### A Note on List Internals A list is implemented in Go as a `[]interface{}`. For Go developers embedding Sprig, you may pass `[]interface{}` items into your template context and be able to use all of the `list` functions on those items. -# Dictionaries and Dict Functions + +## Dictionaries and Dict Functions Sprig provides a key/value storage type called a `dict` (short for "dictionary", as in Python). A `dict` is an _unorder_ type. @@ -998,9 +1209,10 @@ type, even another `dict` or `list`. Unlike `list`s, `dict`s are not immutable. The `set` and `unset` functions will modify the contents of a dictionary. -## dict - -Creating dictionaries is done by calling the `dict` function and passing it a + + + + + -## get - -Given a map and a key, get the value from the map. + + + + -## set - -Use `set` to add a new key/value pair to a dictionary. + + + + -## unset - -Given a map and a key, delete the key from the map. + + + + -## hasKey - -The `hasKey` function returns `true` if the given dict contains the given key. + + + + -## pluck - -The `pluck` function makes it possible to give one key and multiple maps, and + + + + -## dig - -The `dig` function traverses a nested set of dicts, selecting keys from a list + + + + -## keys - -The `keys` function will return a `list` of all of the keys in one or more `dict` + + + + -## pick - -The `pick` function selects just the given keys out of a dictionary, creating a + + + + -## omit - -The `omit` function is similar to `pick`, except it returns a new `dict` with all + + + + -## values - -The `values` function is similar to `keys`, except it returns a new `list` with + + + + +
dictCreating dictionaries is done by calling the `dict` function and passing it a list of pairs. The following creates a dictionary with three items: @@ -1008,10 +1220,12 @@ The following creates a dictionary with three items: ``` $myDict := dict "name1" "value1" "name2" "value2" "name3" "value 3" ``` +
getGiven a map and a key, get the value from the map. ``` get $myDict "name1" @@ -1021,10 +1235,12 @@ The above returns `"value1"` Note that if the key is not found, this operation will simply return `""`. No error will be generated. +
setUse `set` to add a new key/value pair to a dictionary. ``` $_ := set $myDict "name4" "value4" @@ -1032,10 +1248,12 @@ $_ := set $myDict "name4" "value4" Note that `set` _returns the dictionary_ (a requirement of Go template functions), so you may need to trap the value as done above with the `$_` assignment. +
unsetGiven a map and a key, delete the key from the map. ``` $_ := unset $myDict "name4" @@ -1045,20 +1263,24 @@ As with `set`, this returns the dictionary. Note that if the key is not found, this operation will simply return. No error will be generated. +
hasKeyThe `hasKey` function returns `true` if the given dict contains the given key. ``` hasKey $myDict "name1" ``` If the key is not found, this returns `false`. +
pluckThe `pluck` function makes it possible to give one key and multiple maps, and get a list of all of the matches: ``` @@ -1076,10 +1298,12 @@ inserted. A common idiom in Sprig templates is to uses `pluck... | first` to get the first matching key out of a collection of dictionaries. +
digThe `dig` function traverses a nested set of dicts, selecting keys from a list of values. It returns a default value if any of the keys are not found at the associated dict. @@ -1107,10 +1331,12 @@ especially since Go's template package's `and` doesn't shortcut. For instance `a.maybeNil.iNeedThis`, and panic if `a` lacks a `maybeNil` field.) `dig` accepts its dict argument last in order to support pipelining. +
keysThe `keys` function will return a `list` of all of the keys in one or more `dict` types. Since a dictionary is _unordered_, the keys will not be in a predictable order. They can be sorted with `sortAlpha`. @@ -1124,10 +1350,12 @@ function along with `sortAlpha` to get a unqiue, sorted list of keys. ``` keys $myDict $myOtherDict | uniq | sortAlpha ``` +
pickThe `pick` function selects just the given keys out of a dictionary, creating a new `dict`. ``` @@ -1135,10 +1363,12 @@ $new := pick $myDict "name1" "name2" ``` The above returns `{name1: value1, name2: value2}` +
omitThe `omit` function is similar to `pick`, except it returns a new `dict` with all the keys that _do not_ match the given keys. ``` @@ -1146,10 +1376,12 @@ $new := omit $myDict "name1" "name3" ``` The above returns `{name2: value2}` +
valuesThe `values` function is similar to `keys`, except it returns a new `list` with all the values of the source `dict` (only one dictionary is supported). ``` @@ -1159,48 +1391,68 @@ $vals := values $myDict The above returns `list["value1", "value2", "value 3"]`. Note that the `values` function gives no guarantees about the result ordering- if you care about this, then use `sortAlpha`. -# Type Conversion Functions +
+ +## Type Conversion Functions The following type conversion functions are provided by Sprig: -- `atoi`: Convert a string to an integer. -- `float64`: Convert to a `float64`. -- `int`: Convert to an `int` at the system's width. -- `int64`: Convert to an `int64`. -- `toDecimal`: Convert a unix octal to a `int64`. -- `toString`: Convert to a string. -- `toStrings`: Convert a list, slice, or array to a list of strings. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
atoiConvert a string to an integer. +
float64Convert to a `float64`. +
intConvert to an `int` at the system's width. +
int64Convert to an `int64`. +
toDecimalConvert a unix octal to a `int64`. +
toStringConvert to a string. +
toStringsConvert a list, slice, or array to a list of strings. +
Only `atoi` requires that the input be a specific type. The others will attempt to convert from any type to the destination type. For example, `int64` can convert floats to ints, and it can also convert strings to ints. -## toStrings - -Given a list-like collection, produce a slice of strings. - -``` -list 1 2 3 | toStrings -``` - -The above converts `1` to `"1"`, `2` to `"2"`, and so on, and then returns -them as a list. - -## toDecimal - -Given a unix octal permission, produce a decimal. - -``` -"0777" | toDecimal -``` - -The above converts `0777` to `511` and returns the value as an int64. -# Path and Filepath Functions +## Path and Filepath Functions While Sprig does not grant access to the filesystem, it does provide functions for working with strings that follow file path conventions. -## Paths +### Paths Paths separated by the slash character (`/`), processed by the `path` package. @@ -1214,46 +1466,58 @@ Examples: [URIs](https://en.wikipedia.org/wiki/Uniform_Resource_Identifier): `https://example.com/some/content/`, `ftp://example.com/file/`. -### base - -Return the last element of a path. + + + + + -### dir - -Return the directory, stripping the last part of the path. So `dir "foo/bar/baz"` + + + + -### clean - -Clean up a path. + + + + -### ext - -Return the file extension. + + + + -### isAbs + + + + +
baseReturn the last element of a path. ``` base "foo/bar/baz" ``` The above prints "baz". +
dirReturn the directory, stripping the last part of the path. So `dir "foo/bar/baz"` returns `foo/bar`. +
cleanClean up a path. ``` clean "foo/bar/../baz" ``` The above resolves the `..` and returns `foo/baz`. +
extReturn the file extension. ``` ext "foo.bar" ``` The above returns `.bar`. +
isAbsTo check whether a path is absolute, use `isAbs`. +
-To check whether a path is absolute, use `isAbs`. - -## Filepaths +### Filepaths Paths separated by the `os.PathSeparator` variable, processed by the `path/filepath` package. @@ -1267,9 +1531,10 @@ Examples: the filesystem path is separated by the backslash character (`\`): `C:\Users\Username\`, `C:\Program Files\Application\`; -### osBase - -Return the last element of a filepath. + + + + + -### osDir - -Return the directory, stripping the last part of the path. So `osDir "/foo/bar/baz"` + + + + -### osClean - -Clean up a path. + + + + -### osExt - -Return the file extension. + + + + -### osIsAbs + + + + +
osBaseReturn the last element of a filepath. ``` osBase "/foo/bar/baz" @@ -1277,16 +1542,20 @@ osBase "C:\\foo\\bar\\baz" ``` The above prints "baz" on Linux and Windows, respectively. +
osDirReturn the directory, stripping the last part of the path. So `osDir "/foo/bar/baz"` returns `/foo/bar` on Linux, and `osDir "C:\\foo\\bar\\baz"` returns `C:\\foo\\bar` on Windows. +
osCleanClean up a path. ``` osClean "/foo/bar/../baz" @@ -1294,10 +1563,12 @@ osClean "C:\\foo\\bar\\..\\baz" ``` The above resolves the `..` and returns `foo/baz` on Linux and `C:\\foo\\baz` on Windows. +
osExtReturn the file extension. ``` osExt "/foo.bar" @@ -1305,31 +1576,50 @@ osExt "C:\\foo.bar" ``` The above returns `.bar` on Linux and Windows, respectively. +
osIsAbsTo check whether a file path is absolute, use `osIsAbs`. +
-To check whether a file path is absolute, use `osIsAbs`. -# Flow Control Functions +## Flow Control Functions -## fail - -Unconditionally returns an empty `string` and an `error` with the specified + + + + + +
failUnconditionally returns an empty `string` and an `error` with the specified text. This is useful in scenarios where other conditionals have determined that template rendering should fail. ``` fail "Please accept the end user license agreement" ``` -# UUID Functions +
+ +## UUID Functions Sprig can generate UUID v4 universally unique IDs. + + + + + +
uuidv4 ``` uuidv4 ``` The above returns a new UUID of the v4 (randomly generated) type. -# Reflection Functions +
+ +## Reflection Functions Sprig provides rudimentary reflection tools. These help advanced template developers understand the underlying Go type information for a particular value. @@ -1340,37 +1630,65 @@ Go has an open _type_ system that allows developers to create their own types. Sprig provides a set of functions for each. -## Kind Functions +### Kind Functions -There are two Kind functions: `kindOf` returns the kind of an object. + + + + + + + + + + +
kindOfReturns the kind of an object. ``` kindOf "hello" ``` -The above would return `string`. For simple tests (like in `if` blocks), the -`kindIs` function will let you verify that a value is a particular kind: +The above would return `string`. +
kindIsFor simple tests (like in `if` blocks), the `kindIs` function will let you verify that a value is a particular kind: ``` kindIs "int" 123 ``` The above will return `true` +
-## Type Functions +### Type Functions Types are slightly harder to work with, so there are three different functions: -- `typeOf` returns the underlying type of a value: `typeOf $foo` -- `typeIs` is like `kindIs`, but for types: `typeIs "*io.Buffer" $myVal` -- `typeIsLike` works as `typeIs`, except that it also dereferences pointers. + + + + + + + + + + + + + + + +
typeOfReturns the underlying type of a value: `typeOf $foo` +
typeIsLike `kindIs`, but for types: `typeIs "*io.Buffer" $myVal` +
typeIsLikeWorks as `typeIs`, except that it also dereferences pointers. +
**Note:** None of these can test whether or not something implements a given interface, since doing so would require compiling the interface in ahead of time. -## deepEqual - -`deepEqual` returns true if two values are ["deeply equal"](https://golang.org/pkg/reflect/#DeepEqual) + + + + + +
deepEqualReturns true if two values are ["deeply equal"](https://golang.org/pkg/reflect/#DeepEqual) Works for non-primitive types as well (compared to the built-in `eq`). @@ -1379,21 +1697,28 @@ deepEqual (list 1 2 3) (list 1 2 3) ``` The above will return `true` -# Cryptographic and Security Functions +
+ +## Cryptographic and Security Functions Sprig provides a couple of advanced cryptographic functions. -## sha1sum - -The `sha1sum` function receives a string, and computes it's SHA1 digest. + + + + + -## sha256sum - -The `sha256sum` function receives a string, and computes it's SHA256 digest. + + + + -## sha512sum - -The `sha512sum` function receives a string, and computes it's SHA512 digest. + + + + -## adler32sum - -The `adler32sum` function receives a string, and computes its Adler-32 checksum. + + + + +
sha1sumThe `sha1sum` function receives a string, and computes it's SHA1 digest. ``` sha1sum "Hello world!" ``` +
sha256sumThe `sha256sum` function receives a string, and computes it's SHA256 digest. ``` sha256sum "Hello world!" @@ -1401,10 +1726,12 @@ sha256sum "Hello world!" The above will compute the SHA 256 sum in an "ASCII armored" format that is safe to print. +
sha512sumThe `sha512sum` function receives a string, and computes it's SHA512 digest. ``` sha512sum "Hello world!" @@ -1412,18 +1739,26 @@ sha512sum "Hello world!" The above will compute the SHA 512 sum in an "ASCII armored" format that is safe to print. +
adler32sumThe `adler32sum` function receives a string, and computes its Adler-32 checksum. ``` adler32sum "Hello world!" ``` -# URL Functions +
-## urlParse -Parses string for URL and produces dict with URL parts +## URL Functions + + + + + + -## urlJoin -Joins map (produced by `urlParse`) to produce URL string + + + + +
urlParseParses string for URL and produces dict with URL parts ``` urlParse "http://admin:secret@server.com:8080/api?list=false#anchor" @@ -1441,9 +1776,12 @@ userinfo: 'admin:secret' ``` For more info, check https://golang.org/pkg/net/url/#URL +
urlJoinJoins map (produced by `urlParse`) to produce URL string ``` urlJoin (dict "fragment" "fragment" "host" "host:80" "path" "/path" "query" "query" "scheme" "http") @@ -1453,3 +1791,6 @@ The above returns the following string: ``` proto://host:80/path?query#fragment ``` +
diff --git a/server/server.go b/server/server.go index c6991ba8..7bad3fde 100644 --- a/server/server.go +++ b/server/server.go @@ -1183,7 +1183,7 @@ func (s *Server) replaceTemplate(tpl string, source string) (string, error) { if err := json.Unmarshal([]byte(source), &data); err != nil { return "", errHTTPBadRequestTemplateMessageNotJSON } - t, err := template.New("").Funcs(sprig.FuncMap()).Parse(tpl) + t, err := template.New("").Funcs(sprig.TxtFuncMap()).Parse(tpl) if err != nil { return "", errHTTPBadRequestTemplateInvalid.Wrap("%s", err.Error()) } @@ -2111,32 +2111,3 @@ func (s *Server) updateAndWriteStats(messagesCount int64) { } }() } - -func loadTemplatesFromDir(dir string) (map[string]*template.Template, error) { - templates := make(map[string]*template.Template) - entries, err := os.ReadDir(dir) - if err != nil { - return nil, err - } - for _, entry := range entries { - if entry.IsDir() { - continue - } - name := entry.Name() - if !strings.HasSuffix(name, ".tmpl") { - continue - } - path := filepath.Join(dir, name) - content, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("failed to read template %s: %w", name, err) - } - tmpl, err := template.New(name).Funcs(sprig.FuncMap()).Parse(string(content)) - if err != nil { - return nil, fmt.Errorf("failed to parse template %s: %w", name, err) - } - base := strings.TrimSuffix(name, ".tmpl") - templates[base] = tmpl - } - return templates, nil -} diff --git a/server/templates/github.yml b/server/templates/github.yml index 92f3ab13..5d1b0b46 100644 --- a/server/templates/github.yml +++ b/server/templates/github.yml @@ -1,31 +1,56 @@ title: | - {{- if .pull_request }} - Pull request {{ .action }}: #{{ .pull_request.number }} {{ .pull_request.title }} - {{- else if and .starred_at (eq .action "created")}} - ⭐ {{ .sender.login }} starred {{ .repository.full_name }} + {{- if and .starred_at (eq .action "created")}} + ⭐ {{ .sender.login }} starred {{ .repository.name }} + + {{- else if and .repository (eq .action "started")}} + 👀 {{ .sender.login }} started watching {{ .repository.name }} + {{- else if and .comment (eq .action "created") }} - 💬 New comment on issue #{{ .issue.number }} — {{ .issue.title }} + 💬 New comment on #{{ .issue.number }}: {{ .issue.title }} + + {{- else if .pull_request }} + 🔀 Pull request {{ .action }}: #{{ .pull_request.number }} {{ .pull_request.title }} + + {{- else if .issue }} + 🐛 Issue {{ .action }}: #{{ .issue.number }} {{ .issue.title }} + {{- else }} - Unsupported GitHub event type or action. + {{ fail "Unsupported GitHub event type or action." }} {{- end }} message: | - {{- if .pull_request }} - Repository: {{ .repository.full_name }}, branch {{ .pull_request.head.ref }} → {{ .pull_request.base.ref }} - Created by: {{ .pull_request.user.login }} - Link: {{ .pull_request.html_url }} - {{ if .pull_request.body }}Description: - {{ .pull_request.body }}{{ end }} - {{- else if and .starred_at (eq .action "created")}} - ⭐ {{ .sender.login }} starred {{ .repository.full_name }} - 📦 {{ .repository.description | default "(no description)" }} - 🔗 {{ .repository.html_url }} - 📅 {{ .starred_at }} + {{ if and .starred_at (eq .action "created")}} + Stargazer: {{ .sender.html_url }} + Repository: {{ .repository.html_url }} + + {{- else if and .repository (eq .action "started")}} + Watcher: {{ .sender.html_url }} + Repository: {{ .repository.html_url }} + {{- else if and .comment (eq .action "created") }} - 💬 New comment on issue #{{ .issue.number }} — {{ .issue.title }} - 📦 {{ .repository.full_name }} - 👤 {{ .comment.user.login }} - 🔗 {{ .comment.html_url }} - 📝 {{ .comment.body | default "(no comment body)" }} + Commenter: {{ .comment.user.html_url }} + Repository: {{ .repository.html_url }} + Comment link: {{ .comment.html_url }} + {{ if .comment.body }} + Comment: + {{ .comment.body | trunc 2000 }}{{ end }} + + {{- else if .pull_request }} + Branch: {{ .pull_request.head.ref }} → {{ .pull_request.base.ref }} + {{ .action | title }} by: {{ .pull_request.user.html_url }} + Repository: {{ .repository.html_url }} + Pull request: {{ .pull_request.html_url }} + {{ if .pull_request.body }} + Description: + {{ .pull_request.body | trunc 2000 }}{{ end }} + + {{- else if .issue }} + {{ .action | title }} by: {{ .issue.user.html_url }} + Repository: {{ .repository.html_url }} + Issue link: {{ .issue.html_url }} + {{ if .issue.body }} + Description: + {{ .issue.body | trunc 2000 }}{{ end }} + {{- else }} {{ fail "Unsupported GitHub event type or action." }} {{- end }} diff --git a/server/testdata/webhook_github_issue_opened.json b/server/testdata/webhook_github_issue_opened.json new file mode 100644 index 00000000..1b3e74c0 --- /dev/null +++ b/server/testdata/webhook_github_issue_opened.json @@ -0,0 +1,216 @@ +{ + "action": "opened", + "issue": { + "url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391", + "repository_url": "https://api.github.com/repos/binwiederhier/ntfy", + "labels_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391/labels{/name}", + "comments_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391/comments", + "events_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391/events", + "html_url": "https://github.com/binwiederhier/ntfy/issues/1391", + "id": 3236389051, + "node_id": "I_kwDOGRBhi87A52C7", + "number": 1391, + "title": "http 500 error (ntfy error 50001)", + "user": { + "login": "TheUser-dev", + "id": 213207407, + "node_id": "U_kgDODLVJbw", + "avatar_url": "https://avatars.githubusercontent.com/u/213207407?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/TheUser-dev", + "html_url": "https://github.com/TheUser-dev", + "followers_url": "https://api.github.com/users/TheUser-dev/followers", + "following_url": "https://api.github.com/users/TheUser-dev/following{/other_user}", + "gists_url": "https://api.github.com/users/TheUser-dev/gists{/gist_id}", + "starred_url": "https://api.github.com/users/TheUser-dev/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/TheUser-dev/subscriptions", + "organizations_url": "https://api.github.com/users/TheUser-dev/orgs", + "repos_url": "https://api.github.com/users/TheUser-dev/repos", + "events_url": "https://api.github.com/users/TheUser-dev/events{/privacy}", + "received_events_url": "https://api.github.com/users/TheUser-dev/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [ + { + "id": 3480884102, + "node_id": "LA_kwDOGRBhi87PehOG", + "url": "https://api.github.com/repos/binwiederhier/ntfy/labels/%F0%9F%AA%B2%20bug", + "name": "🪲 bug", + "color": "d73a4a", + "default": false, + "description": "Something isn't working" + } + ], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [ + ], + "milestone": null, + "comments": 0, + "created_at": "2025-07-16T15:20:56Z", + "updated_at": "2025-07-16T15:20:56Z", + "closed_at": null, + "author_association": "NONE", + "active_lock_reason": null, + "sub_issues_summary": { + "total": 0, + "completed": 0, + "percent_completed": 0 + }, + "body": ":lady_beetle: **Describe the bug**\nWhen sending a notification (especially when it happens with multiple requests) this error occurs\n\n:computer: **Components impacted**\nntfy server 2.13.0 in docker, debian 12 arm64\n\n:bulb: **Screenshots and/or logs**\n```\nclosed with HTTP 500 (ntfy error 50001) (error=database table is locked, http_method=POST, http_path=/_matrix/push/v1/notify, tag=http, visitor_auth_limiter_limit=0.016666666666666666, visitor_auth_limiter_tokens=30, visitor_id=ip:, visitor_ip=, visitor_messages=448, visitor_messages_limit=17280, visitor_messages_remaining=16832, visitor_request_limiter_limit=0.2, visitor_request_limiter_tokens=57.049697891799994, visitor_seen=2025-07-16T15:06:35.429Z)\n```\n\n:crystal_ball: **Additional context**\nLooks like this has already been fixed by #498, regression?\n", + "reactions": { + "url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/1391/timeline", + "performed_via_github_app": null, + "state_reason": null + }, + "repository": { + "id": 420503947, + "node_id": "R_kgDOGRBhiw", + "name": "ntfy", + "full_name": "binwiederhier/ntfy", + "private": false, + "owner": { + "login": "binwiederhier", + "id": 664597, + "node_id": "MDQ6VXNlcjY2NDU5Nw==", + "avatar_url": "https://avatars.githubusercontent.com/u/664597?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/binwiederhier", + "html_url": "https://github.com/binwiederhier", + "followers_url": "https://api.github.com/users/binwiederhier/followers", + "following_url": "https://api.github.com/users/binwiederhier/following{/other_user}", + "gists_url": "https://api.github.com/users/binwiederhier/gists{/gist_id}", + "starred_url": "https://api.github.com/users/binwiederhier/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/binwiederhier/subscriptions", + "organizations_url": "https://api.github.com/users/binwiederhier/orgs", + "repos_url": "https://api.github.com/users/binwiederhier/repos", + "events_url": "https://api.github.com/users/binwiederhier/events{/privacy}", + "received_events_url": "https://api.github.com/users/binwiederhier/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/binwiederhier/ntfy", + "description": "Send push notifications to your phone or desktop using PUT/POST", + "fork": false, + "url": "https://api.github.com/repos/binwiederhier/ntfy", + "forks_url": "https://api.github.com/repos/binwiederhier/ntfy/forks", + "keys_url": "https://api.github.com/repos/binwiederhier/ntfy/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/binwiederhier/ntfy/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/binwiederhier/ntfy/teams", + "hooks_url": "https://api.github.com/repos/binwiederhier/ntfy/hooks", + "issue_events_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/events{/number}", + "events_url": "https://api.github.com/repos/binwiederhier/ntfy/events", + "assignees_url": "https://api.github.com/repos/binwiederhier/ntfy/assignees{/user}", + "branches_url": "https://api.github.com/repos/binwiederhier/ntfy/branches{/branch}", + "tags_url": "https://api.github.com/repos/binwiederhier/ntfy/tags", + "blobs_url": "https://api.github.com/repos/binwiederhier/ntfy/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/binwiederhier/ntfy/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/binwiederhier/ntfy/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/binwiederhier/ntfy/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/binwiederhier/ntfy/statuses/{sha}", + "languages_url": "https://api.github.com/repos/binwiederhier/ntfy/languages", + "stargazers_url": "https://api.github.com/repos/binwiederhier/ntfy/stargazers", + "contributors_url": "https://api.github.com/repos/binwiederhier/ntfy/contributors", + "subscribers_url": "https://api.github.com/repos/binwiederhier/ntfy/subscribers", + "subscription_url": "https://api.github.com/repos/binwiederhier/ntfy/subscription", + "commits_url": "https://api.github.com/repos/binwiederhier/ntfy/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/binwiederhier/ntfy/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/binwiederhier/ntfy/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/binwiederhier/ntfy/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/binwiederhier/ntfy/contents/{+path}", + "compare_url": "https://api.github.com/repos/binwiederhier/ntfy/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/binwiederhier/ntfy/merges", + "archive_url": "https://api.github.com/repos/binwiederhier/ntfy/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/binwiederhier/ntfy/downloads", + "issues_url": "https://api.github.com/repos/binwiederhier/ntfy/issues{/number}", + "pulls_url": "https://api.github.com/repos/binwiederhier/ntfy/pulls{/number}", + "milestones_url": "https://api.github.com/repos/binwiederhier/ntfy/milestones{/number}", + "notifications_url": "https://api.github.com/repos/binwiederhier/ntfy/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/binwiederhier/ntfy/labels{/name}", + "releases_url": "https://api.github.com/repos/binwiederhier/ntfy/releases{/id}", + "deployments_url": "https://api.github.com/repos/binwiederhier/ntfy/deployments", + "created_at": "2021-10-23T19:25:32Z", + "updated_at": "2025-07-16T14:54:16Z", + "pushed_at": "2025-07-16T11:49:26Z", + "git_url": "git://github.com/binwiederhier/ntfy.git", + "ssh_url": "git@github.com:binwiederhier/ntfy.git", + "clone_url": "https://github.com/binwiederhier/ntfy.git", + "svn_url": "https://github.com/binwiederhier/ntfy", + "homepage": "https://ntfy.sh", + "size": 36831, + "stargazers_count": 25112, + "watchers_count": 25112, + "language": "Go", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 984, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 369, + "license": { + "key": "apache-2.0", + "name": "Apache License 2.0", + "spdx_id": "Apache-2.0", + "url": "https://api.github.com/licenses/apache-2.0", + "node_id": "MDc6TGljZW5zZTI=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "curl", + "notifications", + "ntfy", + "ntfysh", + "pubsub", + "push-notifications", + "rest-api" + ], + "visibility": "public", + "forks": 984, + "open_issues": 369, + "watchers": 25112, + "default_branch": "main" + }, + "sender": { + "login": "TheUser-dev", + "id": 213207407, + "node_id": "U_kgDODLVJbw", + "avatar_url": "https://avatars.githubusercontent.com/u/213207407?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/TheUser-dev", + "html_url": "https://github.com/TheUser-dev", + "followers_url": "https://api.github.com/users/TheUser-dev/followers", + "following_url": "https://api.github.com/users/TheUser-dev/following{/other_user}", + "gists_url": "https://api.github.com/users/TheUser-dev/gists{/gist_id}", + "starred_url": "https://api.github.com/users/TheUser-dev/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/TheUser-dev/subscriptions", + "organizations_url": "https://api.github.com/users/TheUser-dev/orgs", + "repos_url": "https://api.github.com/users/TheUser-dev/repos", + "events_url": "https://api.github.com/users/TheUser-dev/events{/privacy}", + "received_events_url": "https://api.github.com/users/TheUser-dev/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + } +} diff --git a/util/sprig/functions.go b/util/sprig/functions.go index 68ef516d..1cd026c6 100644 --- a/util/sprig/functions.go +++ b/util/sprig/functions.go @@ -2,40 +2,26 @@ package sprig import ( "errors" - "html/template" + "golang.org/x/text/cases" + "golang.org/x/text/language" "math/rand" "path" "path/filepath" "reflect" "strconv" "strings" - ttemplate "text/template" + "text/template" "time" - - "golang.org/x/text/cases" ) -// FuncMap produces the function map. +// TxtFuncMap produces the function map. // // Use this to pass the functions into the template engine: // // tpl := template.New("foo").Funcs(sprig.FuncMap())) -func FuncMap() template.FuncMap { - return HTMLFuncMap() -} - +// // TxtFuncMap returns a 'text/template'.FuncMap -func TxtFuncMap() ttemplate.FuncMap { - return GenericFuncMap() -} - -// HTMLFuncMap returns an 'html/template'.Funcmap -func HTMLFuncMap() template.FuncMap { - return GenericFuncMap() -} - -// GenericFuncMap returns a copy of the basic function map as a map[string]any. -func GenericFuncMap() map[string]any { +func TxtFuncMap() template.FuncMap { gfm := make(map[string]any, len(genericMap)) for k, v := range genericMap { gfm[k] = v @@ -63,11 +49,13 @@ var genericMap = map[string]any{ "unixEpoch": unixEpoch, // Strings - "trunc": trunc, - "trim": strings.TrimSpace, - "upper": strings.ToUpper, - "lower": strings.ToLower, - "title": cases.Title, + "trunc": trunc, + "trim": strings.TrimSpace, + "upper": strings.ToUpper, + "lower": strings.ToLower, + "title": func(s string) string { + return cases.Title(language.English).String(s) + }, "substr": substring, // Switch order so that "foo" | repeat 5 "repeat": func(count int, str string) string { return strings.Repeat(str, count) }, @@ -99,11 +87,6 @@ var genericMap = map[string]any{ "seq": seq, "toDecimal": toDecimal, - //"gt": func(a, b int) bool {return a > b}, - //"gte": func(a, b int) bool {return a >= b}, - //"lt": func(a, b int) bool {return a < b}, - //"lte": func(a, b int) bool {return a <= b}, - // split "/" foo/bar returns map[int]string{0: foo, 1: bar} "split": split, "splitList": func(sep, orig string) []string { return strings.Split(orig, sep) },