Skip to content

Commit fc05193

Browse files
committed
Updates
1 parent a551fdd commit fc05193

9 files changed

+652
-290
lines changed

context.go

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
1-
// Package script is a library facilitating the creation of programs that resemble
2-
// bash scripts.
31
package script
42

53
import (
64
"os"
5+
6+
"github.com/spf13/afero"
77
)
88

99
// Context for script operations. A Context includes the working directory and provides
1010
// access the buffers and results of commands run in the Context.
1111
// Using different Contexts it is possible to handle multiple separate environments.
1212
type Context struct {
1313
workingDir string
14-
env []string
14+
env map[string]string
15+
fs afero.Fs
1516
}
1617

1718
// NewContext returns a pointer to a new Context.
1819
func NewContext() (context *Context) {
1920
// initialize Context
2021
context = &Context{}
22+
context.env = make(map[string]string, 0)
23+
context.fs = afero.NewOsFs()
2124

2225
cwd, err := os.Getwd()
2326
if err == nil {
@@ -26,12 +29,17 @@ func NewContext() (context *Context) {
2629
return
2730
}
2831

29-
// SetWorkingDir changes the current working dir.
32+
// SetWorkingDir changes the current working dir
3033
func (c *Context) SetWorkingDir(workingDir string) {
3134
c.workingDir = workingDir
3235
}
3336

34-
// WorkingDir retrieves the current working dir.
37+
// WorkingDir retrieves the current working dir
3538
func (c *Context) WorkingDir() string {
3639
return c.workingDir
3740
}
41+
42+
// IsUserRoot checks if a user is root priviledged (Linux only? Mac? Windows?)
43+
func (c *Context) IsUserRoot() bool {
44+
return os.Geteuid() == 0
45+
}

context_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package script
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestWorkingDir(t *testing.T) {
11+
workingDir := "/tmp/working-dir"
12+
sc := NewContext()
13+
sc.SetWorkingDir(workingDir)
14+
assert.Equal(t, workingDir, sc.WorkingDir(), fmt.Sprintf("Expected working directory not set (should be %s)", workingDir))
15+
}
16+
17+
func TestIsUserRoot(t *testing.T) {
18+
sc := NewContext()
19+
assert.False(t, sc.IsUserRoot())
20+
}

environment.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package script
2+
3+
import (
4+
"fmt"
5+
"os"
6+
)
7+
8+
// SetEnv sets a certain environment variable for this context
9+
func (c *Context) SetEnv(key, value string) {
10+
c.env[key] = value
11+
}
12+
13+
func (c *Context) getFullEnv() []string {
14+
env := os.Environ()
15+
for key, value := range c.env {
16+
env = append(env, fmt.Sprintf("%s=%s", key, value))
17+
}
18+
return env
19+
}

environment_test.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package script
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestEnvironment(t *testing.T) {
10+
envKey := "MY_ENV_KEY"
11+
envValue := "environment value"
12+
envKeyValue := envKey + "=" + envValue
13+
sc := NewContext()
14+
15+
assert.False(t, inStringArray(sc.getFullEnv(), envKeyValue))
16+
sc.SetEnv(envKey, envValue)
17+
assert.True(t, inStringArray(sc.getFullEnv(), envKeyValue))
18+
}
19+
20+
func inStringArray(haystack []string, needle string) bool {
21+
for _, f := range haystack {
22+
if f == needle {
23+
return true
24+
}
25+
}
26+
return false
27+
}

filesystem.go

+61-63
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,66 @@
1-
// Package script is a library facilitating the creation of programs that resemble
2-
// bash scripts.
31
package script
42

53
import (
6-
"fmt"
7-
"io/ioutil"
84
"os"
95
"path"
106
"path/filepath"
7+
"strings"
118

12-
"github.com/termie/go-shutil"
9+
"github.com/spf13/afero"
1310
)
1411

1512
// FileExists checks if a given filename exists (being a file).
1613
func (c *Context) FileExists(filename string) bool {
1714
filename = c.AbsPath(filename)
18-
fi, err := os.Stat(filename)
15+
fi, err := c.fs.Stat(filename)
1916
return !os.IsNotExist(err) && !fi.IsDir()
2017
}
2118

22-
// MustFileExist ensures if a given filename exists (being a file), panics otherwise.
23-
func (c *Context) MustFileExist(filename string) {
24-
if !c.FileExists(filename) {
25-
panic(fmt.Errorf("File %s does not exist.", filename))
26-
}
27-
}
28-
2919
// EnsureDirExists ensures a directory with the given name exists.
3020
// This function panics if it is unable to find or create a directory as requested.
3121
// TODO also check if permissions are less than requested and update if possible
32-
func (c *Context) EnsureDirExists(dirname string, perm os.FileMode) {
33-
if !c.DirExists(dirname) {
34-
err := os.MkdirAll(dirname, perm)
22+
func (c *Context) EnsureDirExists(dirname string, perm os.FileMode) error {
23+
fullPath := c.AbsPath(dirname)
24+
if !c.DirExists(fullPath) {
25+
err := c.fs.MkdirAll(fullPath, perm)
3526
if err != nil {
36-
panic(fmt.Errorf("Could not create directory at %s.", dirname))
27+
return err
3728
}
3829
}
30+
return nil
3931
}
4032

4133
// EnsurePathForFile guarantees the path for a given filename to exist.
4234
// If the directory is not yet existing, it will be created using the permission
4335
// mask given.
4436
// TODO also check if permissions are less than requested and update if possible
45-
func (c *Context) EnsurePathForFile(filename string, perm os.FileMode) {
46-
c.EnsureDirExists(filepath.Dir(filename), perm)
37+
func (c *Context) EnsurePathForFile(filename string, perm os.FileMode) error {
38+
return c.EnsureDirExists(filepath.Dir(filename), perm)
4739
}
4840

4941
// DirExists checks if a given filename exists (being a directory).
5042
func (c *Context) DirExists(path string) bool {
5143
path = c.AbsPath(path)
52-
fi, err := os.Stat(path)
44+
fi, err := c.fs.Stat(path)
5345
return !os.IsNotExist(err) && fi.IsDir()
5446
}
5547

56-
// MustDirExist checks if a given filename exists (being a directory).
57-
func (c *Context) MustDirExist(path string) {
58-
if !c.DirExists(path) {
59-
panic(fmt.Errorf("Directory %s does not exist.", path))
60-
}
61-
}
62-
63-
// MustGetTempFile guarantees to return a temporary file, panics otherwise
64-
func (c *Context) MustGetTempFile() (tempFile *os.File) {
65-
tempFile, err := ioutil.TempFile("", "")
66-
if err != nil {
67-
panic(err)
68-
}
69-
return
48+
// TempFile returns a temporary file and an error if one occurred
49+
func (c *Context) TempFile() (afero.File, error) {
50+
return afero.TempFile(c.fs, "", "")
7051
}
7152

72-
// MustGetTempDir guarantees to return a temporary directory, panics otherwise
73-
func (c *Context) MustGetTempDir() (tempDir string) {
74-
tempDir, err := ioutil.TempDir("", "")
75-
if err != nil {
76-
panic(err)
77-
}
78-
return
53+
// TempDir guarantees to return a temporary directory, panics otherwise
54+
func (c *Context) TempDir() (string, error) {
55+
return afero.TempDir(c.fs, "", "")
7956
}
8057

8158
// AbsPath returns the absolute path of the path given. If the input path
82-
// is absolute, it is returned untouched. Otherwise the absolute path is
83-
// built relative to the current working directory of the Context.
59+
// is absolute, it is returned untouched except for removing trailing path separators.
60+
// Otherwise the absolute path is built relative to the current working directory of the Context.
61+
// This function always returns a path *without* path separator at the end. See AbsPathSep for one that adds it.
8462
func (c *Context) AbsPath(filename string) string {
63+
filename = c.WithoutTrailingPathSep(filename)
8564
absPath, err := filepath.Abs(filename)
8665
if err != nil {
8766
return filename
@@ -97,23 +76,46 @@ func (c *Context) AbsPath(filename string) string {
9776
return filename
9877
}
9978

79+
// AbsPathSep is a variant of AbsPath that always adds a trailing path separator
80+
func (c *Context) AbsPathSep(filename string) string {
81+
return c.WithTrailingPathSep(c.AbsPath(filename))
82+
}
83+
84+
// WithoutTrailingPathSep trims trailing path seps from a string
85+
func (c *Context) WithoutTrailingPathSep(input string) string {
86+
return strings.TrimRight(input, string(os.PathSeparator))
87+
}
88+
89+
// WithTrailingPathSep trims trailing path seps from a string
90+
func (c *Context) WithTrailingPathSep(input string) string {
91+
if strings.HasSuffix(input, string(os.PathSeparator)) {
92+
return input
93+
}
94+
return input + string(os.PathSeparator)
95+
}
96+
10097
// ResolveSymlinks resolve symlinks in a directory. All symlinked files are
10198
// replaced with copies of the files they point to. Only one level symlinks
10299
// are currently supported.
103100
func (c *Context) ResolveSymlinks(dir string) error {
104-
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
101+
var (
102+
err error
103+
linkTargetPath string
104+
targetInfo os.FileInfo
105+
)
106+
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
105107
// symlink?
106108
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
107109
// resolve
108-
linkTargetPath, err := filepath.EvalSymlinks(path)
110+
linkTargetPath, err = filepath.EvalSymlinks(path)
109111
if err != nil {
110112
panic(err)
111113
}
112-
targetInfo, err := os.Stat(linkTargetPath)
114+
targetInfo, err = c.fs.Stat(linkTargetPath)
113115
if err != nil {
114116
panic(err)
115117
}
116-
os.Remove(path)
118+
c.fs.Remove(path)
117119
// directory?
118120
if targetInfo.IsDir() {
119121
c.CopyDir(linkTargetPath, path)
@@ -135,11 +137,11 @@ func (c *Context) MoveFile(from, to string) error {
135137
to = c.AbsPath(to)
136138

137139
// work around "invalid cross-device link" for os.Rename
138-
err := shutil.CopyFile(from, to, true)
140+
err := CopyFile(c.fs, from, to, true)
139141
if err != nil {
140142
return err
141143
}
142-
err = os.Remove(from)
144+
err = c.fs.Remove(from)
143145
if err != nil {
144146
return err
145147
}
@@ -153,17 +155,15 @@ func (c *Context) MoveDir(from, to string) error {
153155
to = c.AbsPath(to)
154156

155157
// work around "invalid cross-device link" for os.Rename
156-
options := &shutil.CopyTreeOptions{
157-
Symlinks: true,
158-
Ignore: nil,
159-
CopyFunction: shutil.Copy,
160-
IgnoreDanglingSymlinks: false,
158+
options := &CopyTreeOptions{
159+
Ignore: nil,
160+
CopyFunction: Copy,
161161
}
162-
err := shutil.CopyTree(from, to, options)
162+
err := CopyTree(c.fs, from, to, options)
163163
if err != nil {
164164
return err
165165
}
166-
err = os.RemoveAll(from)
166+
err = c.fs.RemoveAll(from)
167167
if err != nil {
168168
return err
169169
}
@@ -173,18 +173,16 @@ func (c *Context) MoveDir(from, to string) error {
173173
// CopyFile copies a file. Cross-device copying is supported, so files
174174
// can be copied from and to tmpfs mounts.
175175
func (c *Context) CopyFile(from, to string) error {
176-
return shutil.CopyFile(from, to, true) // don't follow symlinks
176+
return CopyFile(c.fs, from, to, true) // don't follow symlinks
177177
}
178178

179179
// CopyDir copies a directory. Cross-device copying is supported, so directories
180180
// can be copied from and to tmpfs mounts.
181181
func (c *Context) CopyDir(src, dst string) error {
182-
options := &shutil.CopyTreeOptions{
183-
Symlinks: true,
184-
Ignore: nil,
185-
CopyFunction: shutil.Copy,
186-
IgnoreDanglingSymlinks: false,
182+
options := &CopyTreeOptions{
183+
Ignore: nil,
184+
CopyFunction: Copy,
187185
}
188-
err := shutil.CopyTree(src, dst, options)
186+
err := CopyTree(c.fs, src, dst, options)
189187
return err
190188
}

0 commit comments

Comments
 (0)