Skip to content

Commit

Permalink
feat: allow paths to be skipped by template evaluation
Browse files Browse the repository at this point in the history
eg. The following file will be omitted if `.Include` is false.

```
-rw-r--r--  1 alec  staff   8 20 Nov 12:51 {{ if .Include }}include{{ end }}
```
  • Loading branch information
alecthomas committed Nov 20, 2023
1 parent e2080d1 commit d5bd8d7
Show file tree
Hide file tree
Showing 7 changed files with 23 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ ctx:

- Both path names and file contents are evaluated.
- If a file name ends with `.tmpl`, the `.tmpl` suffix is removed.
- If a file or directory name evalutes to the empty string it will be excluded.

[cookiecutter]: https://github.com/cookiecutter/cookiecutter
18 changes: 16 additions & 2 deletions scaffolder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package scaffolder

import (
"errors"
"fmt"
"io/fs"
"os"
Expand Down Expand Up @@ -70,10 +71,17 @@ func Scaffold(source, destination string, ctx any, options ...Option) error {
return fmt.Errorf("failed to get file info: %w", err)
}

if lastComponent, err := evaluate(filepath.Base(path), ctx, opts.funcs); err != nil {
return fmt.Errorf("failed to evaluate path name: %w", err)
} else if lastComponent == "" {
return errSkip
}

dstPath, err := evaluate(path, ctx, opts.funcs)
if err != nil {
return fmt.Errorf("failed to evaluate path name: %w", err)
}

dstPath = filepath.Join(destination, dstPath)
dstPath = strings.TrimSuffix(dstPath, ".tmpl")

Expand Down Expand Up @@ -155,13 +163,19 @@ func applySymlinks(symlinks map[string]string, path string) error {
return os.Symlink(target, path)
}

// errSkip is returned by walkDir to skip a file or directory.
var errSkip = errors.New("skip directory")

// Depth-first walk of dir executing fn after each entry.
func walkDir(dir string, fn func(path string, d fs.DirEntry) error) error {
dirInfo, err := os.Stat(dir)
if err != nil {
return err
}
if err = fn(dir, fs.FileInfoToDirEntry(dirInfo)); err != nil {
if errors.Is(err, errSkip) {
return nil
}
return err
}
entries, err := os.ReadDir(dir)
Expand All @@ -171,12 +185,12 @@ func walkDir(dir string, fn func(path string, d fs.DirEntry) error) error {
for _, entry := range entries {
if entry.IsDir() {
err = walkDir(filepath.Join(dir, entry.Name()), fn)
if err != nil {
if err != nil && !errors.Is(err, errSkip) {
return err
}
} else {
err = fn(filepath.Join(dir, entry.Name()), entry)
if err != nil {
if err != nil && !errors.Is(err, errSkip) {
return err
}
}
Expand Down
5 changes: 4 additions & 1 deletion scaffolder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import (
func TestScaffolder(t *testing.T) {
tmpDir := filepath.Join(t.TempDir(), "new")
err := Scaffold("testdata/template", tmpDir, map[string]any{
"Name": "test",
"Name": "test",
"Include": true,
}, Exclude("excluded"))
assert.NoError(t, err)
type file struct {
Expand All @@ -21,6 +22,8 @@ func TestScaffolder(t *testing.T) {
content string
}
expect := []file{
{"include", 0o600, "included"},
{"included-dir/included", 0o600, "included"},
{"intermediate", 0o700 | os.ModeSymlink, "Hello, test!\n"},
{"regular-test", 0o600, "Hello, test!\n"},
{"symlink-test", 0o700 | os.ModeSymlink, "Hello, test!\n"},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
included
1 change: 1 addition & 0 deletions testdata/template/{{ if .Include }}include{{ end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
included
Empty file.
Empty file.

0 comments on commit d5bd8d7

Please sign in to comment.