TOML to JSON cli tool (#85)

* Implement tomljson
* Add note about tools in README
diff --git a/README.md b/README.md
index fa6d55e..162273d 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,23 @@
 The documentation and additional examples are available at
 [godoc.org](http://godoc.org/github.com/pelletier/go-toml).
 
+## Tools
+
+Go-toml provides two handy command line tools:
+
+* `tomll`: Reads TOML files and lint them.
+
+    ```
+    go install github.com/pelletier/go-toml/cmd/tomll
+    tomll --help
+    ```
+* `tomljson`: Reads a TOML file and outputs its JSON representation.
+
+    ```
+    go install github.com/pelletier/go-toml/cmd/tomjson
+    tomljson --help
+    ```
+
 ## Contribute
 
 Feel free to report bugs and patches using GitHub's pull requests system on
diff --git a/cmd/tomljson/main.go b/cmd/tomljson/main.go
new file mode 100644
index 0000000..809f688
--- /dev/null
+++ b/cmd/tomljson/main.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+	"github.com/pelletier/go-toml"
+	"io"
+	"os"
+	"flag"
+	"fmt"
+	"encoding/json"
+)
+
+func main() {
+	flag.Usage = func() {
+		fmt.Fprintln(os.Stderr, `tomljson can be used in two ways:
+Writing to STDIN and reading from STDOUT:
+  cat file.toml | tomljson > file.json
+
+Reading from a file name:
+  tomljson file.toml
+`)
+	}
+	flag.Parse()
+	os.Exit(processMain(flag.Args(), os.Stdin, os.Stdout, os.Stderr))
+}
+
+func processMain(files []string, defaultInput io.Reader, output io.Writer, errorOutput io.Writer) int {
+	// read from stdin and print to stdout
+	inputReader := defaultInput
+
+	if len(files) > 0 {
+		var err error
+		inputReader, err = os.Open(files[0])
+		if err != nil {
+			printError(err, errorOutput)
+			return -1
+		}
+	}
+	s, err := reader(inputReader)
+	if err != nil {
+		printError(err, errorOutput)
+		return -1
+	}
+	io.WriteString(output, s + "\n")
+	return 0
+}
+
+func printError(err error, output io.Writer) {
+	io.WriteString(output, err.Error() + "\n")
+}
+
+func reader(r io.Reader) (string, error) {
+	tree, err := toml.LoadReader(r)
+	if err != nil {
+		return "", err
+	}
+	return mapToJson(tree)
+}
+
+func mapToJson(tree *toml.TomlTree) (string, error) {
+	treeMap := tree.ToMap()
+	bytes, err := json.MarshalIndent(treeMap, "", "  ")
+	if err != nil {
+		return "", err
+	}
+	return string(bytes[:]), nil
+}
\ No newline at end of file
diff --git a/cmd/tomljson/main_test.go b/cmd/tomljson/main_test.go
new file mode 100644
index 0000000..6260b2b
--- /dev/null
+++ b/cmd/tomljson/main_test.go
@@ -0,0 +1,84 @@
+package main
+
+import (
+	"testing"
+	"strings"
+	"bytes"
+	"os"
+	"io/ioutil"
+)
+
+func expectBufferEquality(t *testing.T, name string, buffer *bytes.Buffer, expected string) {
+	output := buffer.String()
+	if output != expected {
+		t.Errorf("incorrect %s:\n%s\n\nexpected %s:\n%s", name, output, name, expected)
+		t.Log([]rune(output))
+		t.Log([]rune(expected))
+	}
+}
+
+func expectProcessMainResults(t *testing.T, input string, args []string, exitCode int, expectedOutput string, expectedError string) {
+	inputReader := strings.NewReader(input)
+	outputBuffer := new(bytes.Buffer)
+	errorBuffer := new(bytes.Buffer)
+
+	returnCode := processMain(args, inputReader, outputBuffer, errorBuffer)
+
+	expectBufferEquality(t, "output", outputBuffer, expectedOutput)
+	expectBufferEquality(t, "error", errorBuffer, expectedError)
+
+	if returnCode != exitCode {
+		t.Error("incorrect return code:", returnCode, "expected", exitCode)
+	}
+}
+
+
+func TestProcessMainReadFromStdin(t *testing.T) {
+	input := `
+		[mytoml]
+		a = 42`
+	expectedOutput := `{
+  "mytoml": {
+    "a": 42
+  }
+}
+`
+	expectedError := ``
+	expectedExitCode := 0
+
+	expectProcessMainResults(t, input, []string{}, expectedExitCode, expectedOutput, expectedError)
+}
+
+func TestProcessMainReadFromFile(t *testing.T) {
+	input := `
+		[mytoml]
+		a = 42`
+
+
+	tmpfile, err := ioutil.TempFile("", "example.toml")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, err := tmpfile.Write([]byte(input)); err != nil {
+		t.Fatal(err)
+	}
+
+	defer os.Remove(tmpfile.Name())
+
+	expectedOutput := `{
+  "mytoml": {
+    "a": 42
+  }
+}
+`
+	expectedError := ``
+	expectedExitCode := 0
+
+	expectProcessMainResults(t, ``, []string{tmpfile.Name()}, expectedExitCode, expectedOutput, expectedError)
+}
+
+func TestProcessMainReadFromMissingFile(t *testing.T) {
+	expectedError := `open /this/file/does/not/exist: no such file or directory
+`
+	expectProcessMainResults(t, ``, []string{"/this/file/does/not/exist"}, -1, ``, expectedError)
+}
\ No newline at end of file
diff --git a/test.sh b/test.sh
index 33ae6df..15ac1e1 100755
--- a/test.sh
+++ b/test.sh
@@ -34,11 +34,12 @@
 # NOTE: this basically mocks an install without having to go back out to github for code
 mkdir -p src/github.com/pelletier/go-toml/cmd
 cp *.go *.toml src/github.com/pelletier/go-toml
-cp cmd/*.go src/github.com/pelletier/go-toml/cmd
+cp -R cmd/* src/github.com/pelletier/go-toml/cmd
 go build -o test_program_bin src/github.com/pelletier/go-toml/cmd/test_program.go
 
 # Run basic unit tests
-go test -v github.com/pelletier/go-toml
+go test github.com/pelletier/go-toml \
+        github.com/pelletier/go-toml/cmd/tomljson
 
 # run the entire BurntSushi test suite
 if [[ $# -eq 0 ]] ; then