Skip to content

Commit

Permalink
tomll: port to v2 (#727)
Browse files Browse the repository at this point in the history
Fixes #721
  • Loading branch information
pelletier authored Dec 31, 2021
1 parent d8ddc00 commit 4807229
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 14 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ In case of trouble: [Go Modules FAQ][mod-faq].

## Tools

Go-toml provides two handy command line tools:
Go-toml provides three handy command line tools:

* `tomljson`: Reads a TOML file and outputs its JSON representation.

Expand All @@ -225,6 +225,13 @@ Go-toml provides two handy command line tools:
$ jsontoml --help
```
* `tomll`: Lints and reformats a TOML file.
```
$ go install github.com/pelletier/go-toml/v2/cmd/tomll@latest
$ tomll --help
```
## Migrating from v1
This section describes the differences between v1 and v2, with some pointers on
Expand Down
11 changes: 8 additions & 3 deletions cmd/jsontoml/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@ import (
"github.com/pelletier/go-toml/v2/internal/cli"
)

func main() {
usage := `jsontoml can be used in two ways:
const usage = `jsontoml can be used in two ways:
Reading from stdin:
cat file.json | jsontoml > file.toml
Reading from a file:
jsontoml file.json > file.toml
`
cli.Execute(usage, convert)

func main() {
p := cli.Program{
Usage: usage,
Fn: convert,
}
p.Execute()
}

func convert(r io.Reader, w io.Writer) error {
Expand Down
11 changes: 8 additions & 3 deletions cmd/tomljson/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@ import (
"github.com/pelletier/go-toml/v2/internal/cli"
)

func main() {
usage := `tomljson can be used in two ways:
const usage = `tomljson can be used in two ways:
Reading from stdin:
cat file.toml | tomljson > file.json
Reading from a file:
tomljson file.toml > file.json
`
cli.Execute(usage, convert)

func main() {
p := cli.Program{
Usage: usage,
Fn: convert,
}
p.Execute()
}

func convert(r io.Reader, w io.Writer) error {
Expand Down
46 changes: 46 additions & 0 deletions cmd/tomll/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Tomll is a linter for TOML
//
// Usage:
// cat file.toml | tomll > file_linted.toml
// tomll file1.toml file2.toml # lint the two files in place
package main

import (
"io"

"github.com/pelletier/go-toml/v2"
"github.com/pelletier/go-toml/v2/internal/cli"
)

const usage = `tomll can be used in two ways:
Reading from stdin, writing to stdout:
cat file.toml | tomll > file.toml
Reading and updating a list of files in place:
tomll a.toml b.toml c.toml
When given a list of files, tomll will modify all files in place without asking.
`

func main() {
p := cli.Program{
Usage: usage,
Fn: convert,
Inplace: true,
}
p.Execute()
}

func convert(r io.Reader, w io.Writer) error {
var v interface{}

d := toml.NewDecoder(r)
err := d.Decode(&v)
if err != nil {
return err
}

e := toml.NewEncoder(w)
return e.Encode(v)
}
46 changes: 46 additions & 0 deletions cmd/tomll/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package main

import (
"bytes"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestConvert(t *testing.T) {
examples := []struct {
name string
input string
expected string
errors bool
}{
{
name: "valid toml",
input: `
mytoml.a = 42.0
`,
expected: `[mytoml]
a = 42.0
`,
},
{
name: "invalid toml",
input: `[what`,
errors: true,
},
}

for _, e := range examples {
b := new(bytes.Buffer)
err := convert(strings.NewReader(e.input), b)
if e.errors {
require.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, e.expected, b.String())
}
}
}
53 changes: 46 additions & 7 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
@@ -1,37 +1,76 @@
package cli

import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
)

type ConvertFn func(r io.Reader, w io.Writer) error

func Execute(usage string, fn ConvertFn) {
flag.Usage = func() { fmt.Fprintf(os.Stderr, usage) }
type Program struct {
Usage string
Fn ConvertFn
// Inplace allows the command to take more than one file as argument and
// perform convertion in place on each provided file.
Inplace bool
}

func (p *Program) Execute() {
flag.Usage = func() { fmt.Fprintf(os.Stderr, p.Usage) }
flag.Parse()
os.Exit(processMain(flag.Args(), os.Stdin, os.Stdout, os.Stderr, fn))
os.Exit(p.main(flag.Args(), os.Stdin, os.Stdout, os.Stderr))
}

func processMain(files []string, input io.Reader, output, error io.Writer, f ConvertFn) int {
err := run(files, input, output, f)
func (p *Program) main(files []string, input io.Reader, output, error io.Writer) int {
err := p.run(files, input, output)
if err != nil {
fmt.Fprintln(error, err.Error())
return -1
}
return 0
}

func run(files []string, input io.Reader, output io.Writer, convert ConvertFn) error {
func (p *Program) run(files []string, input io.Reader, output io.Writer) error {
if len(files) > 0 {
if p.Inplace {
return p.runAllFilesInPlace(files)
}
f, err := os.Open(files[0])
if err != nil {
return err
}
defer f.Close()
input = f
}
return convert(input, output)
return p.Fn(input, output)
}

func (p *Program) runAllFilesInPlace(files []string) error {
for _, path := range files {
err := p.runFileInPlace(path)
if err != nil {
return err
}
}
return nil
}

func (p *Program) runFileInPlace(path string) error {
in, err := ioutil.ReadFile(path)
if err != nil {
return err
}

out := new(bytes.Buffer)

err = p.Fn(bytes.NewReader(in), out)
if err != nil {
return err
}

return ioutil.WriteFile(path, out.Bytes(), 0600)
}
82 changes: 82 additions & 0 deletions internal/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ import (
"io"
"io/ioutil"
"os"
"path"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func processMain(args []string, input io.Reader, stdout, stderr io.Writer, f ConvertFn) int {
p := Program{Fn: f}
return p.main(args, input, stdout, stderr)
}

func TestProcessMainStdin(t *testing.T) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
Expand Down Expand Up @@ -72,3 +78,79 @@ func TestProcessMainFileDoesNotExist(t *testing.T) {
assert.Empty(t, stdout.String())
assert.NotEmpty(t, stderr.String())
}

func TestProcessMainFilesInPlace(t *testing.T) {
dir, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(dir)

path1 := path.Join(dir, "file1")
path2 := path.Join(dir, "file2")

err = ioutil.WriteFile(path1, []byte("content 1"), 0600)
require.NoError(t, err)
err = ioutil.WriteFile(path2, []byte("content 2"), 0600)
require.NoError(t, err)

p := Program{
Fn: dummyFileFn,
Inplace: true,
}

exit := p.main([]string{path1, path2}, os.Stdin, os.Stdout, os.Stderr)

require.Equal(t, 0, exit)

v1, err := ioutil.ReadFile(path1)
require.NoError(t, err)
require.Equal(t, "1", string(v1))

v2, err := ioutil.ReadFile(path2)
require.NoError(t, err)
require.Equal(t, "2", string(v2))
}

func TestProcessMainFilesInPlaceErrRead(t *testing.T) {
p := Program{
Fn: dummyFileFn,
Inplace: true,
}

exit := p.main([]string{"/this/path/is/invalid"}, os.Stdin, os.Stdout, os.Stderr)

require.Equal(t, -1, exit)
}

func TestProcessMainFilesInPlaceFailFn(t *testing.T) {
dir, err := ioutil.TempDir("", "")
require.NoError(t, err)
defer os.RemoveAll(dir)

path1 := path.Join(dir, "file1")

err = ioutil.WriteFile(path1, []byte("content 1"), 0600)
require.NoError(t, err)

p := Program{
Fn: func(io.Reader, io.Writer) error { return fmt.Errorf("oh no") },
Inplace: true,
}

exit := p.main([]string{path1}, os.Stdin, os.Stdout, os.Stderr)

require.Equal(t, -1, exit)

v1, err := ioutil.ReadFile(path1)
require.NoError(t, err)
require.Equal(t, "content 1", string(v1))
}

func dummyFileFn(r io.Reader, w io.Writer) error {
b, err := ioutil.ReadAll(r)
if err != nil {
return err
}
v := strings.SplitN(string(b), " ", 2)[1]
_, err = w.Write([]byte(v))
return err
}

0 comments on commit 4807229

Please sign in to comment.