@@ -209,7 +209,7 @@ In case of trouble: [Go Modules FAQ][mod-faq].
|
|||||||
|
|
||||||
## Tools
|
## 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.
|
* `tomljson`: Reads a TOML file and outputs its JSON representation.
|
||||||
|
|
||||||
@@ -225,6 +225,13 @@ Go-toml provides two handy command line tools:
|
|||||||
$ jsontoml --help
|
$ 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
|
## Migrating from v1
|
||||||
|
|
||||||
This section describes the differences between v1 and v2, with some pointers on
|
This section describes the differences between v1 and v2, with some pointers on
|
||||||
|
|||||||
@@ -13,15 +13,20 @@ import (
|
|||||||
"github.com/pelletier/go-toml/v2/internal/cli"
|
"github.com/pelletier/go-toml/v2/internal/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
const usage = `jsontoml can be used in two ways:
|
||||||
usage := `jsontoml can be used in two ways:
|
|
||||||
Reading from stdin:
|
Reading from stdin:
|
||||||
cat file.json | jsontoml > file.toml
|
cat file.json | jsontoml > file.toml
|
||||||
|
|
||||||
Reading from a file:
|
Reading from a file:
|
||||||
jsontoml file.json > file.toml
|
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 {
|
func convert(r io.Reader, w io.Writer) error {
|
||||||
|
|||||||
@@ -15,15 +15,20 @@ import (
|
|||||||
"github.com/pelletier/go-toml/v2/internal/cli"
|
"github.com/pelletier/go-toml/v2/internal/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
const usage = `tomljson can be used in two ways:
|
||||||
usage := `tomljson can be used in two ways:
|
|
||||||
Reading from stdin:
|
Reading from stdin:
|
||||||
cat file.toml | tomljson > file.json
|
cat file.toml | tomljson > file.json
|
||||||
|
|
||||||
Reading from a file:
|
Reading from a file:
|
||||||
tomljson file.toml > file.json
|
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 {
|
func convert(r io.Reader, w io.Writer) error {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
-8
@@ -1,22 +1,32 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConvertFn func(r io.Reader, w io.Writer) error
|
type ConvertFn func(r io.Reader, w io.Writer) error
|
||||||
|
|
||||||
func Execute(usage string, fn ConvertFn) {
|
type Program struct {
|
||||||
flag.Usage = func() { fmt.Fprintf(os.Stderr, usage) }
|
Usage string
|
||||||
flag.Parse()
|
Fn ConvertFn
|
||||||
os.Exit(processMain(flag.Args(), os.Stdin, os.Stdout, os.Stderr, fn))
|
// Inplace allows the command to take more than one file as argument and
|
||||||
|
// perform convertion in place on each provided file.
|
||||||
|
Inplace bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func processMain(files []string, input io.Reader, output, error io.Writer, f ConvertFn) int {
|
func (p *Program) Execute() {
|
||||||
err := run(files, input, output, f)
|
flag.Usage = func() { fmt.Fprintf(os.Stderr, p.Usage) }
|
||||||
|
flag.Parse()
|
||||||
|
os.Exit(p.main(flag.Args(), os.Stdin, os.Stdout, os.Stderr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Program) main(files []string, input io.Reader, output, error io.Writer) int {
|
||||||
|
err := p.run(files, input, output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(error, err.Error())
|
fmt.Fprintln(error, err.Error())
|
||||||
return -1
|
return -1
|
||||||
@@ -24,8 +34,11 @@ func processMain(files []string, input io.Reader, output, error io.Writer, f Con
|
|||||||
return 0
|
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 len(files) > 0 {
|
||||||
|
if p.Inplace {
|
||||||
|
return p.runAllFilesInPlace(files)
|
||||||
|
}
|
||||||
f, err := os.Open(files[0])
|
f, err := os.Open(files[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -33,5 +46,31 @@ func run(files []string, input io.Reader, output io.Writer, convert ConvertFn) e
|
|||||||
defer f.Close()
|
defer f.Close()
|
||||||
input = f
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -13,6 +14,11 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"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) {
|
func TestProcessMainStdin(t *testing.T) {
|
||||||
stdout := new(bytes.Buffer)
|
stdout := new(bytes.Buffer)
|
||||||
stderr := new(bytes.Buffer)
|
stderr := new(bytes.Buffer)
|
||||||
@@ -72,3 +78,79 @@ func TestProcessMainFileDoesNotExist(t *testing.T) {
|
|||||||
assert.Empty(t, stdout.String())
|
assert.Empty(t, stdout.String())
|
||||||
assert.NotEmpty(t, stderr.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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user