Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add .gomplateignore support #225

Merged
merged 14 commits into from
Feb 2, 2019
Merged
9 changes: 9 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@
[[constraint]]
branch = "v2"
name = "gopkg.in/hairyhenderson/yaml.v2"

[[constraint]]
name = "github.com/zealic/xignore"
version = "0.3.2"
zealic marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 12 additions & 0 deletions docs/content/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ You can specify multiple `--file` and `--out` arguments. The same number of each

For processing multiple templates in a directory you can use `--input-dir` and `--output-dir` together. In this case all files in input directory will be processed as templates and the resulting files stored in `--output-dir`. The output directory will be created if it does not exist and the directory structure of the input directory will be preserved.

You can use `.gomplateignore` ignore some files, have the similar behavior to the [.gitignore](https://git-scm.com/docs/gitignore) file.
zealic marked this conversation as resolved.
Show resolved Hide resolved

Example:

```bash
Expand All @@ -43,6 +45,13 @@ Example:
gomplate --input-dir=templates --output-dir=config --datasource config=config.yaml
```

### Ignorefile

You can use ignore file `.gomplateignore` to ignore some files, have the similar behavior to the [.gitignore](https://git-scm.com/docs/gitignore) file.
Nested ignorefile are supported, you can use ignorefile on any sub directroy.

Ignorefile only support `--input-dir` option.

### `--chmod`

By default, output files are created with the same file mode (permissions) as input files. If desired, the `--chmod` option can be used to override this behaviour, and set the output file mode explicitly. This can be useful for creating executable scripts or ensuring write permissions.
Expand All @@ -63,6 +72,9 @@ This will stop all files in the example folder from being processed, as well as

You can also chain the flag to build up a series of globs to be excluded.

The `--exclude` option compatible [.gomplateignore](#ignorefile), exclude rules will effective after [.gomplateignore](#ignorefile).


### `--datasource`/`-d`

Add a data source in `name=URL` form. Specify multiple times to add multiple sources. The data can then be used by the [`datasource`](../functions/data/#datasource) and [`include`](../functions/data/#include) functions.
Expand Down
96 changes: 37 additions & 59 deletions template.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ import (
"github.com/pkg/errors"

"github.com/spf13/afero"
"github.com/zealic/xignore"
)

// Gomplateignore ignore file name, like .gitignore
const Gomplateignore = ".gomplateignore"
zealic marked this conversation as resolved.
Show resolved Hide resolved

// for overriding in tests
var stdin io.ReadCloser = os.Stdin
var fs = afero.NewOsFs()
Expand Down Expand Up @@ -139,57 +143,57 @@ func processTemplates(templates []*tplate) ([]*tplate, error) {
}

// walkDir - given an input dir `dir` and an output dir `outDir`, and a list
// of exclude globs (if any), walk the input directory and create a list of
// of .gomplateignore and exclude globs (if any), walk the input directory and create a list of
// tplate objects, and an error, if any.
func walkDir(dir, outDir string, excludeGlob []string, mode os.FileMode, modeOverride bool) ([]*tplate, error) {
dir = filepath.Clean(dir)
outDir = filepath.Clean(outDir)
si, err := fs.Stat(dir)
if err != nil {
return nil, err
}

entries, err := afero.ReadDir(fs, dir)
dirStat, err := fs.Stat(dir)
if err != nil {
return nil, err
}
dirMode := dirStat.Mode()

if err = fs.MkdirAll(outDir, si.Mode()); err != nil {
return nil, err
}

excludes, err := executeCombinedGlob(excludeGlob)
templates := make([]*tplate, 0)
matcher := xignore.NewMatcher(fs)
matches, err := matcher.Matches(dir, &xignore.MatchesOptions{
Ignorefile: Gomplateignore,
Nested: true, // allow nested ignorefile
AfterPatterns: excludeGlob,
})
if err != nil {
return nil, err
}

templates := make([]*tplate, 0)
for _, entry := range entries {
nextInPath := filepath.Join(dir, entry.Name())
nextOutPath := filepath.Join(outDir, entry.Name())
// Unmatched ignorefile rules's files
files := matches.UnmatchedFiles
for _, file := range files {
nextInPath := filepath.Join(dir, file)
nextOutPath := filepath.Join(outDir, file)

if inList(excludes, nextInPath) {
continue
if mode == 0 {
stat, perr := fs.Stat(nextInPath)
if perr == nil {
mode = stat.Mode()
} else {
mode = dirMode
}
}

if entry.IsDir() {
t, err := walkDir(nextInPath, nextOutPath, excludes, mode, modeOverride)
if err != nil {
return nil, err
}
templates = append(templates, t...)
} else {
if mode == 0 {
mode = entry.Mode()
}
templates = append(templates, &tplate{
name: nextInPath,
targetPath: nextOutPath,
mode: mode,
modeOverride: modeOverride,
})
// Ensure file parent dirs
if err = fs.MkdirAll(filepath.Dir(nextOutPath), dirMode); err != nil {
return nil, err
}

templates = append(templates, &tplate{
name: nextInPath,
targetPath: nextOutPath,
mode: mode,
modeOverride: modeOverride,
})
}

return templates, nil
}

Expand All @@ -213,16 +217,6 @@ func fileToTemplates(inFile, outFile string, mode os.FileMode, modeOverride bool
return tmpl, nil
}

func inList(list []string, entry string) bool {
for _, file := range list {
if file == entry {
return true
}
}

return false
}

func openOutFile(filename string, mode os.FileMode, modeOverride bool) (out io.WriteCloser, err error) {
if conv.ToBool(env.Getenv("GOMPLATE_SUPPRESS_EMPTY", "false")) {
out = newEmptySkipper(func() (io.WriteCloser, error) {
Expand Down Expand Up @@ -272,22 +266,6 @@ func readInput(filename string) (string, error) {
return string(bytes), nil
}

// takes an array of glob strings and executes it as a whole,
// returning a merged list of globbed files
func executeCombinedGlob(globArray []string) ([]string, error) {
var combinedExcludes []string
for _, glob := range globArray {
excludeList, err := afero.Glob(fs, glob)
if err != nil {
return nil, err
}

combinedExcludes = append(combinedExcludes, excludeList...)
}

return combinedExcludes, nil
}

// emptySkipper is a io.WriteCloser wrapper that will only start writing once a
// non-whitespace byte has been encountered. The writer must be provided by the
// `open` func
Expand Down
32 changes: 1 addition & 31 deletions template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,36 +60,6 @@ func TestOpenOutFile(t *testing.T) {
assert.Equal(t, Stdout, f)
}

func TestInList(t *testing.T) {
list := []string{}
assert.False(t, inList(list, ""))

list = nil
assert.False(t, inList(list, ""))

list = []string{"foo", "baz", "qux"}
assert.False(t, inList(list, "bar"))

list = []string{"foo", "bar", "baz"}
assert.True(t, inList(list, "bar"))
}

func TestExecuteCombinedGlob(t *testing.T) {
origfs := fs
defer func() { fs = origfs }()
fs = afero.NewMemMapFs()
_ = fs.MkdirAll("/tmp/one", 0777)
_ = fs.MkdirAll("/tmp/two", 0777)
_ = fs.MkdirAll("/tmp/three", 0777)
afero.WriteFile(fs, "/tmp/one/a", []byte("file a"), 0644)
afero.WriteFile(fs, "/tmp/two/b", []byte("file b"), 0644)
afero.WriteFile(fs, "/tmp/three/c", []byte("file c"), 0644)

excludes, err := executeCombinedGlob([]string{"/tmp/o*/*", "/*/*/b"})
assert.NoError(t, err)
assert.Equal(t, []string{"/tmp/one/a", "/tmp/two/b"}, excludes)
}

func TestWalkDir(t *testing.T) {
origfs := fs
defer func() { fs = origfs }()
Expand All @@ -104,7 +74,7 @@ func TestWalkDir(t *testing.T) {
afero.WriteFile(fs, "/indir/one/bar", []byte("bar"), 0644)
afero.WriteFile(fs, "/indir/two/baz", []byte("baz"), 0644)

templates, err := walkDir("/indir", "/outdir", []string{"/*/two"}, 0, false)
templates, err := walkDir("/indir", "/outdir", []string{"*/two"}, 0, false)

assert.NoError(t, err)
assert.Equal(t, 2, len(templates))
Expand Down
Loading