Go can't evaluate field when using range to build from template - go

I have Files slice of File structure in my Go program to keep name and size of files. I created template, see below:
type File struct {
FileName string
FileSize int64
}
var Files []File
const tmpl = `
{{range .Files}}
file {{.}}
{{end}}
`
t := template.Must(template.New("html").Parse(tmplhtml))
err = t.Execute(os.Stdout, Files)
if err != nil { panic(err) }
Of course I got panic saying:
can't evaluate field Files in type []main.File
Not sure how to correctly display file names and sizes using range in template.

The initial value of your pipeline (the dot) is the value you pass to Template.Execute() which in your case is Files which is of type []File.
So during your template execution the dot . is []File. This slice has no field or method named Files which is what .Files would refer to in your template.
What you should do is simply use . which refers to your slice:
const tmpl = `
{{range .}}
file {{.}}
{{end}}
`
And that's all. Testing it:
var Files []File = []File{
File{"data.txt", 123},
File{"prog.txt", 5678},
}
t := template.Must(template.New("html").Parse(tmpl))
err := t.Execute(os.Stdout, Files)
Output (try it on the Go Playground):
file {data.txt 123}
file {prog.txt 5678}

Related

Go template post processing: is it possible?

In my template, I use a sub-template that generates a piece of output.
The template output must be shifted though (because the output is in YAML format).
Is there any possibility to post-process template output?
{{ template "subtemplate" | indent 10 }}
This indent 10 is fictional, just to explain what I need.
It is possible (as #icza suggested) to save the output
into a variable and then work with it,
but maybe there is a better, more elegant approach?
{{$var := execTempl "subtemplate"}}
{{$var}}
The closest you can get to {{ template "subtemplate" | indent 10 }} is to define a function that parses and executes the subtemplate and outputs the result as string.
var externalTemplates = map[string]*template.Template{
"subtemplate": template.Must(template.New("subtemplate").Parse(sub_template)),
}
// Executes external template, must be registered with FuncMap in the main template.
func xtemplate(name string) (string, error) {
var b bytes.Buffer
if err := externalTemplates[name].ExecuteTemplate(&b, name, nil); err != nil {
return "", err
}
return b.String(), nil
}
t := template.Must(template.New("t").Funcs(template.FuncMap{
"xtemplate": xtemplate, // register func
}).Parse(main_template))
In the main template you can then use the function like this:
{{ xtemplate "subtemplate" | indent 10 }}
https://play.golang.org/p/brolOLFT4xL

escape template in range

I want to print user list like: <#user1> <#user2>, this is an internal format in my company internal, but the golang template always escape the < to <. My code:
tpl, _ := template.New("text").Parse(`{{range .Users}} <#{{.}}> {{end}}`)
var buffer bytes.Buffer
tpl.Execute(&buffer, struct {
Users []string
}{
Users: []string{"user1", "user2"},
})
fmt.Println(buffer.String())
expect:
<#user1> <#user2>
output:
<#user1> <#user2>
How to fix this?
If you want to do so, use text/template. Here's a part of documentation for better understanding each:
Godoc: html/template:
This package wraps package text/template so you can share its template API to parse and execute HTML templates safely.
tmpl, err := template.New("name").Parse(...)
// Error checking elided
err = tmpl.Execute(out, data)
If successful, tmpl will now be injection-safe. Otherwise, err is an error defined in the docs for ErrorCode.
HTML templates treat data values as plain text which should be encoded so they can be safely embedded in an HTML document. The escaping is contextual, so actions can appear within JavaScript, CSS, and URI contexts.
The security model used by this package assumes that template authors are trusted, while Execute's data parameter is not. More details are provided below.
Example
import "text/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
produces
Hello, <script>alert('you have been pwned')</script>!
but the contextual autoescaping in html/template
import "html/template"
...
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")
produces safe, escaped HTML output
Hello, <script>alert('you have been pwned')</script>!

Yaml file to struct parsing (translation) in Golang

I'm trying to build a translation feature for my webapp. There are multiple packages in my app. Each package(directory) contains a translation folder and yaml files inside. But I have a problem with parsing and assign it to messages.
en.yaml
msgLogin : "You've login successfully"
msgProducts:
0: "You don't have any product."
1: "You have %d product."
2: "You have %d products."
errLogin: "Wrong password or username"
my code:
type TranslationEntry struct {
Key struct {
Value interface{}
}
}
func parseTranslations(dir string) {
files, _ := ioutil.ReadDir(dir)
for _, f := range files {
yamlFile, _ := ioutil.ReadFile(dir + "/" + f.Name())
var data translation
if err := yaml.Unmarshal(yamlFile, &data); err != nil {
return nil, err
}
lang := strings.Split(f.Name(), ".")[0]
switch msg := data.Key.Value.(type) {
case string:
message.SetString(language.Make(lang), cast.ToString(data.Key), cast.ToString(data.Key.Value))
case map[int]string:
message.Set(language.Make(lang), cast.ToString(data.Key),
plural.Selectf(1, "%d",
"=0", cast.ToString(data.Key.Value["0"]),
"=1", cast.ToString(data.Key.Value["1"]),
"=2", cast.ToString(data.Key.Value["2"]),
))
}
translations[lang] = &dictionary{Data: data}
}
}
I'm totally lost about how to design my struct or parse yaml file.
Thank you in advanced
If you're using the YAML library I think you're using (https://godoc.org/gopkg.in/yaml.v2), to make a Golang struct which can use Unmarshal to map from the YAML file in your post you can do this:
type TranslationEntry struct {
MsgLogin string `yaml:"msgLogin"`
MsgProducts map[int]string `yaml:"msgProducts"`
ErrLogin string `yaml:"errLogin"`
}
The things inside the `` after the field declarations are called tags. They're the way field names are usually specified when mapping between some datatype and a Golang struct (in my case usually I map a struct to JSON, but I've also done YAML). If you're using the same YAML parser I mentioned above, this is how it works.
Basically the text inside the double quotes is the YAML key to which your struct field will be mapped. In the above code the only difference between the YAML key name and the struct field name is capitalization, but here is an example using totally different names:
type ExampleStruct struct {
MyAbcField string `yaml:"abc"`
}
This will set the value of MyAbcField to "my data" when using Unmarshal with ExampleStruct and the following YAML:
abc: "my data"
This allows you to design a Golang struct which matches however you decide to structure your YAML.
Here's my above code in Go Playground: https://play.golang.org/p/Q9FvNsw-BOx
Now, if you are unable to fix a structure for your YAML files, you can also parse into nested maps. You can do this by passing a variable of type interface{} (empty interface) to Unmarshal instead of a struct. However, this requires a lot of boilerplate because you will need to do type assertions to access your data. Thus I recommend using a fixed structure instead unless you absolutely can't avoid it.
Here's an example where I parse the YAML you posted and then get the msgLogin field:
var data interface{}
if err := yaml.Unmarshal([]byte(yamlFile), &data); err != nil {
// handle error
}
// a type assertion that data is a map is needed in order to get keys or iterate
topLevel, ok := data.(map[interface{}]interface{})
if !ok {
// handle error
}
fmt.Println(topLevel["msgLogin"])
And here's the Go Playground of my struct example changed to use parsing into a nested map instead: https://play.golang.org/p/ERBjClSazkz

get the value of a go template from inside another template [duplicate]

This question already has an answer here:
Capture or assign golang template output to variable
(1 answer)
Closed 5 years ago.
I have two templates T1 and T2. I want to get the output of T1 and do a some extra processing on it inside T2. My question is:
how do I store the output of T1 in a variable inside T2? Is this even possible?
Here's some pseudo-template:
{{define "T1"}}
{{ printf "%s-%s" complex stuff }}
{{end}}
{{define "T2"}}
{{ $some_var := output_from_template "T1"}} <<<<<<<<<<<
{{ etc }}
{{end}}
There is no builtin support for storing the result of a template in a template variable, only for the inclusion of the result.
But you can register custom functions with any complex functionality you want. You may register a GetOutput function which would execute a template identified by its name, and it could return the result as a string, which you can store in a template variable.
Example doing this:
func main() {
t := template.New("")
t = template.Must(t.Funcs(template.FuncMap{
"GetOutput": func(name string) (string, error) {
buf := &bytes.Buffer{}
err := t.ExecuteTemplate(buf, name, nil)
return buf.String(), err
},
}).Parse(src))
if err := t.ExecuteTemplate(os.Stdout, "T2", nil); err != nil {
panic(err)
}
}
const src = `
{{define "T1"}}{{ printf "%s-%s" "complex" "stuff" }}{{end}}
{{define "T2"}}
{{ $t1Out := (GetOutput "T1")}}
{{ printf "%s-%s" "even-more" $t1Out }}
{{end}}`
Output will be (try it on the Go Playground):
even-more-complex-stuff
The "T1" template simply outputs "complex-stuff", and the "T2" template gets the output of "T1", and concatenates the static text "even-more-" and the result of "T1".
The registered GetOutput function gets the name of a template to execute, executes it by directing its output to a local buffer, and returns the content of the buffer (along with the optional error of its execution).
Edit: I've found an exact duplicate: Capture or assign golang template output to variable

Go template and function

In my go code I often use if like this
if user && user.Registered { }
equivalent code in go templates would be
{{ if and .User .User.Registered }} {{ end }}
Unfortunately code in the template fails, if .User is nil :/
Is it possible to achieve the same thing in go templates?
The template and function does not do short circuit evaluation like the Go && operator.
The arguments to the and function are evaluated before the function is called. The expression .User.Registered is always evaluated, even if .User is nil.
The fix is to use nested if:
{{if .User}}{{if .User.Registered}} {{end}}{{end}}
You can avoid the nested if or with by using a template function:
func isRegistered(u *user) bool {
return u != nil && u.Registered
}
const tmpl = `{{if isRegistered .User}}registered{{else}}not registered{{end}}`
t := template.Must(template.New("").Funcs(template.FuncMap{"isRegistered": isRegistered}).Parse(tmpl))
playground example
Another option is to use the {{with}} action instead of the and template function.
Quoting from package doc of text/template:
{{with pipeline}} T1 {{end}}
If the value of the pipeline is empty, no output is generated;
otherwise, dot is set to the value of the pipeline and T1 is
executed.
Using {{with}} often results in cleaner and shorter code, as inside the {{with}} the dot . is already set to the non-empty "wrapper", the .User in our case; moreover you don't have to worry about how and if the arguments of the and template function are evaluated.
Your template rewritten:
{{with .User -}}
{{if .Registered}}REGISTERED{{end}}
{{- end}}
Testing it without and with a user:
t := template.Must(template.New("").Parse(tmpl))
fmt.Println("No user:")
if err := t.Execute(os.Stdout, nil); err != nil {
panic(err)
}
u := struct{ Registered bool }{true}
fmt.Printf("User: %+v\n", u)
if err := t.Execute(os.Stdout, map[string]interface{}{"User": u}); err != nil {
panic(err)
}
Output (try it on the Go Playground):
No user:
User: {Registered:true}
REGISTERED

Resources