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
}
Related
I am trying to zip an existing directory that has some empty subdirectories as well.
Here is the folder structure.
parent/
├── child
│ └── child.txt
├── empty-folder
└── parent.txt
2 directories, 2 files
Here is the source code. It writes all the subdirectories which have files on that. But it skipped an empty subdirectory. Is there any way to add an empty subdirectory as well in the zip file?. Thanks in advance.
package main
import (
"archive/zip"
"fmt"
"io"
"os"
"path/filepath"
)
// check for error and stop the execution
func checkForError(err error) {
if err != nil {
fmt.Println("Error - ", err)
os.Exit(1)
}
}
const (
ZIP_FILE_NAME = "example.zip"
MAIN_FOLDER_NAME = "parent"
)
// Main function
func main() {
var targetFilePaths []string
// get filepaths in all folders
err := filepath.Walk(MAIN_FOLDER_NAME, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
// add all the file paths to slice
targetFilePaths = append(targetFilePaths, path)
return nil
})
checkForError(err)
// zip file logic starts here
ZipFile, err := os.Create(ZIP_FILE_NAME)
checkForError(err)
defer ZipFile.Close()
zipWriter := zip.NewWriter(ZipFile)
defer zipWriter.Close()
for _, targetFilePath := range targetFilePaths {
file, err := os.Open(targetFilePath)
checkForError(err)
defer file.Close()
// create path in zip
w, err := zipWriter.Create(targetFilePath)
checkForError(err)
// write file to zip
_, err = io.Copy(w, file)
checkForError(err)
}
}
To write an empty directory you just need to call Create with the directory path with a trailing path separator.
package main
import (
"archive/zip"
"fmt"
"io"
"log"
"os"
"path/filepath"
)
const (
ZIP_FILE_NAME = "example.zip"
MAIN_FOLDER_NAME = "parent"
)
type fileMeta struct {
Path string
IsDir bool
}
func main() {
var files []fileMeta
err := filepath.Walk(MAIN_FOLDER_NAME, func(path string, info os.FileInfo, err error) error {
files = append(files, fileMeta{Path: path, IsDir: info.IsDir()})
return nil
})
if err != nil {
log.Fatalln(err)
}
z, err := os.Create(ZIP_FILE_NAME)
if err != nil {
log.Fatalln(err)
}
defer z.Close()
zw := zip.NewWriter(z)
defer zw.Close()
for _, f := range files {
path := f.Path
if f.IsDir {
path = fmt.Sprintf("%s%c", path, os.PathSeparator)
}
w, err := zw.Create(path)
if err != nil {
log.Fatalln(err)
}
if !f.IsDir {
file, err := os.Open(f.Path)
if err != nil {
log.Fatalln(err)
}
defer file.Close()
if _, err = io.Copy(w, file); err != nil {
log.Fatalln(err)
}
}
}
}
Is there an easy way to get the permanent MAC Address using Go?
package main
import (
"fmt"
"log"
"net"
)
func getMacAddr() ([]string, error) {
ifas, err := net.Interfaces()
if err != nil {
return nil, err
}
var as []string
for _, ifa := range ifas {
a := ifa.HardwareAddr.String()
if a != "" {
as = append(as, a)
}
}
return as, nil
}
func main() {
as, err := getMacAddr()
if err != nil {
log.Fatal(err)
}
for _, a := range as {
fmt.Println(a)
}
}
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"})
}
I have an issue with reading a YAML file. I think it's something in the file structure but I can't figure out what.
YAML file:
conf:
hits:5
time:5000000
code:
type conf struct {
hits int64 `yaml:"hits"`
time int64 `yaml:"time"`
}
func (c *conf) getConf() *conf {
yamlFile, err := ioutil.ReadFile("conf.yaml")
if err != nil {
log.Printf("yamlFile.Get err #%v ", err)
}
err = yaml.Unmarshal(yamlFile, c)
if err != nil {
log.Fatalf("Unmarshal: %v", err)
}
return c
}
your yaml file must be
hits: 5
time: 5000000
your code should look like this:
package main
import (
"fmt"
"gopkg.in/yaml.v2"
"io/ioutil"
"log"
)
type conf struct {
Hits int64 `yaml:"hits"`
Time int64 `yaml:"time"`
}
func (c *conf) getConf() *conf {
yamlFile, err := ioutil.ReadFile("conf.yaml")
if err != nil {
log.Printf("yamlFile.Get err #%v ", err)
}
err = yaml.Unmarshal(yamlFile, c)
if err != nil {
log.Fatalf("Unmarshal: %v", err)
}
return c
}
func main() {
var c conf
c.getConf()
fmt.Println(c)
}
the main error was capital letter for your struct.
Example
Using an upgraded version 3 of yaml package.
An example conf.yaml file:
conf:
hits: 5
time: 5000000
camelCase: sometext
The main.go file:
package main
import (
"fmt"
"io/ioutil"
"log"
"gopkg.in/yaml.v3"
)
type myData struct {
Conf struct {
Hits int64
Time int64
CamelCase string `yaml:"camelCase"`
}
}
func readConf(filename string) (*myData, error) {
buf, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
c := &myData{}
err = yaml.Unmarshal(buf, c)
if err != nil {
return nil, fmt.Errorf("in file %q: %w", filename, err)
}
return c, err
}
func main() {
c, err := readConf("conf.yaml")
if err != nil {
log.Fatal(err)
}
fmt.Printf("%#v", c)
}
Running instructions (in case it's the first time you step out of stdlib):
cat conf.yaml
go mod init example.com/whatever
go get gopkg.in/yaml.v3
cat go.sum
go run .
About The yaml:"field"
The tags (like yaml:"field") are optional for all-lowercase yaml key identifiers. For demonstration the above example parses an extra camel case identifier which does require such a tag.
Corner Case: JSON+YAML
Confusingly, the useful lowercasing behavior of yaml package is not seen in the standard json package. Do you have a data structure which is sometimes encoded to JSON and sometimes to YAML? If so, consider specifying both JSON tags and YAML tags on literally every field. Verbose, but reduces mistakes. Example below.
type myData struct {
Conf conf `yaml:"conf" json:"conf"`
}
type conf struct {
Hits int64 `yaml:"hits" json:"hits"`
Time int64 `yaml:"time" json:"time"`
CamelCase string `yaml:"camelCase" json:"camelCase"`
}
package main
import (
"gopkg.in/yaml.v2"
"io/ioutil"
"log"
)
type someConf struct {
AccessKeyID string `yaml:"access_key_id"`
//...
}
func getConf(file string, cnf interface{}) error {
yamlFile, err := ioutil.ReadFile(file)
if err == nil {
err = yaml.Unmarshal(yamlFile, cnf)
}
return err
}
func main() {
cfg := &awsConf{}
if err := getConf("conf.yml", cfg); err != nil {
log.Panicln(err)
}
}
shortest getConf ever
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)
}
}