From 4d5afd743f6d97abcf808b942735e5596f2bbd6c Mon Sep 17 00:00:00 2001 From: Gaurav Dhameeja Date: Fri, 6 Sep 2019 22:06:56 +0530 Subject: [PATCH] jsontoml tool (#296) `jsontoml` is very similar to `tomljson` It uses json.Unmarshal to convert read json to map and then converts the map to tree using `toml.TreeFromMap`. Then this tree is converted to toml using `tree.toTomlString()` The numbers when taken as input from json get converted to float64 because of how `json.Unmarshal()` converts all json numbers to float. Fixes #280 --- .circleci/config.yml | 4 ++ .travis.yml | 1 + Dockerfile | 1 + README.md | 7 +++ appveyor.yml | 1 + cmd/jsontoml/main.go | 81 ++++++++++++++++++++++++++++++++++ cmd/jsontoml/main_test.go | 93 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 188 insertions(+) create mode 100644 cmd/jsontoml/main.go create mode 100644 cmd/jsontoml/main_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c13c8e..c39aff4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,6 +66,10 @@ jobs: test_name: "tomljson" module: "github.com/pelletier/go-toml/cmd/tomljson" allow_fail: <> + - run_test: + test_name: "jsontoml" + module: "github.com/pelletier/go-toml/cmd/jsontoml" + allow_fail: <> - run_test: test_name: "tomll" module: "github.com/pelletier/go-toml/cmd/tomll" diff --git a/.travis.yml b/.travis.yml index 230c5b8..ae0f7ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ script: - if [ -n "$(go fmt ./...)" ]; then exit 1; fi - go test github.com/pelletier/go-toml -race -coverprofile=coverage.txt -covermode=atomic - go test github.com/pelletier/go-toml/cmd/tomljson + - go test github.com/pelletier/go-toml/cmd/jsontoml - go test github.com/pelletier/go-toml/cmd/tomll - go test github.com/pelletier/go-toml/query - ./benchmark.sh $TRAVIS_BRANCH https://github.com/$TRAVIS_REPO_SLUG.git diff --git a/Dockerfile b/Dockerfile index 8f439d4..fffdb01 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,3 +8,4 @@ RUN go install ./... FROM scratch COPY --from=builder /go/bin/tomll /usr/bin/tomll COPY --from=builder /go/bin/tomljson /usr/bin/tomljson +COPY --from=builder /go/bin/jsontoml /usr/bin/jsontoml diff --git a/README.md b/README.md index f0311b9..2cc02a2 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,13 @@ Go-toml provides two handy command line tools: go install github.com/pelletier/go-toml/cmd/tomljson tomljson --help ``` + + * `jsontoml`: Reads a JSON file and outputs a TOML representation. + + ``` + go install github.com/pelletier/go-toml/cmd/jsontoml + jsontoml --help + ``` ### Docker image diff --git a/appveyor.yml b/appveyor.yml index 40e8a41..d158ba5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,5 +30,6 @@ deploy: false test_script: - go test github.com/pelletier/go-toml - go test github.com/pelletier/go-toml/cmd/tomljson + - go test github.com/pelletier/go-toml/cmd/jsontoml - go test github.com/pelletier/go-toml/cmd/tomll - go test github.com/pelletier/go-toml/query diff --git a/cmd/jsontoml/main.go b/cmd/jsontoml/main.go new file mode 100644 index 0000000..153ad16 --- /dev/null +++ b/cmd/jsontoml/main.go @@ -0,0 +1,81 @@ +// Jsontoml reads JSON and converts to TOML. +// +// Usage: +// cat file.toml | jsontoml > file.json +// jsontoml file1.toml > file.json +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/pelletier/go-toml" +) + +func main() { + flag.Usage = func() { + fmt.Fprintln(os.Stderr, "jsontoml can be used in two ways:") + fmt.Fprintln(os.Stderr, "Writing to STDIN and reading from STDOUT:") + fmt.Fprintln(os.Stderr, "") + fmt.Fprintln(os.Stderr, "") + fmt.Fprintln(os.Stderr, "Reading from a file name:") + fmt.Fprintln(os.Stderr, " 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 { + file, err := os.Open(files[0]) + if err != nil { + printError(err, errorOutput) + } + inputReader = file + defer file.Close() + } + s, err := reader(inputReader) + if err != nil { + printError(err, errorOutput) + return -1 + } + io.WriteString(output, s) + return 0 +} + +func printError(err error, output io.Writer) { + io.WriteString(output, err.Error()+"\n") +} + +func reader(r io.Reader) (string, error) { + jsonMap := make(map[string]interface{}) + jsonBytes, err := ioutil.ReadAll(r) + if err != nil { + return "", err + } + error := json.Unmarshal(jsonBytes, &jsonMap) + if error != nil { + return "", error + } + + tree, err := toml.TreeFromMap(jsonMap) + if err != nil { + return "", err + } + return mapToTOML(tree) +} + +func mapToTOML(t *toml.Tree) (string, error) { + tomlBytes, err := t.ToTomlString() + if err != nil { + return "", err + } + return string(tomlBytes[:]), nil +} diff --git a/cmd/jsontoml/main_test.go b/cmd/jsontoml/main_test.go new file mode 100644 index 0000000..d5229de --- /dev/null +++ b/cmd/jsontoml/main_test.go @@ -0,0 +1,93 @@ +package main + +import ( + "bytes" + "io/ioutil" + "os" + "runtime" + "strings" + "testing" +) + +func expectBufferEquality(t *testing.T, name string, buffer *bytes.Buffer, expected string) { + output := buffer.String() + if output != expected { + t.Errorf("incorrect %s: \n%sexpected %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) { + expectedOutput := ` +[mytoml] + a = 42.0 +` + input := `{ + "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.json") + 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.0 +` + expectedError := `` + expectedExitCode := 0 + + expectProcessMainResults(t, ``, []string{tmpfile.Name()}, expectedExitCode, expectedOutput, expectedError) +} + +func TestProcessMainReadFromMissingFile(t *testing.T) { + var expectedError string + if runtime.GOOS == "windows" { + expectedError = `open /this/file/does/not/exist: The system cannot find the path specified. +` + } else { + expectedError = `open /this/file/does/not/exist: no such file or directory +invalid argument +` + } + + expectProcessMainResults(t, ``, []string{"/this/file/does/not/exist"}, -1, ``, expectedError) +}