Differing behaviors for ParseFiles functions in html/template - go

I don't understand why the behaviors of func (t *Template) Parsefiles(... differs from func ParseFiles(.... Both functions are from the "html/template" package.
package example
import (
"html/template"
"io/ioutil"
"testing"
)
func MakeTemplate1(path string) *template.Template {
return template.Must(template.ParseFiles(path))
}
func MakeTemplate2(path string) *template.Template {
return template.Must(template.New("test").ParseFiles(path))
}
func TestExecute1(t *testing.T) {
tmpl := MakeTemplate1("template.html")
err := tmpl.Execute(ioutil.Discard, "content")
if err != nil {
t.Error(err)
}
}
func TestExecute2(t *testing.T) {
tmpl := MakeTemplate2("template.html")
err := tmpl.Execute(ioutil.Discard, "content")
if err != nil {
t.Error(err)
}
}
This exits with the error:
--- FAIL: TestExecute2 (0.00 seconds)
parse_test.go:34: html/template:test: "test" is an incomplete or empty template
FAIL
exit status 1
Note that TestExecute1 passed fine so this not a problem with template.html.
What's going on here?
What am I missing in MakeTemplate2?

It's because of the template names. Template objects can hold multiple teplates, each has a name. When using template.New("test"), and then Executing it, it will try to execute a template called "test" inside that template. However, tmpl.ParseFiles stores the template to the name of the file. That explains the error message.
How to fix it:
a) Give the template the correct name:
Use
return template.Must(template.New("template.html").ParseFiles(path))
instead of
return template.Must(template.New("test").ParseFiles(path))
b) Specify, which template you want to execute in you Template object:
Use
err := tmpl.ExecuteTemplate(ioutil.Discard, "template.html", "content")
instead of
err := tmpl.Execute(ioutil.Discard, "content")
Read more about this in http://golang.org/pkg/text/template/

Related

Is there a better way where I can check if a template property was not resolved?

I am trying to build a string using text/template, where the template string could have arbitrary properties that are resolved via a map.
What I am trying to accomplish is identifying where one/any of the template properties is not resolved and return an error.
At the moment, I am using regexp but reaching out to the community of see if there was a better solution.
package main
import (
"bytes"
"fmt"
"regexp"
"text/template"
)
func main() {
data := "teststring/{{.someData}}/{{.notExist}}/{{.another}}"
// the issue here is that data can be arbitrary so i cannot do
// a lot of unknown if statements
t := template.Must(template.New("").Parse(data))
var b bytes.Buffer
fillers := map[string]interface{}{
"someData": "123",
"another": true,
// in this case, notExist is not defined, so the template will
// note resolve it
}
if err := t.Execute(&b, fillers); err != nil {
panic(err)
}
fmt.Println(b.String())
// teststring/123/<no value>/true
// here i am trying to catch if a the template required a value that was not provided
hasResolved := regexp.MustCompile(`<no value>`)
fmt.Println(hasResolved.MatchString(b.String()))
// add notExist to the fillers map
fillers["notExist"] = "testdata"
b.Reset()
if err := t.Execute(&b, fillers); err != nil {
panic(err)
}
fmt.Println(b.String())
fmt.Println(hasResolved.MatchString(b.String()))
// Output:
// teststring/123/<no value>/true
// true
// teststring/123/testdata/true
// false
}
You can let it fail by settings the options on the template:
func (t *Template) Option(opt ...string) *Template
"missingkey=default" or "missingkey=invalid"
The default behavior: Do nothing and continue execution.
If printed, the result of the index operation is the string
"<no value>".
"missingkey=zero"
The operation returns the zero value for the map type's element.
"missingkey=error"
Execution stops immediately with an error.
If you set it to missingkey=error, you get what what want.
t = t.Options("missingkey=error")

Go - How to combine multiple error objects

Say I have the following code:
package lib
import (
"errors"
"strconv"
)
var ErrSomething = errors.New("foobar")
func SomeFunc(str string) (int, error) {
i, err := strconv.Atoi(str)
if err != nil {
// how to combine ErrSomething with err?
return 0, fmt.Errorf("%w: %w", ErrSomething, err)
}
// do other things, potentially return other errors
return i
}
How do I combine the error returned from strconv.Atoi with my named error ErrSomething. The reason for combining is so that users of my SomeFunc() can check what exactly went wrong using my error "constants" while not losing information about the underlying error.
I have read similar questions but the usual answer is to just do: return 0, fmt.Errorf("foobar: %w", err) but this way my users can't check the error using errors.Is(err, ???)
You can achieve the desired behavior by creating an error type that implements the Is and Unwrap methods as follows:
package lib
import (
"fmt"
"strconv"
)
type FoobarError struct {
msg string
original error
}
func (err *FoobarError) Error() string {
return fmt.Sprintf("%s: %s", err.msg, err.original.Error())
}
func (err *FoobarError) Unwrap() error {
return err.original
}
func (err *FoobarError) Is(target error) bool {
_, ok := target.(*FoobarError)
return ok
}
func SomeFunc() error {
// strconv.ErrSyntax is used as a dummy error here for the error
// that might be returned by strconv.Atoi or any other operation.
err := strconv.ErrSyntax
return &FoobarError{"foobar", err}
}
Usage:
package main
import (
"errors"
"fmt"
"strconv"
"lib"
)
func main() {
if err := lib.SomeFunc(); err != nil {
fmt.Println(err) // foobar: invalid syntax
fmt.Println(errors.Is(err, &lib.FoobarError{})) // true
fmt.Println(errors.Is(err, strconv.ErrSyntax)) // true
}
}
You can read more about this approach here.
Bonus
Similar to Go's os.IsExist, you may be interested in adding a helper function to your library that makes it easier for the user to check the error:
package lib
import (
"errors"
// ...
)
// ...
func IsFoobar(err error) bool {
return errors.Is(err, &FoobarError{})
}
Usage:
package main
// ...
func main() {
err := lib.SomeFunc();
if lib.IsFoobar(err) {
// ...
}
}

The type InteractionCallback is not being found, and atom is removing the import statement

I am using the atom IDE, and for some reason whenever I add this to my imports:
"github.com/nlopes/slack"
And save the file, it removes the import. So I'm not sure why but it isn't finding the InteractionCallback type in the library?
I copied this code from the example:
func unmarshalSuggestionCallback(j string) (*InteractionCallback, error) {
callback := &InteractionCallback{}
if err := json.Unmarshal([]byte(j), &callback); err != nil {
return nil, err
}
return callback, nil
}
I am getting this error:
undefined: InteractionCallback
How can I tell if my library I just downloaded has the type defined? Or am I referencing the type incorrectly?
Please use this command in your terminal:
go get -u github.com/nlopes/slack
After that try to run this code:
package main
import (
"encoding/json"
"fmt"
"github.com/nlopes/slack"
)
func unmarshalSuggestionCallback(j string) (*slack.InteractionCallback, error) {
callback := &slack.InteractionCallback{}
if err := json.Unmarshal([]byte(j), &callback); err != nil {
return nil, err
}
return callback, nil
}
func main() {
callback,_:=unmarshalSuggestionCallback(`{"type":"callback"}`)
fmt.Println(callback.Type)
}
Everything should work fine, I have checked in my PC
You need to specify from which package InteractionCallback comes from, in your case its slack package - slack.InteractionCallback

Golang downcasting list of structs

I want to be able to unmarshal yaml files less rigidly. That is, my library has a predefined number of options the yaml file must have. Then, the user should be able to extend this to include any custom options.
Here is what I have
package main
import (
"net/http"
"yamlcms"
"github.com/julienschmidt/httprouter"
)
type Page struct {
*yamlcms.Page
Title string
Date string
}
func getBlogRoutes() {
pages := []*Page{}
yamlcms.ReadDir("html", pages)
}
// This section is a work in progress, I only include it for loose context
func main() {
router := httprouter.New()
//blogRoutes := getBlogRoutes()
//for _, blogRoute := range *blogRoutes {
// router.Handle(blogRoute.Method, blogRoute.Pattern,
// func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {})
//}
http.ListenAndServe(":8080", router)
}
Here is the yamlcms package:
package yamlcms
import (
"io/ioutil"
"os"
"strings"
"gopkg.in/yaml.v2"
)
type Page struct {
Slug string `yaml:"slug"`
File string `yaml:"file"`
}
func (page *Page) ReadFile(file string) (err error) {
fileContents, err := ioutil.ReadFile(file)
if err != nil {
return
}
err = yaml.Unmarshal(fileContents, &page)
return
}
func isYamlFile(fileInfo os.FileInfo) bool {
return !fileInfo.IsDir() && strings.HasSuffix(fileInfo.Name(), ".yaml")
}
func ReadDir(dir string, pages []*Page) (err error) {
filesInfo, err := ioutil.ReadDir(dir)
if err != nil {
return
}
for i, fileInfo := range filesInfo {
if isYamlFile(fileInfo) {
pages[i].ReadFile(fileInfo.Name())
}
}
return
}
There is a compiler issue here:
src/main.go:19: cannot use pages (type []*Page) as type []*yamlcms.Page in argument to yamlcms.ReadDir
My main intent in this question is to learn the idiomatic way of doing this kind of thing in Go. Other 3rd-party solutions may exist but I am not immediately interested in them because I have problems like this frequently in Go having to do with inheritance, etc. So along the lines of what I've presented, how can I best (idiomatically) accomplish what I am going for?
EDIT:
So I've made some changes as suggested. Now I have this:
type FileReader interface {
ReadFile(file string) error
}
func ReadDir(dir string, pages []*FileReader) (err error) {
filesInfo, err := ioutil.ReadDir(dir)
if err != nil {
return
}
for i, fileInfo := range filesInfo {
if isYamlFile(fileInfo) {
(*pages[i]).ReadFile(fileInfo.Name())
}
}
return
}
However, I still get a similar compiler error:
src/main.go:19: cannot use pages (type []*Page) as type []*yamlcms.FileReader in argument to yamlcms.ReadDir
Even though main.Page should be a FileReader because it embeds yamlcms.Page.
EDIT: I forgot that slices of interfaces don't work like that. You'd need to allocate a new slice, convert all pages to FileReaders, call the function, and convert them back.
Another possible solution is refactoring yamlcms.ReadDir to return the contents of the files, so that they could be unmarshaled later:
// In yamlcms.
func ReadYAMLFilesInDir(dir string) ([][]byte, error) { ... }
// In client code.
files := yamlcms.ReadYAMLFilesInDir("dir")
for i := range pages {
if err := yaml.Unmarshal(files[i], &pages[i]); err != nil { return err }
}
The original answer:
There are no such things as inheritance or casting in Go. Prefer composition and interfaces in your designs. In your case, you can redefine your yamlcms.ReadDir to accept an interface, FileReader.
type FileReader interface {
ReadFile(file string) error
}
Both yamlcms.Page and main.Page will implement this, as the latter embeds the former.

Any way to use html.Parse without it adding nodes to make a 'well-formed tree'?

package main
import (
"bytes"
"code.google.com/p/go.net/html"
"fmt"
"log"
"strings"
)
func main() {
s := "Blah. <b>Blah.</b> Blah."
n, err := html.Parse(strings.NewReader(s))
if err != nil {
log.Fatalf("Parse error: %s", err)
}
var buf bytes.Buffer
if err := html.Render(&buf, n); err != nil {
log.Fatalf("Render error: %s", err)
}
fmt.Println(buf.String())
}
Output:
<html><head></head><body>Blah. <b>Blah.</b> Blah.</body></html>
Is there a way to stop html.Parse from making a document out of fragments (ie avoid adding <html>, <body> etc.)? I'm aware of html.ParseFragment but it seems to exhibit the same behaviour.
You can get around it by wrapping the text to be parsed with a parent element such as <span> then doing something like the following:
n = n.FirstChild.LastChild.FirstChild
but that seems, well, kludgy to say the least.
Ideally I'd like to: accept input, manipulate or remove nodes found within it, and write the result back to a string, even if the result is an incomplete document.
You need to provide a context to ParseFragment. The following program prints out the original text:
package main
import (
"bytes"
"code.google.com/p/go.net/html"
"code.google.com/p/go.net/html/atom"
"fmt"
"log"
"strings"
)
func main() {
s := "Blah. <b>Blah.</b> Blah."
n, err := html.ParseFragment(strings.NewReader(s), &html.Node{
Type: html.ElementNode,
Data: "body",
DataAtom: atom.Body,
})
if err != nil {
log.Fatalf("Parse error: %s", err)
}
var buf bytes.Buffer
for _, node := range n {
if err := html.Render(&buf, node); err != nil {
log.Fatalf("Render error: %s", err)
}
}
fmt.Println(buf.String())
}
You want http://godoc.org/code.google.com/p/go.net/html#ParseFragment. Pass in a fake Body element as your context and the fragment will be returned as a slice of just the elements in your fragment.
You can see an example in the Partial* functions for go-html-transform's go.net/html wrapper package. https://code.google.com/p/go-html-transform/source/browse/h5/h5.go#32

Resources