Skip to content

Commit

Permalink
Improve readmes etc (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
karelbilek authored Feb 15, 2024
1 parent 55001c2 commit 1c15f29
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 18 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/lint-revive.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: lint-revive
on:
push:
branches:
- master
- main
pull_request:

permissions:
contents: read

jobs:
revive:
name: revive
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: stable
- uses: morphy2k/revive-action@v2
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ jobs:
with:
go-version: stable
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v4
with:
version: latest
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# template-parse-recursive
Package for parsing go templates recursively
Tool for parsing go templates recursively.

By default, go's template.ParseGlob does not traverse folders recursively, and uses only filename without folder name as a template name.

This package goes through subfolders recursively and parses the files matching the glob. The templates have the subfolder path in the name, separated by OS-specific separator, as done by path/filepath.
This package goes through subfolders recursively and parses the files matching the glob. The templates have the subfolder path in the name, separated by forward slash (even on Windows).

The template names are as relative to the given folder.

Expand All @@ -23,7 +23,7 @@ import (

func main() {
t, err := recurparse.HTMLParse(
template.New("templates"),
nil, // or existing txt.template
"path/to/templates",
"*.html",
)
Expand Down
Empty file added go.sum
Empty file.
12 changes: 6 additions & 6 deletions parse_test.go → match_names_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import (
"testing"
)

func TestGetFiles(t *testing.T) {
type getFilesTestdatum struct {
func TestMatchingNames(t *testing.T) {
type matchingNamesDatum struct {
Directory string
Glob string
Expected []string
ExpectedErr string
}

getFilesTestdata := []getFilesTestdatum{
matchingNamesData := []matchingNamesDatum{
{
Directory: "testdata/getFiles/1_simple_flat",
Glob: "*.html",
Expand All @@ -37,7 +37,7 @@ func TestGetFiles(t *testing.T) {
},
}
if runtime.GOOS != "windows" {
getFilesTestdata = append(getFilesTestdata, getFilesTestdatum{
matchingNamesData = append(matchingNamesData, matchingNamesDatum{
Directory: "testdata/getFiles/3_unix_symlink",
Glob: "*.html",
Expected: []string{
Expand All @@ -50,15 +50,15 @@ func TestGetFiles(t *testing.T) {
}

TEST:
for i, d := range getFilesTestdata {
for i, d := range matchingNamesData {
resolved, err := filepath.EvalSymlinks(d.Directory)
if err != nil {
t.Fatalf("test %d: cannot resolve %q: %+v", i, d.Directory, err)
}

fsys := os.DirFS(resolved)

files, err := getFilesFS(fsys, d.Glob)
files, err := matchingNames(fsys, d.Glob)

if d.ExpectedErr != "" {
if err == nil || err.Error() != d.ExpectedErr {
Expand Down
61 changes: 53 additions & 8 deletions parse.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
// Package recurparse parsing go templates recursively, instead of default template behavior
// that puts all files together.
//
// It goes through subfolders recursively and parses the files matching the glob.
// The templates have the subfolder path in the name, separated by forward slash (even on windows).
//
// The template names are as relative to the given folder.
//
// All the 4 functions behave in similar way.
//
// If the first argument is nil,
// the resulting template will have one of the files as name and content;
// if it's an existing template, it will add the files as associated templates.
//
// The pattern works only on the final filename; that is, k*.html will match foo/bar/kxxx.html;
// it does NOT filter the directory name, all directories are walked through.
//
// The matching logic is using filepath.Match on the filename, in the same way template.Parse do it.
// It follows all symlinks, the symlinks will be there under the symlink name.
// If there is a "symlink loop" (that is, symlink to .. or similar), the function will panic and run out of memory.
//
// If there is no files that matches, the function errors, same as go's ParseFiles.
package recurparse

import (
Expand All @@ -10,12 +32,15 @@ import (
"path/filepath"
)

type nameGiver interface {
// named templ, as `template` is import name in tests
// this is all we actually need for template type
type templ interface {
comparable
Name() string
}

type templateCreator[T nameGiver] interface {
// this is abstraction of top-level template functions, so I can reuse same functions
type templateCreator[T templ] interface {
New(name string) T
NewBasedOn(nameGiver T, name string) T
Parse(nameGiver T, text string) (T, error)
Expand Down Expand Up @@ -58,19 +83,22 @@ func (textTemplateCreator) Nil() *templateText.Template {
return nil
}

func parseFS[T nameGiver](t T, creator templateCreator[T], fsys fs.FS, glob string) (T, error) {
func parseFS[T templ](t T, creator templateCreator[T], fsys fs.FS, glob string) (T, error) {
// first we get the names
n := creator.Nil()
files, err := getFilesFS(fsys, glob)
files, err := matchingNames(fsys, glob)
if err != nil {
return n, err
}
// logic copied from src/html/template/helper.go

if len(files) == 0 {
// Not really a problem, but be consistent.
// Not really a problem, but be consistent with ParseFiles
return n, fmt.Errorf("recurparse: no files matched")
}

// now parse the templates.
// the actual code just logic copied from src/html/template/helper.go, just changed for our purposes

for _, filename := range files {
b, err := fs.ReadFile(fsys, filename)
if err != nil {
Expand Down Expand Up @@ -102,10 +130,16 @@ func parseFS[T nameGiver](t T, creator templateCreator[T], fsys fs.FS, glob stri
return t, nil
}

// TextParseFS opens a fs.FS filesystem and recursively parses the files there as text templates.
//
// See package docs for details of the behavior.
func TextParseFS(t *templateText.Template, fsys fs.FS, glob string) (*templateText.Template, error) {
return parseFS[*templateText.Template](t, textTemplateCreator{}, fsys, glob)
}

// TextParse opens a directory and recursively parses the files there as text templates.
//
// See package docs for details of the behavior.
func TextParse(t *templateText.Template, dirPath, glob string) (*templateText.Template, error) {
resolved, err := filepath.EvalSymlinks(dirPath)
if err != nil {
Expand All @@ -116,10 +150,16 @@ func TextParse(t *templateText.Template, dirPath, glob string) (*templateText.Te
return TextParseFS(t, fsys, glob)
}

// HTMLParseFS opens a fs.FS filesystem and recursively parses the files there as HTML templates.
//
// See package docs for details of the behavior.
func HTMLParseFS(t *templateHtml.Template, fsys fs.FS, glob string) (*templateHtml.Template, error) {
return parseFS[*templateHtml.Template](t, htmlTemplateCreator{}, fsys, glob)
}

// HTMLParse opens a fs.FS filesystem and recursively parses the files there as HTML templates.
//
// See package docs for details of the behavior.
func HTMLParse(t *templateHtml.Template, dirPath, glob string) (*templateHtml.Template, error) {
resolved, err := filepath.EvalSymlinks(dirPath)
if err != nil {
Expand All @@ -130,7 +170,8 @@ func HTMLParse(t *templateHtml.Template, dirPath, glob string) (*templateHtml.Te
return HTMLParseFS(t, fsys, glob)
}

func getFilesFS(myfs fs.FS, glob string) ([]string, error) {
// matchingNames is where we walk through the FS and actually get the names
func matchingNames(myfs fs.FS, glob string) ([]string, error) {
isSymlink := func(d fs.DirEntry) (bool, error) {
info, err := d.Info()
if err != nil {
Expand All @@ -145,6 +186,11 @@ func getFilesFS(myfs fs.FS, glob string) ([]string, error) {
var walk func(dir string) error
walk = func(dir string) error {
err := fs.WalkDir(myfs, dir, func(path string, d fs.DirEntry, err error) error {
// we return errors everywhere we can... not sure if it's the best idea,
// but I guess better error than not? be safe
if err != nil {
return err
}
if !d.IsDir() {
isMatched, err := filepath.Match(glob, d.Name())
if err != nil {
Expand All @@ -160,7 +206,6 @@ func getFilesFS(myfs fs.FS, glob string) ([]string, error) {
if sym {
fsStat, err := fs.Stat(myfs, path)
if err == nil {
// ignore error here; cen be non-existent symlink target
if err != nil {
return err
}
Expand Down
67 changes: 67 additions & 0 deletions simple_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package recurparse

import (
"embed"
"html/template"
"strings"
"testing"
)

//go:embed testdata/simple/*.txt
var simple embed.FS

func TestSimpleNil(t *testing.T) {
tmpl, err := HTMLParseFS(
nil,
simple,
"*.txt",
)
if err != nil {
panic(err)
}

b := &strings.Builder{}
err = tmpl.ExecuteTemplate(b, "testdata/simple/simple.txt", struct{ Foo string }{Foo: "bar"})
if err != nil {
panic(err)
}
if b.String() != "super simple bar" {
t.Error("not equal super simple bar")
}

}

func TestSimpleExisting(t *testing.T) {
existing, err := template.New("existing").Parse("existing {{.Foo}}")
if err != nil {
panic(err)
}

tmpl, err := HTMLParseFS(
existing,
simple,
"*.txt",
)
if err != nil {
panic(err)
}

b := &strings.Builder{}
err = tmpl.ExecuteTemplate(b, "testdata/simple/simple.txt", struct{ Foo string }{Foo: "bar"})
if err != nil {
panic(err)
}
if b.String() != "super simple bar" {
t.Error("not equal super simple bar")
}

b = &strings.Builder{}
err = tmpl.ExecuteTemplate(b, "existing", struct{ Foo string }{Foo: "bar"})
if err != nil {
panic(err)
}

if b.String() != "existing bar" {
t.Error("not equal super simple bar")
}
}
1 change: 1 addition & 0 deletions testdata/simple/simple.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
super simple {{.Foo}}

0 comments on commit 1c15f29

Please sign in to comment.