The eq function allows for comparing if two strings are equal
{{if eq .Name "MyName"}}
Is there a way to test if a string ends in (or contains) another string?
Use a function map containing the relevant string functions.
funcs := map[string]any{
"contains": strings.Contains,
"hasPrefix": strings.HasPrefix,
"hasSuffix": strings.HasSuffix}
tmpl := `{{if hasSuffix . ".txt"}}yes!{{end}}`
t := template.Must(template.New("").Funcs(funcs).Parse(tmpl))
t.Execute(os.Stdout, "example.txt") // writes yes! to standard out
Run the example on the playground.
Some applications that use Go templates as a feature (Hugo and Helm are examples) provide these functions by default.
(h/t to mkopriva).
Related
I'm wondering if it's possible to use a custom function as a template block with Golang templates. The code below shows an example.
{{ custom_func . }}
This is content that "custom_func" should do something with.
{{ end }}
Use case is a bit peculiar and non-standard. Basically I want the ability for the template author to pass in large block of text where newlines etc. is respected and for that entire block of text to be passed to the function. I could have done something like:
{{ custom_func "This is a lot of text\n with many lines etc." }}
But this is not very user friendly to the template author. The end goal is for them to write something like this:
Author is writing something normal...
{{ note }}
But would like to wrap this content as a "note".
Which when passed to the "note" function, will wrap the content with appropriate divs etc.
{{ end }}
Basically I'm trying an experiment to see if I can achieve "markdown/reStructuredText"-like content with pure go templates. It's mostly an experiment for now.
Eventually I'll probably need to write a proper PEG parser for this, but I want to see if this is possible first.
String arguments to functions may be wrapped both in double quotes " or in backticks `.
String literals wrapped in backticks in templates are called raw string constants, and they work like raw string literals in Go source: may include newlines (and cannot contain escape sequences).
So it's possible what you want if you use backticks for the argument.
For example, a.tmpl:
START
{{ note `a
b\t
c
d`}}
END
App to load and execute the template:
t := template.Must(template.New("").Funcs(template.FuncMap{
"note": func(s string) string { return "<note>\n" + s + "\n</note>" },
}).ParseFiles("a.tmpl"))
if err := t.ExecuteTemplate(os.Stdout, "a.tmpl", nil); err != nil {
panic(err)
}
This will output:
START
<note>
a
b\t
c
d
</note>
END
It's a bit tricky if you define the template in your Go source, as if you use backticks for the template text (because you want to write multiple lines), you can't embed backticks in a raw string literal. You have to break the literal, and concatenate the backticks.
Example doing this in a Go source file:
func main() {
t := template.Must(template.New("").Funcs(template.FuncMap{
"note": func(s string) string { return "<note>\n" + s + "\n</note>" },
}).Parse(src))
if err := t.Execute(os.Stdout, nil); err != nil {
panic(err)
}
}
const src = `START
{{ note ` + "`" + `a
b\t
c
d` + "`" + `}}
END
`
This will output the same, try it on the Go Playground.
I read Golang slices gotcha,
and now I ask myself if there are valid usecases for assigning the result of append() to a different variable.
Is there a use-case for using this golang code?
foo = append(bar, x)
Here's something I saw once (from memory). The header slice does not change but is repeatedly appended to.
schemes := []string{
"Basic",
"Bearer",
"Digest",
}
header := []byte("Www-Authenticate:")
for _, scheme := range schemes {
w.Write(append(header, scheme...))
}
Note this also uses a special feature of append that allows a string to be treated as []byte.
As suggested here names of people should be capitalized like John William Smith.
I'm writing a small software in Golang which gets last and first name from user's form inputs.
Until Go 1.18 I was using:
lastname = strings.Title(strings.ToLower(strings.TrimSpace(lastname)))
firstname = strings.Title(strings.ToLower(strings.TrimSpace(firstname)))
It works but now Go 1.18 has deprecated strings.Title().
They suggest to use golang.org/x/text/cases instead.
So I think I should change my code in something like this:
caser := cases.Title(language.Und)
lastname = caser.Title(strings.ToLower(strings.TrimSpace(lastname)))
firstname = caser.Title(strings.ToLower(strings.TrimSpace(firstname)))
It works the same as before.
The difference is for Dutch word like ijsland that should be titled as IJsland and not Ijsland.
The question
In the line caser := cases.Title(language.Und) I'm using Und because I don't know what language Tag to use.
Should I use language.English or language.AmericanEnglish or other?
So far it was like strings.Title() was using Und or English?
As mentioned in documentation strings.Title is deprecated and you should use cases.Title instead.
Deprecated: The rule Title uses for word boundaries does not handle
Unicode punctuation properly. Use golang.org/x/text/cases instead.
Here is an example code of how to use it as from two perspectives:
// Straightforward approach
caser := cases.Title(language.BrazilianPortuguese)
titleStr := caser.String(str)
// Transformer interface aware approach
src := []byte(s)
dest := []byte(s) // dest can also be `dest := src`
caser := cases.Title(language.BrazilianPortuguese)
_, _, err := caser.Transform(dest, src, true)
Make sure to take a look on the transform.Transformer.Transform and cases.Caser in order to understand what each parameter and return values mean, as well as the tool's limitations. For example:
A Caser may be stateful and should therefore not be shared between
goroutines.
Regarding what language to use, you should be aware of their difference in the results, besides that, you should be fine with any choice. Here is a copy from 煎鱼's summary on the differences that cleared it for me:
Go Playground: https://go.dev/play/p/xp59r1BkC9L
func main() {
src := []string{
"hello world!",
"i with dot",
"'n ijsberg",
"here comes O'Brian",
}
for _, c := range []cases.Caser{
cases.Lower(language.Und),
cases.Upper(language.Turkish),
cases.Title(language.Dutch),
cases.Title(language.Und, cases.NoLower),
} {
fmt.Println()
for _, s := range src {
fmt.Println(c.String(s))
}
}
}
With the following output
hello world!
i with dot
'n ijsberg
here comes o'brian
HELLO WORLD!
İ WİTH DOT
'N İJSBERG
HERE COMES O'BRİAN
Hello World!
I With Dot
'n IJsberg
Here Comes O'brian
Hello World!
I With Dot
'N Ijsberg
Here Comes O'Brian
So far it was like strings.Title() was using Und or English?
strings.Title() works based on ASCII, where cases.Title() works based on Unicode, there is no way to get the exact same behavior.
Should I use language.English or language.AmericanEnglish or other?
language.English, language.AmericanEnglish and language.Und all seem to have the same Title rules. Using any of them should get you the closest to the original strings.Title() behavior as you are going to get.
The whole point of using this package with Unicode support is that it is objectively more correct. So pick a tag appropriate for your users.
strings.Title(str) was deprecated, should change to cases.Title(language.Und, cases.NoLower).String(str)
package main
import (
"fmt"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
func main() {
fmt.Println(strings.Title("abcABC")) // AbcABC
fmt.Println(cases.Title(language.Und, cases.NoLower).String("abcABC")) // AbcABC
}
Playground : https://go.dev/play/p/i0Eqh3QfxTx
Here is a straightforward example of how to capitalize the initial letter of each string value in the variable using the golang.org/x/text package.
package main
import (
"fmt"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
func main() {
sampleStr := "with value lower, all the letters are lowercase. this is good for poetry perhaps"
caser := cases.Title(language.English)
fmt.Println(caser.String(sampleStr))
}
Output : With Value Lower, All The Letters Are Lowercase. This Is Good For Poetry Perhaps
Playground Example: https://go.dev/play/p/_J8nGVuhYC9
I've a terratest where I get an output from terraform like so s := "[a b]". The terraform output's value = toset([resource.name]), it's a set of strings.
Apparently fmt.Printf("%T", s) returns string. I need to iterate to perform further validation.
I tried the below approach but errors!
var v interface{}
if err := json.Unmarshal([]byte(s), &v); err != nil {
fmt.Println(err)
}
My current implementation to convert to a slice is:
s := "[a b]"
s1 := strings.Fields(strings.Trim(s, "[]"))
for _, v:= range s1 {
fmt.Println("v -> " + v)
}
Looking for suggestions to current approach or alternative ways to convert to arr/slice that I should be considering. Appreciate any inputs. Thanks.
Actually your current implementation seems just fine.
You can't use JSON unmarshaling because JSON strings must be enclosed in double quotes ".
Instead strings.Fields does just that, it splits a string on one or more characters that match unicode.IsSpace, which is \t, \n, \v. \f, \r and .
Moeover this works also if terraform sends an empty set as [], as stated in the documentation:
returning [...] an empty slice if s contains only white space.
...which includes the case of s being empty "" altogether.
In case you need additional control over this, you can use strings.FieldsFunc, which accepts a function of type func(rune) bool so you can determine yourself what constitutes a "space". But since your input string comes from terraform, I guess it's going to be well-behaved enough.
There may be third-party packages that already implement this functionality, but unless your program already imports them, I think the native solution based on the standard lib is always preferrable.
unicode.IsSpace actually includes also the higher runes 0x85 and 0xA0, in which case strings.Fields calls FieldsFunc(s, unicode.IsSpace)
package main
import (
"fmt"
"strings"
)
func main() {
src := "[a b]"
dst := strings.Split(src[1:len(src)-1], " ")
fmt.Println(dst)
}
https://play.golang.org/p/KVY4r_8RWv6
I have a map of values that looks like this:
vals := map[string]interface{}{"foo": 1, "bar": 2, "baz": 7}
data := map[string]interface{}{"bat": "obj", "values": vals}
What should my template look like to generate the following string (note the correct comma usage)?
SET obj.foo=1, obj.bar=2, obj.baz=7
I started with this as my template:
SET {{range $i, $v := .values}} {{.bat}}.{{$i}}={{$v}},{{end}}
But that just prints out
SET
And even if that did work, the commas would be incorrect. I then tried to use a custom function to format the map, but I couldn't get the template to ever call my function. None of the following seemed to work:
SET {{.MyFunction .values}}
SET {{call .MyFunction .values}}
SET {{call MyFunction .values}}
when MyFunction was defined as:
func MyFunction(data map[string]interface{}) string {
fmt.PrintLn('i was called!')
return "foo"
}
And I'm executing the templates using a helper function that looks like this:
func useTemplate(name string, data interface{}) string {
out := new(bytes.Buffer)
templates[name].Execute(out, data)
return string(out.Bytes())
}
Thanks!
This will get you pretty close:
SET {{range $key, $value := $.values}}{{$.bat}}.{{$key}}={{$value}} {{end}}
rendering as:
SET obj.bar=2 obj.baz=7 obj.foo=1
Unfortunately, I don't think there's any simple way to have the commas added in between the values due to how the range action iterates on maps (there's no numeric index). That said, the template packages were meant to be easily extensible so you can have less logic in your templates and more logic in Go itself, so it's easy enough to code a helper function in Go and make it available to your templates.
If you're happy to go that extra mile, then the template becomes much simpler, and also more efficient. The function can look like this:
func commaJoin(prefix string, m map[string]interface{}) string {
var buf bytes.Buffer
first := true
for k, v := range m {
if !first {
buf.WriteString(", ")
}
first = false
buf.WriteString(prefix)
buf.WriteByte('.')
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(fmt.Sprint(v))
}
return buf.String()
}
and your template would look like:
SET {{$.values | commaJoin $.bat}}
Here is a working example with this logic:
http://play.golang.org/p/5lFUpFCzZm