Skip to content

Commit

Permalink
Add .gomplateignore support (#225)
Browse files Browse the repository at this point in the history
* Add .gomplateignore support

* + Added xignore dependency.

* Fix gometalinter problem : gocyclo and unconvert

* Use xignore to support .gomplateignore.

* Adding intgegration tests for .gomplateignore

* Use AfterPatterns to replace default exclude option implement.

* * Fix  lint issue.

* * Fix unittest issue.

* * Clean test files.

* Update docs/content/usage.md

Co-Authored-By: zealic <[email protected]>

* Use minor range version for xignore.

* Unexport .gomplateignore const.
  • Loading branch information
zealic authored and hairyhenderson committed Feb 2, 2019
1 parent 462c8fd commit d187344
Show file tree
Hide file tree
Showing 12 changed files with 941 additions and 90 deletions.
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"
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` to ignore some files in the input directory, with similar syntax and behaviour to [.gitignore](https://git-scm.com/docs/gitignore) files.

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

// ignorefile name, like .gitignore
const gomplateignore = ".gomplateignore"

// 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

0 comments on commit d187344

Please sign in to comment.