Dynamically parsing files - go

For parsing files i have setup a variable for template.ParseFiles and i currently have to manually set each file.
Two things:
How would i be able to walk through a main folder and a multitude of subfolders and automatically add them to ParseFiles so i dont have to manually add each file individually?
How would i be able to call a file with the same name in a subfolder because currently I get an error at runtime if i add same name file in ParseFiles.
var templates = template.Must(template.ParseFiles(
"index.html", // main file
"subfolder/index.html" // subfolder with same filename errors on runtime
"includes/header.html", "includes/footer.html",
))
func main() {
// Walk and ParseFiles
filepath.Walk("files", func(path string, info os.FileInfo, err error) {
if !info.IsDir() {
// Add path to ParseFiles
}
return
})
http.HandleFunc("/", home)
http.ListenAndServe(":8080", nil)
}
func home(w http.ResponseWriter, r *http.Request) {
render(w, "index.html")
}
func render(w http.ResponseWriter, tmpl string) {
err := templates.ExecuteTemplate(w, tmpl, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

To walk a directory looking for files see: http://golang.org/pkg/path/filepath/#Walk or http://golang.org/pkg/html/template/#New and http://golang.org/pkg/html/template/#Template.Parse
As for your other question ParseFiles uses the base name of the file as the template name which results in a collision in your template. You have two choices
Rename the file.
use t := template.New("name of your choice") to create an initial template
Use the walk function you already have started and call t.Parse("text from file") for each template file. You'll have to open and read the contents of the template files yourself to pass in here.
Edit: Code example.
func main() {
// Walk and ParseFiles
t = template.New("my template name")
filepath.Walk("files", func(path string, info os.FileInfo, err error) {
if !info.IsDir() {
// Add path to ParseFiles
content := ""
// read the file into content here
t.Parse(content)
}
return
})
http.HandleFunc("/", home)
http.ListenAndServe(":8080", nil)
}

So basically i set New("path name i want").Parse("String from read file") while walking through the folders.
var templates = template.New("temp")
func main() {
// Walk and ParseFiles
parseFiles()
http.HandleFunc("/", home)
http.ListenAndServe(":8080", nil)
}
//////////////////////
// Handle Functions //
//////////////////////
func home(w http.ResponseWriter, r *http.Request) {
render(w, "index.html")
render(w, "subfolder/index.html")
}
////////////////////////
// Reusable functions //
////////////////////////
func parseFiles() {
filepath.Walk("files", func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
filetext, err := ioutil.ReadFile(path)
if err != nil {
return err
}
text := string(filetext)
templates.New(path).Parse(text)
}
return nil
})
}
func render(w http.ResponseWriter, tmpl string) {
err := templates.ExecuteTemplate(w, tmpl, nil)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}

Related

How to serve file from go embed

I have a static directory, containing a sign.html file :
//go:embed static
var static embed.FS
It is served that way and works fine :
fSys, err := fs.Sub(static, "static")
if err != nil {
return err
}
mux.Handle("/", http.FileServer(http.FS(fSys)))
On some routes though (for instance: /sign), I want to do some checks before serving the page. This is my handler :
func (h Handler) ServeSignPage(w http.ResponseWriter, r *http.Request) error {
publicKey := r.URL.Query().Get("publicKey")
err := h.Service.AuthorizeClientSigning(r.Context(), publicKey)
if err != nil {
return err
}
// this is where I'd like to serve the embed file
// sign.html from the static directory
http.ServeFile(w, r, "sign.html")
return nil
}
Unfortunately, the ServeFile displays not found. How can I serve the file from the file server within that ServeSignPage ?
Option 1
Read the file to a slice of bytes. Write the bytes to the response.
p, err := static.ReadFile("static/sign.html")
if err != nil {
// TODO: Handle error as appropriate for the application.
}
w.Write(p)
Option 2
If the path for the ServeSignPage handler is the same as the static file in the file server, then delegate to the file server.
Store the file server in a package-level variable.
var staticServer http.Handler
func init() {
fSys, err := fs.Sub(static, "static")
if err != nil {
panic(err)
}
staticServer = http.FileServer(http.FS(fSys)))
}
Use the static server as the handler:
mux.Handle("/", staticServer)
Delegate to the static server in ServeSignPage:
func (h Handler) ServeSignPage(w http.ResponseWriter, r *http.Request) error {
publicKey := r.URL.Query().Get("publicKey")
err := h.Service.AuthorizeClientSigning(r.Context(), publicKey)
if err != nil {
return err
}
staticServer.ServeHTTP(w, r)
return nil
}

File not uploaded to directory using go

Am trying to upload files to a directory using go code below.
source
The Issue am having is that when I run the code, it prints files successfully uploaded
but when I get to the directory, No file is uploaded there. Any solution will be appreciated. Thanks
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// the FormFile function takes in the POST input id file
file, header, err := r.FormFile("file")
if err != nil {
fmt.Fprintln(w, err)
return
}
defer file.Close()
out, err := os.Create("/upload")
if err != nil {
fmt.Fprintf(w, "Unable to create the file for writing. Check your write access privilege")
return
}
defer out.Close()
// write the content from POST to the file
_, err = io.Copy(out, file)
if err != nil {
fmt.Fprintln(w, err)
}
fmt.Fprintf(w, "File uploaded successfully : ")
fmt.Fprintf(w, header.Filename)
}
func main() {
http.HandleFunc("/", uploadHandler)
http.ListenAndServe(":8080", nil)
}
Below is how I get my files uploaded to server.
Source
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func uploadFile(w http.ResponseWriter, r *http.Request) {
fmt.Println("File Upload Endpoint Hit")
// Parse our multipart form, 10 << 20 specifies a maximum
// upload of 10 MB files.
r.ParseMultipartForm(10 << 20)
// FormFile returns the first file for the given key `myFile`
// it also returns the FileHeader so we can get the Filename,
// the Header and the size of the file
file, handler, err := r.FormFile("myFile")
if err != nil {
fmt.Println("Error Retrieving the File")
fmt.Println(err)
return
}
defer file.Close()
fmt.Printf("Uploaded File: %+v\n", handler.Filename)
fmt.Printf("File Size: %+v\n", handler.Size)
fmt.Printf("MIME Header: %+v\n", handler.Header)
// Create a temporary file within our temp-images directory that follows
// a particular naming pattern
tempFile, err := ioutil.TempFile("temp-images", "upload-*.png")
if err != nil {
fmt.Println(err)
}
defer tempFile.Close()
// read all of the contents of our uploaded file into a
// byte array
fileBytes, err := ioutil.ReadAll(file)
if err != nil {
fmt.Println(err)
}
// write this byte array to our temporary file
tempFile.Write(fileBytes)
// return that we have successfully uploaded our file!
fmt.Fprintf(w, "Successfully Uploaded File\n")
}
func setupRoutes() {
http.HandleFunc("/upload", uploadFile)
http.ListenAndServe(":8080", nil)
}
func main() {
fmt.Println("Hello World")
setupRoutes()
}

Deep matching template files in subfolders

I have this structure:
./src/main.go:
package main
import (
"log"
"net/http"
"github.com/djviolin/lanti-mvc-gtpl/src/controllers"
)
func main() {
http.HandleFunc("/", controllers.Index)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
./src/controllers/index.go:
package controllers
import (
"html/template"
"log"
"net/http"
)
// Index : frontpage handler
func Index(w http.ResponseWriter, r *http.Request) {
t := template.New("index")
render, err := t.ParseGlob("./views/*.html")
if err != nil {
log.Fatal("Parse: ", err)
return
}
render.Execute(w, map[string]string{"Title": "My title", "Body": "This is the body"})
}
And I have the following template file's structure:
│ index.html
└───partials
footer.html
header.html
How can I deep match the partials in the subfolder? ParseGlob not supporting the ** operator as far as I know. Is there a way to achieve this with the standard library templates?
Update:
I tried to use github.com/mattn/go-zglob (recommended under this github issue) for recursively list all template files in the views folder:
matches, err := zglob.Glob(`./views/**/*.html`)
if err != nil {
log.Fatal("Parse: ", err)
return
}
fmt.Println(matches)
// RETURNS: [views/index.html views/partials/footer.html views/partials/header.html]
Which is returning a string array, however ParseFiles function needs string input separated with , commas, and because of that, the following code throwing this error:
message: 'cannot use matches (type []string) as type string in argument to t.ParseFiles'
I found a solution here. If anyone have a better idea than this, I highly welcome!
package controllers
// github.com/djviolin/lanti-mvc-gtpl/src/controllers/index.go
import (
"fmt"
"html/template"
"log"
"net/http"
"os"
"path/filepath"
)
// GetAllFilePathsInDirectory : Recursively get all file paths in directory, including sub-directories.
func GetAllFilePathsInDirectory(dirpath string) ([]string, error) {
var paths []string
err := filepath.Walk(dirpath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
paths = append(paths, path)
}
return nil
})
if err != nil {
return nil, err
}
return paths, nil
}
// ParseDirectory : Recursively parse all files in directory, including sub-directories.
func ParseDirectory(dirpath string, filename string) (*template.Template, error) {
paths, err := GetAllFilePathsInDirectory(dirpath)
if err != nil {
return nil, err
}
fmt.Println(paths) // logging
t := template.New(filename)
return t.ParseFiles(paths...)
}
// Index : is the index handler
func Index(w http.ResponseWriter, r *http.Request) {
render, err := ParseDirectory("./views", "index")
if err != nil {
log.Fatal("Parse: ", err)
return
}
render.Execute(w, map[string]string{"Title": "My title", "Body": "This is the body"})
}

Is it possible to reload html templates after app is started?

Right now I currently parse all the templates into a variable like so:
var templates = template.New("Template")
func LoadTemplates() {
filepath.Walk("view/", func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(path, ".html") {
templates.ParseFiles(path)
}
return nil
})
}
func Render(w http.ResponseWriter, tmplName string, data interface{}) {
if err := templates.ExecuteTemplate(w, tmplName, data); err != nil {
log.Println(err)
}
}
So if I make any changes, I need to restart the entire app. Is there any way I can make it so that the changes are reflected when they're made
It is completly OK to reload your templates on every request when developing / in debug mode.
You can define an interface and two "template executors" like so
type TemplateExecutor interface{
ExecuteTemplate(wr io.Writer, name string, data interface{}) error
}
type DebugTemplateExecutor struct {
Glob string
}
func (e DebugTemplateExecutor) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
t := template.Must(template.ParseGlob(e.Glob))
return t.ExecuteTemplate(wr, name, data)
}
type ReleaseTemplateExecutor struct {
Template *template.Template
}
func (e ReleaseTemplateExecutor) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
return e.Template.ExecuteTemplate(wr, name, data)
}
that wrap template.Execute(). Pick which one you want at runtime e.g.
const templateGlob = "templates/*.html"
const debug = true
var executor TemplateExecutor
func main() {
if debug {
executor = DebugTemplateExecutor{templateGlob}
} else {
executor = ReleaseTemplateExecutor{
template.Must(template.ParseGlob(templateGlob)),
}
}
http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
executor.ExecuteTemplate(w, "test.html", nil)
})
http.ListenAndServe(":3000", nil)
}
If you need hot reload in production, i'd suggest watching the directory for changes instead of compiling it on every request.
Yes, it is possible to reload (html or text) templates at runtime.
As long as the folder that you are reading the templates from is accessible by the application you can simply repeat the template parsing.
You could do something like this:
var templates = template.New("Template")
func folderChanged(folder string) bool {
// todo: implement filesystem watcher here
return true
}
func ReloadTemplates(templateFolder string) {
newTemplates, err := LoadTemplates(templateFolder)
if err != nil {
log.Println("Unable to load templates: %s", err)
return
}
// override existing templates variable
templates = newTemplates
}
func LoadTemplates(folder string) (*template.Template, error) {
template := template.New("Template")
walkError := filepath.Walk(folder, func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(path, ".html") {
_, parseError := template.ParseFiles(path)
if parseError != nil {
return parseError
}
}
return nil
})
return template, walkError
}
func Render(w http.ResponseWriter, tmplName string, data interface{}) {
templateFolder := "view"
if folderChanged(templateFolder) {
ReloadTemplates(templateFolder)
}
if err := templates.ExecuteTemplate(w, tmplName, data); err != nil {
log.Println(err)
}
}
If you are using a relative path like the ./view-folder from your example it will only work if the directory from which you are executing the application has such a sub-folder. If that is not the case I would suggest using a path relative to the users' home directory or something like that.

Specify names for parsed templates

I am trying to dynamically parse files using walk in a folder and I want to be able to set the path of the file "path/file.html". But my issue is if I have a file in a folder "path/folder/files.html" I can't do it because when I ExecuteTemplate the file name will be the same "files.html". Is it possible to name each template as I ParseFiles?
Im ok with doing a file one at a time if trying to do them all at once wont work.
// Parse file and send to responsewriter
func View(w http.ResponseWriter, path string) {
temp, err := template.ParseFiles("application/views/"+path+".html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
} else {
temp.ExecuteTemplate(w, path, nil)
}
}
Walk the filesystem using filepath.Walk and a consumer method that will create templates with the full file paths as names:
package main
import (
"fmt"
"html/template"
"os"
"path/filepath"
)
func consumer(p string, i os.FileInfo, e error) error {
t := template.New(p)
fmt.Println(t.Name())
return nil
}
func main() {
filepath.Walk("/path/to/template/root", filepath.WalkFunc(consumer))
}
You can try template.Lookup, the whole process looks like:
var (
templates *template.Template
)
func loadTemplate() {
funcMap := template.FuncMap{
"safe":func(s string) template.HTML {
return template.HTML(s)
},
}
var err error
templates, err = utils.BuildTemplate("/theme/path/", funcMap)
if err != nil {
log.Printf("Can't read template file %v,", err)
}
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
//lookup the theme your want to use
templ = templates.Lookup("theme.html")
err := templ.Execute(w, data)
if err != nil {
log.Println(err)
}
}
func main() {
loadTemplate()
}
BuildTemplate looks like:
func BuildTemplate(dir string, funcMap template.FuncMap) (*template.Template, error) {
fs, err := ioutil.ReadDir(dir)
if err != nil {
fmt.Printf("Can't read template folder: %s\n", dir)
return nil, err
}
files := make([]string, len(fs))
for i, f := range (fs) {
files[i] = path.Join(dir, f.Name())
}
return template.Must(template.New("Template").Funcs(funcMap).ParseFiles(files...)), nil
}

Resources