Repository: magefile/mage
Pull request #159 (2018-09-12 03:23:02)

Add -d flag to set the working directory
This also cleans up some environment variable usage that was unwise.


Patch:
diff --git a/mage/main.go b/mage/main.go
index f31f626..9e311b3 100644
--- a/mage/main.go
+++ b/mage/main.go
@@ -12,7 +12,6 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
-	"regexp"
 	"runtime"
 	"sort"
 	"strings"
@@ -74,7 +73,7 @@ const (
 // function to allow it to be used from other programs, specifically so you can
 // go run a simple file that run's mage's Main.
 func Main() int {
-	return ParseAndRun(".", os.Stdout, os.Stderr, os.Stdin, os.Args[1:])
+	return ParseAndRun(os.Stdout, os.Stderr, os.Stdin, os.Args[1:])
 }
 
 // Invocation contains the args for invoking a run of Mage.
@@ -92,15 +91,16 @@ type Invocation struct {
 	Stderr     io.Writer     // writer to write stderr messages to
 	Stdin      io.Reader     // reader to read stdin from
 	Args       []string      // args to pass to the compiled binary
+	GoCmd      string        // the go binary command to run
+	CacheDir   string        // the directory where we should store compiled binaries
 }
 
 // ParseAndRun parses the command line, and then compiles and runs the mage
 // files in the given directory with the given args (do not include the command
 // name in the args).
-func ParseAndRun(dir string, stdout, stderr io.Writer, stdin io.Reader, args []string) int {
+func ParseAndRun(stdout, stderr io.Writer, stdin io.Reader, args []string) int {
 	log := log.New(stderr, "", 0)
 	inv, cmd, err := Parse(stderr, stdout, args)
-	inv.Dir = dir
 	inv.Stderr = stderr
 	inv.Stdin = stdin
 	if err == flag.ErrHelp {
@@ -119,19 +119,18 @@ func ParseAndRun(dir string, stdout, stderr io.Writer, stdin io.Reader, args []s
 		log.Println("built with:", runtime.Version())
 		return 0
 	case Init:
-		if err := generateInit(dir); err != nil {
+		if err := generateInit(inv.Dir); err != nil {
 			log.Println("Error:", err)
 			return 1
 		}
 		log.Println(initFile, "created")
 		return 0
 	case Clean:
-		dir := mg.CacheDir()
-		if err := removeContents(dir); err != nil {
+		if err := removeContents(inv.CacheDir); err != nil {
 			log.Println("Error:", err)
 			return 1
 		}
-		log.Println(dir, "cleaned")
+		log.Println(inv.CacheDir, "cleaned")
 		return 0
 	case CompileStatic:
 		return Invoke(inv)
@@ -148,13 +147,14 @@ func Parse(stderr, stdout io.Writer, args []string) (inv Invocation, cmd Command
 	inv.Stdout = stdout
 	fs := flag.FlagSet{}
 	fs.SetOutput(stdout)
-	fs.BoolVar(&inv.Debug, "debug", false, "turn on debug messages (implies -keep)")
 	fs.BoolVar(&inv.Force, "f", false, "force recreation of compiled magefile")
-	fs.BoolVar(&inv.Verbose, "v", false, "show verbose output when running mage targets")
+	fs.BoolVar(&inv.Debug, "debug", mg.Debug(), "turn on debug messages (implies -keep)")
+	fs.BoolVar(&inv.Verbose, "v", mg.Verbose(), "show verbose output when running mage targets")
 	fs.BoolVar(&inv.List, "l", false, "list mage targets in this directory")
 	fs.BoolVar(&inv.Help, "h", false, "show this help")
 	fs.DurationVar(&inv.Timeout, "t", 0, "timeout in duration parsable format (e.g. 5m30s)")
 	fs.BoolVar(&inv.Keep, "keep", false, "keep intermediate mage files around after running")
+	fs.StringVar(&inv.Dir, "d", ".", "run magefiles in the given directory")
 	var showVersion bool
 	fs.BoolVar(&showVersion, "version", false, "show version info for the mage binary")
 
@@ -164,8 +164,7 @@ func Parse(stderr, stdout io.Writer, args []string) (inv Invocation, cmd Command
 	fs.BoolVar(&clean, "clean", false, "clean out old generated binaries from CACHE_DIR")
 	var compileOutPath string
 	fs.StringVar(&compileOutPath, "compile", "", "output a static binary to the given path")
-	var goCmd string
-	fs.StringVar(&goCmd, "gocmd", "", "use the given go binary to compile the output")
+	fs.StringVar(&inv.GoCmd, "gocmd", mg.GoCmd(), "use the given go binary to compile the output")
 
 	fs.Usage = func() {
 		fmt.Fprint(stdout, `
@@ -183,6 +182,8 @@ Commands:
   -version  show version info for the mage binary
 
 Options:
+  -d <string> 
+          run magefiles in the given directory (default ".")
   -debug  turn on debug messages (implies -keep)
   -h      show description of a target
   -f      force recreation of compiled magefile
@@ -231,27 +232,12 @@ Options:
 		numFlags++
 	}
 
-	if !inv.Debug {
-		inv.Debug = mg.Debug()
-	} else {
-		os.Setenv(mg.DebugEnv, "1")
-	}
 	if inv.Debug {
 		debug.SetOutput(stderr)
 		inv.Keep = true
 	}
 
-	// If verbose is still false, we're going to peek at the environment variable to see if
-	// MAGE_VERBOSE has been set. If so, we're going to use it for the value of MAGE_VERBOSE.
-	if !inv.Verbose {
-		inv.Verbose = mg.Verbose()
-	}
-
-	if goCmd != "" {
-		if err := os.Setenv(mg.GoCmdEnv, goCmd); err != nil {
-			return inv, cmd, fmt.Errorf("failed to set gocmd: %v", err)
-		}
-	}
+	inv.CacheDir = mg.CacheDir()
 
 	if numFlags > 1 {
 		debug.Printf("%d commands defined", numFlags)
@@ -269,8 +255,17 @@ Options:
 // Invoke runs Mage with the given arguments.
 func Invoke(inv Invocation) int {
 	log := log.New(inv.Stderr, "", 0)
+	if inv.GoCmd == "" {
+		inv.GoCmd = "go"
+	}
+	if inv.Dir == "" {
+		inv.Dir = "."
+	}
+	if inv.CacheDir == "" {
+		inv.CacheDir = mg.CacheDir()
+	}
 
-	files, err := Magefiles(inv)
+	files, err := Magefiles(inv.Dir, inv.GoCmd, inv.Stderr, inv.Debug)
 	if err != nil {
 		log.Println("Error determining list of magefiles:", err)
 		return 1
@@ -281,7 +276,7 @@ func Invoke(inv Invocation) int {
 		return 1
 	}
 	debug.Printf("found magefiles: %s", strings.Join(files, ", "))
-	exePath, err := ExeName(files)
+	exePath, err := ExeName(inv.GoCmd, inv.CacheDir, files)
 	if err != nil {
 		log.Println("Error getting exe name:", err)
 		return 1
@@ -336,7 +331,7 @@ func Invoke(inv Invocation) int {
 		}()
 	}
 	files = append(files, main)
-	if err := Compile(inv, exePath, files); err != nil {
+	if err := Compile(inv.Dir, inv.GoCmd, exePath, files, inv.Debug, inv.Stderr, inv.Stdout); err != nil {
 		log.Println("Error:", err)
 		return 1
 	}
@@ -367,11 +362,8 @@ type data struct {
 	Aliases      map[string]string
 }
 
-// Yeah, if we get to go2, this will need to be revisited. I think that's ok.
-var goVerReg = regexp.MustCompile(`1\.[0-9]+`)
-
-func goVersion() (string, error) {
-	cmd := exec.Command(mg.GoCmd(), "version")
+func goVersion(gocmd string) (string, error) {
+	cmd := exec.Command(gocmd, "version")
 	out, stderr := &bytes.Buffer{}, &bytes.Buffer{}
 	cmd.Stdout = out
 	cmd.Stderr = stderr
@@ -385,20 +377,20 @@ func goVersion() (string, error) {
 }
 
 // Magefiles returns the list of magefiles in dir.
-func Magefiles(inv Invocation) ([]string, error) {
+func Magefiles(magePath, goCmd string, stderr io.Writer, isDebug bool) ([]string, error) {
 	fail := func(err error) ([]string, error) {
 		return nil, err
 	}
 
-	debug.Println("getting all non-mage files in", inv.Dir)
+	debug.Println("getting all non-mage files in", magePath)
 	// // first, grab all the files with no build tags specified.. this is actually
 	// // our exclude list of things without the mage build tag.
-	cmd := exec.Command(mg.GoCmd(), "list", "-e", "-f", `{{join .GoFiles "||"}}`)
+	cmd := exec.Command(goCmd, "list", "-e", "-f", `{{join .GoFiles "||"}}`)
 
-	if inv.Debug {
-		cmd.Stderr = inv.Stderr
+	if isDebug {
+		cmd.Stderr = stderr
 	}
-	cmd.Dir = inv.Dir
+	cmd.Dir = magePath
 	b, err := cmd.Output()
 	if err != nil {
 		fail(fmt.Errorf("failed to list non-mage gofiles: %v", err))
@@ -413,11 +405,11 @@ func Magefiles(inv Invocation) ([]string, error) {
 		}
 	}
 	debug.Println("getting all files plus mage files")
-	cmd = exec.Command(mg.GoCmd(), "list", "-tags=mage", "-e", "-f", `{{join .GoFiles "||"}}`)
-	if inv.Debug {
-		cmd.Stderr = inv.Stderr
+	cmd = exec.Command(goCmd, "list", "-tags=mage", "-e", "-f", `{{join .GoFiles "||"}}`)
+	if isDebug {
+		cmd.Stderr = stderr
 	}
-	cmd.Dir = inv.Dir
+	cmd.Dir = magePath
 	b, err = cmd.Output()
 	list = strings.TrimSpace(string(b))
 
@@ -431,35 +423,35 @@ func Magefiles(inv Invocation) ([]string, error) {
 		}
 	}
 	for i := range files {
-		files[i] = filepath.Join(inv.Dir, files[i])
+		files[i] = filepath.Join(magePath, files[i])
 	}
 	return files, nil
 }
 
 // Compile uses the go tool to compile the files into an executable at path.
-func Compile(inv Invocation, path string, gofiles []string) error {
-	debug.Println("compiling to", path)
-	debug.Println("compiling using gocmd:", mg.GoCmd())
-	if inv.Debug {
-		runDebug(mg.GoCmd(), "version")
-		runDebug(mg.GoCmd(), "env")
+func Compile(magePath, goCmd, compileTo string, gofiles []string, isDebug bool, stderr, stdout io.Writer) error {
+	debug.Println("compiling to", compileTo)
+	debug.Println("compiling using gocmd:", goCmd)
+	if isDebug {
+		runDebug(goCmd, "version")
+		runDebug(goCmd, "env")
 	}
 
 	// strip off the path since we're setting the path in the build command
 	for i := range gofiles {
 		gofiles[i] = filepath.Base(gofiles[i])
 	}
-	debug.Printf("running %s build -o %s %s", mg.GoCmd(), path, strings.Join(gofiles, ", "))
-	c := exec.Command(mg.GoCmd(), append([]string{"build", "-o", path}, gofiles...)...)
+	debug.Printf("running %s build -o %s %s", goCmd, compileTo, strings.Join(gofiles, ", "))
+	c := exec.Command(goCmd, append([]string{"build", "-o", compileTo}, gofiles...)...)
 	c.Env = os.Environ()
-	c.Stderr = inv.Stderr
-	c.Stdout = inv.Stdout
-	c.Dir = inv.Dir
+	c.Stderr = stderr
+	c.Stdout = stdout
+	c.Dir = magePath
 	err := c.Run()
 	if err != nil {
 		return errors.New("error compiling magefiles")
 	}
-	if _, err := os.Stat(path); err != nil {
+	if _, err := os.Stat(compileTo); err != nil {
 		return errors.New("failed to find compiled magefile")
 	}
 	return nil
@@ -506,7 +498,7 @@ func GenerateMainfile(path string, info *parse.PkgInfo) error {
 
 // ExeName reports the executable filename that this version of Mage would
 // create for the given magefiles.
-func ExeName(files []string) (string, error) {
+func ExeName(goCmd, cacheDir string, files []string) (string, error) {
 	var hashes []string
 	for _, s := range files {
 		h, err := hashFile(s)
@@ -519,14 +511,14 @@ func ExeName(files []string) (string, error) {
 	// binary.
 	hashes = append(hashes, fmt.Sprintf("%x", sha1.Sum([]byte(tpl))))
 	sort.Strings(hashes)
-	ver, err := goVersion()
+	ver, err := goVersion(goCmd)
 	if err != nil {
 		return "", err
 	}
 	hash := sha1.Sum([]byte(strings.Join(hashes, "") + magicRebuildKey + ver))
 	filename := fmt.Sprintf("%x", hash)
 
-	out := filepath.Join(mg.CacheDir(), filename)
+	out := filepath.Join(cacheDir, filename)
 	if runtime.GOOS == "windows" {
 		out += ".exe"
 	}
@@ -569,6 +561,7 @@ func RunCompiled(inv Invocation, exePath string) int {
 	c.Stderr = inv.Stderr
 	c.Stdout = inv.Stdout
 	c.Stdin = inv.Stdin
+	c.Dir = inv.Dir
 	c.Env = os.Environ()
 	if inv.Verbose {
 		c.Env = append(c.Env, "MAGEFILE_VERBOSE=1")
diff --git a/mg/runtime.go b/mg/runtime.go
index 5c325d2..d738f7a 100644
--- a/mg/runtime.go
+++ b/mg/runtime.go
@@ -23,7 +23,6 @@ const DebugEnv = "MAGEFILE_DEBUG"
 // verbose mode when running a magefile.
 const GoCmdEnv = "MAGEFILE_GOCMD"
 
-
 // Verbose reports whether a magefile was run with the verbose flag.
 func Verbose() bool {
 	b, _ := strconv.ParseBool(os.Getenv(VerboseEnv))
diff --git a/site/content/index.md b/site/content/index.md
index 202c72a..aad1afb 100644
--- a/site/content/index.md
+++ b/site/content/index.md
@@ -65,6 +65,8 @@ Commands:
   -version  show version info for the mage binary
 
 Options:
+  -d <string> 
+          run magefiles in the given directory (default ".")
   -debug  turn on debug messages (implies -keep)
   -h      show description of a target
   -f      force recreation of compiled magefile


Test patch:
diff --git a/mage/main_test.go b/mage/main_test.go
index 9aaf352..85c8bfb 100644
--- a/mage/main_test.go
+++ b/mage/main_test.go
@@ -36,31 +36,21 @@ func TestMain(m *testing.M) {
 func testmain(m *testing.M) int {
 	// ensure we write our temporary binaries to a directory that we'll delete
 	// after running tests.
-	dir := "./testing"
-	abs, err := filepath.Abs(dir)
+	dir, err := ioutil.TempDir("", "")
 	if err != nil {
 		log.Fatal(err)
 	}
-	if err := os.Setenv(mg.CacheEnv, abs); err != nil {
+	defer os.RemoveAll(dir)
+	if err := os.Setenv(mg.CacheEnv, dir); err != nil {
 		log.Fatal(err)
 	}
-	if err := os.Mkdir(dir, 0700); err != nil {
-		if os.IsExist(err) {
-			os.RemoveAll(dir)
-		} else {
-			log.Fatal(err)
-		}
-	}
-	defer os.RemoveAll(dir)
+	defer os.Unsetenv(mg.CacheEnv)
 	return m.Run()
 }
 
 func TestListMagefilesMain(t *testing.T) {
 	buf := &bytes.Buffer{}
-	files, err := Magefiles(Invocation{
-		Dir:    "testdata/mixed_main_files",
-		Stderr: buf,
-	})
+	files, err := Magefiles("testdata/mixed_main_files", "go", buf, false)
 	if err != nil {
 		t.Errorf("error from magefile list: %v: %s", err, buf)
 	}
@@ -72,10 +62,7 @@ func TestListMagefilesMain(t *testing.T) {
 
 func TestListMagefilesLib(t *testing.T) {
 	buf := &bytes.Buffer{}
-	files, err := Magefiles(Invocation{
-		Dir:    "testdata/mixed_lib_files",
-		Stderr: buf,
-	})
+	files, err := Magefiles("testdata/mixed_lib_files", "go", buf, false)
 	if err != nil {
 		t.Errorf("error from magefile list: %v: %s", err, buf)
 	}
@@ -136,6 +123,7 @@ func TestVerbose(t *testing.T) {
 
 func TestVerboseEnv(t *testing.T) {
 	os.Setenv("MAGEFILE_VERBOSE", "true")
+	defer os.Unsetenv("MAGEFILE_VERBOSE")
 	stdout := &bytes.Buffer{}
 	inv, _, err := Parse(ioutil.Discard, stdout, []string{})
 	if err != nil {
@@ -148,7 +136,6 @@ func TestVerboseEnv(t *testing.T) {
 		t.Fatalf("expected %t, but got %t ", expected, inv.Verbose)
 	}
 
-	os.Unsetenv("MAGEFILE_VERBOSE")
 }
 
 func TestList(t *testing.T) {
@@ -292,12 +279,15 @@ func TestPanicsErr(t *testing.T) {
 func TestHashTemplate(t *testing.T) {
 	templ := tpl
 	defer func() { tpl = templ }()
-	name, err := ExeName([]string{"testdata/func.go", "testdata/command.go"})
+	name, err := ExeName("go", mg.CacheDir(), []string{"testdata/func.go", "testdata/command.go"})
 	if err != nil {
 		t.Fatal(err)
 	}
 	tpl = "some other template"
-	changed, err := ExeName([]string{"testdata/func.go", "testdata/command.go"})
+	changed, err := ExeName("go", mg.CacheDir(), []string{"testdata/func.go", "testdata/command.go"})
+	if err != nil {
+		t.Fatal(err)
+	}
 	if changed == name {
 		t.Fatal("expected executable name to chage if template changed")
 	}
@@ -461,18 +451,28 @@ func TestBadSecondTargets(t *testing.T) {
 
 func TestParse(t *testing.T) {
 	buf := &bytes.Buffer{}
-	inv, cmd, err := Parse(ioutil.Discard, buf, []string{"-v", "build"})
+	inv, cmd, err := Parse(ioutil.Discard, buf, []string{"-v", "-debug", "-gocmd=foo", "-d", "dir", "build", "deploy"})
 	if err != nil {
 		t.Fatal("unexpected error", err)
 	}
 	if cmd == Init {
-		t.Fatal("init should be false but was true")
+		t.Error("init should be false but was true")
 	}
 	if cmd == Version {
-		t.Fatal("showVersion should be false but was true")
+		t.Error("showVersion should be false but was true")
+	}
+	if inv.Debug != true {
+		t.Error("debug should be true")
 	}
-	if len(inv.Args) != 1 && inv.Args[0] != "build" {
-		t.Fatalf("expected args to be %q but got %q", []string{"build"}, inv.Args)
+	if inv.Dir != "dir" {
+		t.Errorf("Expected dir to be \"dir\" but was %q", inv.Dir)
+	}
+	if inv.GoCmd != "foo" {
+		t.Errorf("Expected gocmd to be \"foo\" but was %q", inv.GoCmd)
+	}
+	expected := []string{"build", "deploy"}
+	if !reflect.DeepEqual(inv.Args, expected) {
+		t.Fatalf("expected args to be %q but got %q", expected, inv.Args)
 	}
 	if s := buf.String(); s != "" {
 		t.Fatalf("expected no stdout output but got %q", s)
@@ -480,6 +480,24 @@ func TestParse(t *testing.T) {
 
 }
 
+func TestSetDir(t *testing.T) {
+	stdout := &bytes.Buffer{}
+	stderr := &bytes.Buffer{}
+	code := Invoke(Invocation{
+		Dir:    "testdata/setdir",
+		Stdout: stdout,
+		Stderr: stderr,
+		Args:   []string{"TestCurrentDir"},
+	})
+	if code != 0 {
+		t.Errorf("expected code 0, but got %d. Stdout:\n%s\nStderr:\n%s", code, stdout, stderr)
+	}
+	expected := "setdir.go\n"
+	if out := stdout.String(); out != expected {
+		t.Fatalf("expected list of files to be %q, but was %q", expected, out)
+	}
+}
+
 // Test the timeout option
 func TestTimeout(t *testing.T) {
 	stderr := &bytes.Buffer{}
@@ -579,15 +597,18 @@ func TestHelpAlias(t *testing.T) {
 
 func TestAlias(t *testing.T) {
 	stdout := &bytes.Buffer{}
+	stderr := &bytes.Buffer{}
+	debug.SetOutput(stderr)
 	inv := Invocation{
-		Dir:    "./testdata/alias",
+		Dir:    "testdata/alias",
 		Stdout: stdout,
 		Stderr: ioutil.Discard,
 		Args:   []string{"status"},
+		Debug:  true,
 	}
 	code := Invoke(inv)
 	if code != 0 {
-		t.Errorf("expected to exit with code 0, but got %v", code)
+		t.Errorf("expected to exit with code 0, but got %v\noutput:\n%s\nstderr:\n%s", code, stdout, stderr)
 	}
 	actual := stdout.String()
 	expected := "alias!\n"
@@ -627,14 +648,8 @@ func TestInvalidAlias(t *testing.T) {
 }
 
 func TestClean(t *testing.T) {
-	TestGoRun(t) // make sure we've got something in the CACHE_DIR
-	dir := "./testing"
-	abs, err := filepath.Abs(dir)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	files, err := ioutil.ReadDir(abs)
+	TestAlias(t) // make sure we've got something in the CACHE_DIR
+	files, err := ioutil.ReadDir(mg.CacheDir())
 	if err != nil {
 		t.Error("issue reading file:", err)
 	}
@@ -643,17 +658,19 @@ func TestClean(t *testing.T) {
 		t.Error("Need at least 1 cached binaries to test --clean")
 	}
 
-	buf := &bytes.Buffer{}
-	_, cmd, err := Parse(ioutil.Discard, buf, []string{"-clean"})
+	_, cmd, err := Parse(ioutil.Discard, ioutil.Discard, []string{"-clean"})
+	if err != nil {
+		t.Fatal(err)
+	}
 	if cmd != Clean {
 		t.Errorf("Expected 'clean' command but got %v", cmd)
 	}
-	code := ParseAndRun(dir, ioutil.Discard, ioutil.Discard, buf, []string{"-clean"})
+	code := ParseAndRun(ioutil.Discard, ioutil.Discard, &bytes.Buffer{}, []string{"-clean"})
 	if code != 0 {
 		t.Errorf("expected 0, but got %v", code)
 	}
 
-	files, err = ioutil.ReadDir(abs)
+	files, err = ioutil.ReadDir(mg.CacheDir())
 	if err != nil {
 		t.Error("issue reading file:", err)
 	}
@@ -668,11 +685,7 @@ func TestGoCmd(t *testing.T) {
 	if err := os.Setenv(testExeEnv, textOutput); err != nil {
 		t.Fatal(err)
 	}
-	t.Logf("setting %q as gocmd", os.Args[0])
-	if err := os.Setenv(mg.GoCmdEnv, os.Args[0]); err != nil {
-		t.Fatal(err)
-	}
-	defer os.Unsetenv(mg.GoCmdEnv)
+	defer os.Unsetenv(testExeEnv)
 
 	// fake out the compiled file, since the code checks for it.
 	f, err := ioutil.TempFile("", "")
@@ -686,13 +699,7 @@ func TestGoCmd(t *testing.T) {
 
 	buf := &bytes.Buffer{}
 	stderr := &bytes.Buffer{}
-	inv := Invocation{
-		Dir:    dir,
-		Stderr: stderr,
-		Stdout: buf,
-		Debug:  false,
-	}
-	if err := Compile(inv, name, []string{}); err != nil {
+	if err := Compile(dir, os.Args[0], name, []string{}, false, stderr, buf); err != nil {
 		t.Log("stderr: ", stderr.String())
 		t.Fatal(err)
 	}
diff --git a/mage/testdata/alias/magefile.go b/mage/testdata/alias/magefile.go
index 921002a..d68326d 100644
--- a/mage/testdata/alias/magefile.go
+++ b/mage/testdata/alias/magefile.go
@@ -1,4 +1,4 @@
-// +build mage
+//+build mage
 
 package main
 
diff --git a/mage/testdata/setdir/setdir.go b/mage/testdata/setdir/setdir.go
new file mode 100644
index 0000000..3fc20d8
--- /dev/null
+++ b/mage/testdata/setdir/setdir.go
@@ -0,0 +1,23 @@
+//+build mage
+
+package main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"strings"
+)
+
+func TestCurrentDir() error {
+	files, err := ioutil.ReadDir(".")
+	if err != nil {
+		return err
+	}
+	var out []string
+	for _, f := range files {
+		out = append(out, f.Name())
+	}
+
+	fmt.Println(strings.Join(out, ", "))
+	return nil
+}