error: template: "..." is an incomplete or empty template - go

I'm trying to add a FuncMap to my templates, but I'm receiving the following error:
template: "foo" is an incomplete or empty
template
The parsing of templates worked just fine before I used the FuncMap, so I'm not sure why it's throwing an error now.
Here is my code:
funcMap := template.FuncMap{
"IntToUSD": func(num int) string {
return decimal.New(int64(num), 2).String()
},
}
// ...
tmpl, err := template.New(t.file).Funcs(funcMap).ParseFiles(t.files()...)
if err != nil {
// ...
}
t.files() just returns a slice of strings that are file paths.
Anyone know what's up?

Make sure the argument you pass to template.New is the base name of one of the files in the list you pass to ParseFiles.
One option is
files := t.files()
if len(files) > 0 {
name := path.Base(files[0])
tmpl, err := template.New(name).Funcs(funcMap).ParseFiles(files...)
ParseFiles documentation:
Since the templates created by ParseFiles are named by the base names of the argument files, t should usually have the name of one of the (base) names of the files.

I was having the same problem. I realized that
tmpl, err := template.New("").Funcs(funcMap).ParseFiles("fileName")
also works if you use it with
err := tpl.ExecuteTemplate(wr, "fileName", data)
If I use
err := tpl.Execute(wr, data)
then I should specify the template name in New():
tmpl, err := template.New("fileName").Funcs(funcMap).ParseFiles("fileName")

You can also use Template.Must method,
templ = template.Must(template.New("fileName").Funcs(fm).ParseFiles("fileName"))

Related

Go template missingkey option always return error

So the question seems to have been asked a couple of times before, but none of the previous answers worked for me, I go from errors to errors to no results.
So as I am most certainly missing something that I don't see I would like for some help:
res, err := os.Create(strings.Replace(f, ".tmpl", "", -1))
if err != nil {
log.Fatal(err)
}
t, err := template.ParseFiles(f)
if err != nil {
log.Fatal(err)
}
removes = append(removes, res.Name())
config := make(map[string]string)
for _, v := range vars {
config[v] = os.Getenv(v)
}
err = t.Execute(res, config)
if err != nil {
log.Fatal(err)
}
res.Close()
So to explain what I am doing, I'm passing a string to a file (path/file) that has a yaml.tmpl extension. The result file should be yaml so I remove the last part to generate the result file name.
I then parse the file with go template and then I execute with a configmap that I generate.
This is working fine like this but I would like to add: .Option("missingkey=error") to have it generate an error in case I don't give a value from the configmap to a variable in the template.
So I tried to add the options in the template parse file like this:
t, err := template.New("test").Option("missingkey=error").ParseFiles(f)
But I can't use template Exectute and have to use template ExecuteTemplate but with those I get:
template: no template "test" associated with template "test" or template: test: "test" is an incomplete or empty template
In the rare cases I don't get an error, it just ignore the option like if I do this:
err = t.Option("missingkey=error").Execute(res, config)
Does anyone has an idea of what I'm doing wrong?
EDIT
I updated the code with the answer of Cerise Limon and here is the playground: playground
Currently that playground just ignore errors and do the template even if the config passed is empty and there is no or condition in the template.
The ParseFiles method documentation says:
Since the templates created by ParseFiles are named by the base names of the argument files, t should usually have the name of one of the (base) names of the files.
Use filepath.Base to get the base name of the file. Use that name as the name of the template:
t, err := template.New(filepath.Base(f)).Option("missingkey=error").ParseFiles(f)
Run an example on the Playground.

Is this the correct behavior of ast parsing

I am working on learning how to use and how golang's ast library works. I am parsing https://github.com/modern-go/concurrent, avoiding the test files and the go_below_19.go since it causes errors.
My problem is with the parsing of these lines in the file unbounded_executor.go,
var HandlePanic = func(recovered interface{}, funcName string) {
ErrorLogger.Println(fmt.Sprintf("%s panic: %v", funcName, recovered))
ErrorLogger.Println(string(debug.Stack()))
}
The ast.Ident for ErrorLogger in both instances have a nil obj.
But, I believe that it should not be nil and should reference these lines from log.go,
// ErrorLogger is used to print out error, can be set to writer other than stderr
var ErrorLogger = log.New(os.Stderr, "", 0)
Am I wrong, or is there a problem with the parser? I've followed several references on parsing files and reuse a *token.FileSet across each of the files and use ParseComments as the mode.
edit:
There is a large code base surrounding this, so the code demonstrating this will include snippets.
This is performed with the same fset across all non-test go files, without build restrictions that would stop the code from being used with 1.16
parsedFile, parseErr := parser.ParseFile(fset, filePath, nil, parser.ParseComments)
Call ast.NewPackage to resolve identifiers in the AST:
fset := token.NewFileSet()
files := make(map[string]*ast.File)
for _, name := range []string{"unbounded_executor.go", "log.go"} {
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
if err != nil {
log.Fatal(err)
}
files[name] = f
}
ast.NewPackage(fset, files, nil, nil)
ast.Inspect(files["unbounded_executor.go"], func(n ast.Node) bool {
if n, ok := n.(*ast.Ident); ok && n.Name == "ErrorLogger" {
fmt.Println(n.Obj)
}
return true
})
Because a proper importer is not provided and the list of files does not include all files in the package, NewPackage returns unresolved symbol errors.

How to check if a golang template has all the variables available in the data variable?

I am trying to render a string from the Golangs Template and populating template variables with a map.
I need to have a check as to if the all the variables are available in the map.
Expected the below piece of code to throw an error.
templateVariables := map[string]string {}
tmpl := template.New("test")
tmpl, err := tmpl.Parse("Hello {{.Name}}\n")
if err != nil {
log.Fatal("Parse: ", err)
return
}
buf := bytes.NewBufferString("")
err1 := tmpl.Execute(buf,s)
if err1 != nil {
// Expected this to be true
log.Panic(err1)
}
fmt.print(buf.String())
Executing above throws no Error and the value of buf from fmt.print turns out to be:
Hello <no value>
How does one ensure that all the required variables in the map are available, if not throw an error? Is there a way to do this from templates package itself?
How does one ensure that all the required variables in the map are available, if not throw an error?
The option "missingkey=error" should do what you are looking for:
missingkey: Control the behavior during execution if a map is indexed with a key that is not present in the map.
"missingkey=error"
Execution stops immediately with an error.
In your example this option can be added as follows:
tmpl := template.New("test").Option("missingkey=error")
Try it in the playground.

: first path segment in URL cannot contain colon

Here's my code ( part of it ) :
type SitemapIndex struct {
// Locations []Location `xml:"sitemap"`
Locations []string `xml:"sitemap>loc"`
}
~~~ SNIP ~~~
func main(){
var s SitemapIndex
resp, _ := http.Get("https://www.washingtonpost.com/news-sitemaps/index.xml")
bytes, _ := ioutil.ReadAll(resp.Body)
xml.Unmarshal(bytes, &s)
for _, Location := range s.Locations {
fmt.Printf("%s\n", Location)
resp, err := http.Get(Location)
if err != nil {
log.Fatal(err)
} else {
bytes, _ := ioutil.ReadAll(resp.Body)
xml.Unmarshal(bytes, &n)
for idx := range n.Titles {
newsMap[n.Titles[idx]] = NewsMap{n.Keywords[idx], n.Locations[idx]}
}
}
for idx, data := range newsMap {
fmt.Println("\n\n\n", idx)
fmt.Println("\n", data.Keyword)
fmt.Println("\n", data.Location)
}
}
Now, when I run this code I get this output :
https://www.washingtonpost.com/news-sitemaps/politics.xml
2019/01/28 02:37:13 parse
https://www.washingtonpost.com/news-sitemaps/politics.xml
: first path segment in URL cannot contain colon
exit status 1
I read a few posts and did some experiment myself, like I made another file with the following code
package main
import ("fmt"
"net/url")
func main(){
fmt.Println(url.Parse("https://www.washingtonpost.com/news-sitemaps/politics.xml"))
}
And it didn't throw any error, so I understand the error is not with the url .
Now, I just started learning Go using sentdex's tutorials , a few hours ago and so don't have much idea as of now. Here's the video link
Thanks and regards.
Temporarya
The problem here is that Location has whitespace prefix and suffix so string is not valid URL. Unfortunately, error message does not help to see that.
How to detect:
I typically use %q fmt helper that wraps string into parentheses:
fmt.Printf("%q", Location)
Will be printed as "\nhttps://www.washingtonpost.com/news-sitemaps/politics.xml\n"
How to fix:
add this line before using Location in code:
Location = strings.TrimSpace(Location)
Another reason to get this error is when you use an IP address without specifying the protocol before it.
Example for cases you'll get this error:
parsedUrl, err := url.Parse("127.0.0.1:3213")
How to fix it:
parsedUrl, err := url.Parse("http://127.0.0.1:3213")
Poor documentation, unfortunately.

How to convert a type []os.FileInfo into a string?

I'm using this code to dynamically extract template filenames from a directory using this code:
files, _ := ioutil.ReadDir("./views")
htmlFiles := ".html"
for _, f := range files {
if strings.Contains(files, htmlFiles) {
fmt.Println(f.Name())
}
}
and I'm getting this error:
cannot use files (type []os.FileInfo) as type string in argument to strings.Contains
So, how to convert type []os.FileInfo into a string (or how do to this in a simpler way)?
The value returned by ioutil.ReadDir is a []os.FileInfo, which is a slice of interfaces. There's no general way to convert that into a meaningful string. You want to compare the name for each file individually:
files, err := ioutil.ReadDir("./views")
if err != nil {
log.Fatal(err)
}
for _, f := range files {
if strings.HasSuffix(f.Name(), ".html") {
fmt.Println(f.Name())
}
}

Resources