Deep matching template files in subfolders - go

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"})
}

Related

Golang - Zip a directory which includes empty subdirectory and files

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)
}
}
}
}

Copy files into a folder until it reaches a certain size

I have a folder named "myfolder" that has some txt and jpeg files and and a folder named "test".
I want to copy files inside myfolder to test until it reaches a certain size, for example 10MB, then stop to copy.
This is my code that does not work:
package main
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
)
var (
MyFolderPath = "/home/jim/myfolder"
files []string
)
func copy(source []string, destination string) {
for a, b := range source {
input, _ := ioutil.ReadFile(b)
ioutil.WriteFile(destination+strconv.Itoa(a), input, 0777)
}
}
func CopyFile(path string) {
var size int64
done := errors.New("size reached")
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
copy(files[1:], fmt.Sprintf("%v/test/%v", MyFolderPath, "file"))
size += info.Size()
if size > 10000 {
return done
}
return err
})
if err != nil {
fmt.Println(err)
}
}
func CollectFiles() {
err := filepath.Walk(MyFolderPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Println(err)
}
files = append(files, path)
return nil
})
if err != nil {
fmt.Println(err)
}
}
func main() {
CollectFiles()
CopyFile(fmt.Sprintf("%v/test", MyFolderPath))
}
What I do wrong and how can I do in right way?
This line is wrong:
copy(files[1:], fmt.Sprintf("%v/test/%v", MyFolderPath, "file"))
Every time you find a file match, you tell it to (re)-copy every file in your files slice, except the first one.
You probably want to copy a single file there.
Although your code is difficult to reason through--I'm not sure why files exists at all, nor why you're walking your directory structure twice. So your goals aren't really clear, so it's hard to be more specific.
Also note: You should not use copy as the name of a function, as that is a built-in, so makes your code very confusing to read.
You have multiple places where you're wrong. Perhaps check this code and compare:
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
)
var (
MyFolderPath = "/tmp"
DestPath = "/tmp/dest"
CumulativeSize int64 = 0 // in bytes
ErrDone = errors.New("done")
)
const UpperLimit = 1024 * // 1 Kilobyte
1024 * // 1 Megabyte
10 // 10 Megabytes
func main() {
err := filepath.Walk(MyFolderPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Printf("problem with file (%s): %v", path, err)
return nil
}
if info.IsDir() {
return filepath.SkipDir // skip it, walk will enter it too
}
CumulativeSize += info.Size()
if CumulativeSize >= UpperLimit {
return ErrDone
}
// copy file here
return nil
})
if err == ErrDone {
fmt.Println("finished successfully")
} else {
fmt.Printf("error: %v", err)
}
}

How to read a YAML file

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

Getting blank return when using GoQuery to get video src

I'm trying to get the .mp4 video source of a vine, using GoQuery. However when I run it, I get nothing, no error, or return. Just a blank line.
package main
import (
"fmt"
"log"
"github.com/PuerkitoBio/goquery"
)
func getMP4URL() {
doc, err := goquery.NewDocument("https://vine.co/v/MlWtKgwh7WY")
if err != nil {
log.Fatal(err)
}
doc.Find(".vine-video-container").Each(func(i int, s *goquery.Selection) {
mp4, _ := s.Find("video").Attr("src")
fmt.Printf("MP4: %s", mp4)
})
}
func main() {
getMP4URL()
}
Is this a problem with my code, or with vine itself?
seems that vine adds that id with javascript
if I add
html, err := doc.Html()
if err != nil {
log.Fatal(err)
}
log.Println(html)
before doc.Find there is no .vine-video-container in the html output
try this code :)
package main
import (
"fmt"
"log"
"github.com/PuerkitoBio/goquery"
)
func getMP4URL() {
doc, err := goquery.NewDocument("https://vine.co/v/MlWtKgwh7WY")
if err != nil {
log.Fatal(err)
}
doc.Find("meta").Each(func(i int, s *goquery.Selection) {
op, _ := s.Attr("itemprop")
if op == "contentURL" {
fmt.Println(s.Attr("content"))
}
})
}
func main() {
getMP4URL()
}
Vine embed the metadata of the video in JSON format in the <script type="application/ld+json">. So you need to extract the JSON blob from the tag and decode the JSON to get the src of the video.
The following is complete working code to get the src URL of Vine video:
package main
import (
"encoding/json"
"github.com/PuerkitoBio/goquery"
)
type SharedContent struct {
ContentUrl string `json:"contentUrl"`
}
type VineVideoMetadata struct {
SC SharedContent `json:"sharedContent"`
}
func DecodeVineJsonBlob(blob string) VineVideoMetadata {
meta := VineVideoMetadata{}
err := json.Unmarshal([]byte(blob), &meta)
if err != nil {
panic(err)
}
return meta
}
func GetVineVideoJsonBlob(url string) string {
doc, err := goquery.NewDocument(url)
if err != nil {
panic(err)
}
return doc.Find("script[type=\"application/ld+json\"]").Text()
}
func GetVineVideoSrc(url string) string {
jsonBlob := GetVineVideoJsonBlob(url)
meta := DecodeVineJsonBlob(jsonBlob)
return meta.SC.ContentUrl
}
func main() {
println(GetVineVideoSrc("https://vine.co/v/MlWtKgwh7WY"))
}
output:
https://mtc.cdn.vine.co/r/videos/67FAC9DFA21115619347885645824_22a564aec15.5.0.17428816123715427422.mp4?versionId=4zcm5ySoFhqUQBXU7Ehm3YOuOSjFbkg3

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