Skip to content

Commit

Permalink
Add expansion of envvars and a Glob function to target package (magef…
Browse files Browse the repository at this point in the history
  • Loading branch information
Kent Quirk authored and natefinch committed Mar 14, 2019
1 parent 00c8eff commit 8c9aa25
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 9 deletions.
58 changes: 49 additions & 9 deletions target/target.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
package target

import (
"errors"
"os"
"path/filepath"
"time"
)

// Path reports if any of the sources have been modified more recently
// than the destination. Path does not descend into directories, it literally
// just checks the modtime of each thing you pass to it. If the destination
// file doesn't exist, it always returns true and nil. It's an error if any of
// the sources don't exist.
// expand takes a collection of sources as strings, and for each one, it expands
// environment variables in the form $FOO or ${FOO} using os.ExpandEnv
func expand(sources []string) []string {
for i, s := range sources {
// first expand the environment
sources[i] = os.ExpandEnv(s)
}
return sources
}

// Path first expands environment variables like $FOO or ${FOO}, and then
// reports if any of the sources have been modified more recently than the
// destination. Path does not descend into directories, it literally just checks
// the modtime of each thing you pass to it. If the destination file doesn't
// exist, it always returns true and nil. It's an error if any of the sources
// don't exist.
func Path(dst string, sources ...string) (bool, error) {
stat, err := os.Stat(dst)
stat, err := os.Stat(os.ExpandEnv(dst))
if os.IsNotExist(err) {
return true, nil
}
if err != nil {
return false, err
}
srcTime := stat.ModTime()
dt, err := loadTargets(sources)
dt, err := loadTargets(expand(sources))
if err != nil {
return false, err
}
Expand All @@ -31,13 +43,41 @@ func Path(dst string, sources ...string) (bool, error) {
return false, nil
}

// Glob expands each of the globs (file patterns) into individual sources and
// then calls Path on the result, reporting if any of the resulting sources have
// been modified more recently than the destination. Syntax for Glob patterns is
// the same as stdlib's filepath.Glob. Note that Glob does not expand
// environment variables before globbing -- env var expansion happens during
// the call to Path. It is an error for any glob to return an empty result.
func Glob(dst string, globs ...string) (bool, error) {
for _, g := range globs {
files, err := filepath.Glob(g)
if err != nil {
return false, err
}
if len(files) == 0 {
return false, errors.New("glob didn't match any files: " + g)
}
// it's best to evaluate each glob as we do it
// because we may be able to early-exit
shouldDo, err := Path(dst, files...)
if err != nil {
return false, err
}
if shouldDo {
return true, nil
}
}
return false, nil
}

// Dir reports whether any of the sources have been modified more recently than
// the destination. If a source or destination is a directory, modtimes of
// files under those directories are compared instead. If the destination file
// doesn't exist, it always returns true and nil. It's an error if any of the
// sources don't exist.
func Dir(dst string, sources ...string) (bool, error) {
stat, err := os.Stat(dst)
stat, err := os.Stat(os.ExpandEnv(dst))
if os.IsNotExist(err) {
return true, nil
}
Expand All @@ -51,7 +91,7 @@ func Dir(dst string, sources ...string) (bool, error) {
return false, err
}
}
dt, err := loadTargets(sources)
dt, err := loadTargets(expand(sources))
if err != nil {
return false, err
}
Expand Down
160 changes: 160 additions & 0 deletions target/target_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ func TestPathMissingSource(t *testing.T) {
}
}

func TestGlobEmptyGlob(t *testing.T) {
t.Parallel()
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
dst := filepath.Join(dir, "dst")
err = ioutil.WriteFile(dst, []byte("hi!"), 0644)
if err != nil {
t.Fatal(err)
}
src := filepath.Join(dir, "src*")
_, err = Glob(dst, src)
if err == nil {
t.Fatal("Expected error, but got nil")
}
}

func TestDirMissingSrc(t *testing.T) {
t.Parallel()
dir, err := ioutil.TempDir("", "")
Expand Down Expand Up @@ -95,6 +114,102 @@ func TestDirMissingDest(t *testing.T) {
}
}

func TestGlob(t *testing.T) {
t.Parallel()
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)

err = os.MkdirAll(filepath.Join(dir, filepath.FromSlash("dir/dir2")), 0777)
if err != nil {
t.Fatal(err)
}
// files are created in order so we know how to expect
files := []string{
"old_executable",
"file_one.src",
"dir/file_two.src",
"middle_executable",
"file_three.src",
"dir/dir2/file_four.src",
"built_executable",
}
for _, v := range files {
time.Sleep(10 * time.Millisecond)
f := filepath.Join(dir, filepath.FromSlash(v))
err := ioutil.WriteFile(f, []byte(v), 0644)
if err != nil {
t.Fatal(err)
}
}

// force an environment variable for testing
os.Setenv("MYVAR", "file")
os.Setenv("THREE", "three")

table := []struct {
desc string
target string
sources []string
expect bool
}{
{
desc: "Missing target",
target: "missing_file",
sources: []string{"file*.src"},
expect: true,
},
{
desc: "Target is newer",
target: "built_executable",
sources: []string{"*.src", "dir/*.src"},
expect: false,
},
{
desc: "No actual globs",
target: "built_executable",
sources: []string{"file_one.src", "file_three.src"},
expect: false,
},
{
desc: "Target is older",
target: "old_executable",
sources: []string{"f*.src"},
expect: true,
},
{
desc: "Target is in the middle of files in the glob",
target: "middle_executable",
sources: []string{"file*"},
expect: true,
},
{
desc: "Globs work for dirs",
target: "older_executable",
sources: []string{"d*"},
expect: true,
},
}

for _, c := range table {
t.Run(c.desc, func(t *testing.T) {
for i := range c.sources {
c.sources[i] = filepath.Join(dir, c.sources[i])
}
c.target = filepath.Join(dir, c.target)
v, err := Glob(c.target, c.sources...)
if err != nil {
t.Fatal(err)
}
if v != c.expect {
t.Errorf("expecting %v got %v", c.expect, v)
}
})
}
}

func TestPath(t *testing.T) {
t.Parallel()
dir, err := ioutil.TempDir("", "")
Expand Down Expand Up @@ -123,6 +238,10 @@ func TestPath(t *testing.T) {
}
}

// force an environment variable for testing
os.Setenv("MYVAR", "file")
os.Setenv("THREE", "three")

table := []struct {
desc string
target string
Expand Down Expand Up @@ -168,6 +287,18 @@ func TestPath(t *testing.T) {
sources: []string{"dir"},
expect: true,
},
{
desc: "Target is newer; expand source",
target: "${MYVAR}_$THREE",
sources: []string{"file_one"},
expect: false,
},
{
desc: "Target is older; expand dest",
target: "file_one",
sources: []string{"${MYVAR}_$THREE"},
expect: true,
},
}

for _, c := range table {
Expand Down Expand Up @@ -216,6 +347,11 @@ func TestDir(t *testing.T) {
}
}

// force environment variables for testing
os.Setenv("MYFILE", "file")
os.Setenv("MYDIR", "dir")
os.Setenv("X1", "one")

table := []struct {
desc string
target string
Expand Down Expand Up @@ -262,6 +398,30 @@ func TestDir(t *testing.T) {
sources: []string{"dir"},
expect: true,
},
{
desc: "Target is newer (with env expansion)",
target: "${MYFILE}_three",
sources: []string{"${MYFILE}_$X1"},
expect: false,
},
{
desc: "Target is older (with env expansion)",
target: "${MYFILE}_one",
sources: []string{"$MYFILE_three"},
expect: true,
},
{
desc: "Source is older dir (with env expansion)",
target: "${MYFILE}_five",
sources: []string{"${MYDIR}"},
expect: false,
},
{
desc: "Source is newer dir (with env expansion)",
target: "${MYFILE}_$X1",
sources: []string{"$MYDIR"},
expect: true,
},
}

for _, c := range table {
Expand Down

0 comments on commit 8c9aa25

Please sign in to comment.