Repository: magefile/mage (go)

Issue:
Feature Request : Prefix returned errors with something such as `Error:`
In the video demo your example of `return errors.New("boo!")` just prints out "boo!" and whilst the `echo $?` shows a non-zero exit code, there is nothing on the screen to visually show the command failed.

I know some people have prompts which display something when the previous command failed, however I think it would be nice to show it visually too. Nothing too big, but just big enough.

For example:

```
$ mage install
Error: boo!
```

As a comparison let's just look at what `make` does. We don't have to copy this nor am I holding it up as the ideal, but just a curiosity. For example, if my target just tried to cat a non-existant file:

```
$ make target
cat hello.go
cat: hello.go: No such file or directory
Makefile:2: recipe for target 'target' failed
make: *** [build] Error 1
```

Which (I think) is made up of the command, stderr from the command and then output from make itself. I'm happy to keep it real simple as above and just prefix the output with `Error:`.

(Note: I think we could or should also do the same for functions that don't return errors, so I'm not sure how we'd do that - perhaps if they wrote something on stderr? This comment is a non-goal of this ticket and perhaps another ticket should be opened, however it's pertinent to raise this here in case it impacts what we do here.)


Pull request:
prepend error output with 'Error:'
fixes #27

Patch:
diff --git a/mage/template.go b/mage/template.go
index 3815fdd..1115c0e 100644
--- a/mage/template.go
+++ b/mage/template.go
@@ -46,7 +46,7 @@ func main() {
 	defer func() {
 		err := recover()
 		if err != nil {
-			fmt.Println(err)
+			fmt.Printf("Error: %v\n", err)
 			type code interface { ExitStatus() int }
 			if c, ok := err.(code); ok {
 				os.Exit(c.ExitStatus())
@@ -79,7 +79,7 @@ func main() {
 	case "{{lower .Name}}":
 		{{if .IsError -}}
 			if err := {{.Name}}(); err != nil {
-				fmt.Println(err)
+				fmt.Printf("Error: %v\n", err)
 				os.Exit(1)
 			}
 		{{else -}}


Test patch:
diff --git a/mage/main_test.go b/mage/main_test.go
index ce32430..b2c6a11 100644
--- a/mage/main_test.go
+++ b/mage/main_test.go
@@ -75,6 +75,57 @@ func TestVerbose(t *testing.T) {
 	}
 }
 
+func TestGoRunError(t *testing.T) {
+	c := exec.Command("go", "run", "main.go", "returnsnonnilerror")
+	c.Dir = "./testdata"
+	c.Env = os.Environ()
+	b, err := c.CombinedOutput()
+	actualErr := err.Error()
+	expectedErr := "exit status 1"
+	if actualErr != expectedErr {
+		t.Fatalf("expected %q, but got %q", expectedErr, actualErr)
+	}
+	actual := string(b)
+	expected := "Error: bang!\nexit status 1\n"
+	if actual != expected {
+		t.Fatalf("expected %q, but got %q", expected, actual)
+	}
+}
+
+func TestGoRunPanics(t *testing.T) {
+	c := exec.Command("go", "run", "main.go", "panics")
+	c.Dir = "./testdata"
+	c.Env = os.Environ()
+	b, err := c.CombinedOutput()
+	actualErr := err.Error()
+	expectedErr := "exit status 1"
+	if actualErr != expectedErr {
+		t.Fatalf("expected %q, but got %q", expectedErr, actualErr)
+	}
+	actual := string(b)
+	expected := "Error: boom!\nexit status 1\n"
+	if actual != expected {
+		t.Fatalf("expected %q, but got %q", expected, actual)
+	}
+}
+
+func TestGoRunPanicsErr(t *testing.T) {
+	c := exec.Command("go", "run", "main.go", "panicserr")
+	c.Dir = "./testdata"
+	c.Env = os.Environ()
+	b, err := c.CombinedOutput()
+	actualErr := err.Error()
+	expectedErr := "exit status 1"
+	if actualErr != expectedErr {
+		t.Fatalf("expected %q, but got %q", expectedErr, actualErr)
+	}
+	actual := string(b)
+	expected := "Error: kaboom!\nexit status 1\n"
+	if actual != expected {
+		t.Fatalf("expected %q, but got %q", expected, actual)
+	}
+}
+
 // ensure we include the hash of the mainfile template in determining the
 // executable name to run, so we automatically create a new exe if the template
 // changes.
diff --git a/mage/testdata/error.go b/mage/testdata/error.go
new file mode 100644
index 0000000..bb98a09
--- /dev/null
+++ b/mage/testdata/error.go
@@ -0,0 +1,10 @@
+// +build mage
+
+package main
+
+import "errors"
+
+// Returns a non-nil error.
+func ReturnsNonNilError() error {
+	return errors.New("bang!")
+}
diff --git a/mage/testdata/panic.go b/mage/testdata/panic.go
new file mode 100644
index 0000000..d0761bd
--- /dev/null
+++ b/mage/testdata/panic.go
@@ -0,0 +1,13 @@
+// +build mage
+
+package main
+
+// Function that panics.
+func Panics() {
+	panic("boom!")
+}
+
+// Error function that panics.
+func PanicsErr() error {
+	panic("kaboom!")
+}