1
0

format command line command

This commit is contained in:
Arpad Ryszka 2026-06-02 00:04:03 +02:00
parent 76ebeb0948
commit 9d65878302
8 changed files with 315 additions and 2 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.coverprofile .coverprofile
.coverprofile-cmd .coverprofile-cmd
.build .build
treerack.test

View File

@ -6,7 +6,7 @@ sources = $(shell find . -name '*.go' \
| grep -v .build/headexported.go \ | grep -v .build/headexported.go \
| grep -v internal/self/self.go \ | grep -v internal/self/self.go \
| grep -v .build/self.go) | grep -v .build/self.go)
parsers = $(shell find . -name '*.treerack') parsers = $(shell find . -name '*.treerack' | grep -v baz_test[.]treerack)
release_date = $(shell git show -s --format=%cs HEAD) release_date = $(shell git show -s --format=%cs HEAD)
version = $(release_date)-$(shell git rev-parse --short HEAD) version = $(release_date)-$(shell git rev-parse --short HEAD)
PREFIX ?= /usr/local PREFIX ?= /usr/local

View File

@ -0,0 +1,2 @@
# invalid comment
a = "42";

View File

@ -21,6 +21,16 @@ func init() {
docreflect.Register("main.errInvalidFilename", "") docreflect.Register("main.errInvalidFilename", "")
docreflect.Register("main.errMultipleInputs", "") docreflect.Register("main.errMultipleInputs", "")
docreflect.Register("main.errNoInput", "") docreflect.Register("main.errNoInput", "")
docreflect.Register("main.format", "format input syntax. Accepts syntax from one or more files, inline syntax string or stdin. Use the --in-place\noption, when formatting files in place, or print the formatted syntax to stdout.\n\nfunc(o, stdin, stdout, syntax)")
docreflect.Register("main.formatFile", "\nfunc(name, inPlace, out)")
docreflect.Register("main.formatFiles", "\nfunc(files, inPlace, out)")
docreflect.Register("main.formatInline", "\nfunc(syntax, out)")
docreflect.Register("main.formatOptions", "")
docreflect.Register("main.formatOptions.InPlace", "")
docreflect.Register("main.formatOptions.Syntax", "")
docreflect.Register("main.formatOptions.SyntaxString", "")
docreflect.Register("main.formatStdin", "\nfunc(in, out)")
docreflect.Register("main.formatSyntax", "\nfunc(in, out)")
docreflect.Register("main.generate", "generate generates Go code that can parse arbitrary input with the provided syntax, and can be used embedded\nin an application.\n\nThe syntax may be provided via a file path (using an option or a positional argument), an\ninline string, or piped from standard input.\n\nfunc(o, stdin, stdout, args)") docreflect.Register("main.generate", "generate generates Go code that can parse arbitrary input with the provided syntax, and can be used embedded\nin an application.\n\nThe syntax may be provided via a file path (using an option or a positional argument), an\ninline string, or piped from standard input.\n\nfunc(o, stdin, stdout, args)")
docreflect.Register("main.generateOptions", "") docreflect.Register("main.generateOptions", "")
docreflect.Register("main.generateOptions.Export", "Export determines whether the generated parse function is exported (visible outside its package).\n") docreflect.Register("main.generateOptions.Export", "Export determines whether the generated parse function is exported (visible outside its package).\n")

116
cmd/treerack/format.go Normal file
View File

@ -0,0 +1,116 @@
package main
import (
"bytes"
"code.squareroundforest.org/arpio/treerack"
"errors"
"fmt"
"io"
"os"
)
type formatOptions struct {
InPlace bool
SyntaxString *string
Syntax []string
}
func formatSyntax(in io.Reader, out io.Writer) error {
s := new(treerack.Syntax)
if err := s.ReadSyntax(in); err != nil {
return err
}
if resetter, ok := out.(interface{ Reset() }); ok {
resetter.Reset()
}
if err := s.Format(out); err != nil {
return err
}
return nil
}
func formatFile(name string, inPlace bool, out io.Writer) error {
var (
inBytes []byte
buf *bytes.Buffer
err error
)
if inBytes, err = os.ReadFile(name); err != nil {
return err
}
buf = bytes.NewBuffer(inBytes)
if err = formatSyntax(buf, buf); err != nil {
return err
}
if !inPlace {
_, err = io.Copy(out, buf)
return err
}
if bytes.Equal(buf.Bytes(), inBytes) {
return nil
}
fmt.Fprintln(os.Stderr, name)
return os.WriteFile(name, buf.Bytes(), 0644)
}
func formatFiles(files []string, inPlace bool, out io.Writer) error {
for _, f := range files {
if err := formatFile(f, inPlace, out); err != nil {
return err
}
}
return nil
}
func formatInline(syntax string, out io.Writer) error {
buf := bytes.NewBufferString(syntax)
return formatSyntax(buf, out)
}
func formatStdin(in io.Reader, out io.Writer) error {
return formatSyntax(in, out)
}
// format input syntax. Accepts syntax from one or more files, inline syntax string or stdin. Use the --in-place
// option, when formatting files in place, or print the formatted syntax to stdout.
func format(o formatOptions, stdin io.Reader, stdout io.Writer, syntax ...string) error {
files := make([]string, 0, len(o.Syntax)+len(syntax))
files = append(files, o.Syntax...)
files = append(files, syntax...)
if o.SyntaxString != nil {
if len(files) > 0 {
return errors.New(
"accepted input: either inline syntax, or one or more syntax files, or stdin",
)
}
}
if o.InPlace {
if o.SyntaxString != nil {
return errors.New("cannot format inline syntax in place")
}
if len(files) == 0 {
return errors.New("cannot format stdin in place")
}
}
if len(files) > 0 {
return formatFiles(files, o.InPlace, stdout)
}
if o.SyntaxString != nil {
return formatInline(*o.SyntaxString, stdout)
}
return formatSyntax(stdin, stdout)
}

162
cmd/treerack/format_test.go Normal file
View File

@ -0,0 +1,162 @@
package main
import (
"bytes"
"os"
"testing"
)
func TestFormat(t *testing.T) {
t.Run("syntax string and file from option", func(t *testing.T) {
o := formatOptions{
SyntaxString: ptrto(`a = "42"`),
Syntax: []string{"foo_test.treerack"},
}
if err := format(o, bytes.NewBuffer(nil), bytes.NewBuffer(nil)); err == nil {
t.Fatal("failed to fail")
}
})
t.Run("syntax string and file from arg", func(t *testing.T) {
o := formatOptions{SyntaxString: ptrto(`a = "42"`)}
if err := format(o, bytes.NewBuffer(nil), bytes.NewBuffer(nil), "foo_test.treerack"); err == nil {
t.Fatal("failed to fail")
}
})
t.Run("syntax string in place", func(t *testing.T) {
o := formatOptions{
SyntaxString: ptrto(`a = "42"`),
InPlace: true,
}
if err := format(o, bytes.NewBuffer(nil), bytes.NewBuffer(nil)); err == nil {
t.Fatal("failed to fail")
}
})
t.Run("stdin in place", func(t *testing.T) {
o := formatOptions{InPlace: true}
if err := format(o, bytes.NewBuffer(nil), bytes.NewBuffer(nil)); err == nil {
t.Fatal("failed to fail")
}
})
t.Run("files:", func(t *testing.T) {
t.Run("read error", func(t *testing.T) {
if err := format(
formatOptions{},
bytes.NewBuffer(nil),
bytes.NewBuffer(nil),
"bar_test.treerack",
); err == nil {
t.Fatal("failed to fail")
}
})
t.Run("syntax error", func(t *testing.T) {
if err := format(
formatOptions{},
bytes.NewBuffer(nil),
bytes.NewBuffer(nil),
"baz_test.treerack",
); err == nil {
t.Fatal("failed to fail")
}
})
t.Run("to stdout", func(t *testing.T) {
stdout := bytes.NewBuffer(nil)
if err := format(
formatOptions{},
bytes.NewBuffer(nil),
stdout,
"foo_test.treerack",
); err != nil {
t.Fatal("failed to fail")
}
if stdout.Len() == 0 {
t.Fatal("failed to write out contents")
}
})
t.Run("in place no change", func(t *testing.T) {
c, err := os.ReadFile("foo_test.treerack")
if err != nil {
t.Fatal(err)
}
o := formatOptions{InPlace: true}
if err := format(
o,
bytes.NewBuffer(nil),
bytes.NewBuffer(nil),
"foo_test.treerack",
); err != nil {
t.Fatal("failed to fail")
}
cc, err := os.ReadFile("foo_test.treerack")
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(cc, c) {
t.Fatal("failed to leave the file intact")
}
})
t.Run("in place", func(t *testing.T) {
c, err := os.ReadFile("foo_test.treerack")
if err != nil {
t.Fatal(err)
}
cc := c[:len(c)-1]
if err := os.WriteFile("foo_test.treerack", cc, 0644); err != nil {
t.Fatal(err)
}
o := formatOptions{InPlace: true}
if err := format(
o,
bytes.NewBuffer(nil),
bytes.NewBuffer(nil),
"foo_test.treerack",
); err != nil {
t.Fatal("failed to fail")
}
ccc, err := os.ReadFile("foo_test.treerack")
if !bytes.Equal(ccc, c) {
t.Fatal("failed to format file")
}
})
})
t.Run("syntax string", func(t *testing.T) {
o := formatOptions{SyntaxString: ptrto(`a="42"`)}
stdout := bytes.NewBuffer(nil)
if err := format(o, bytes.NewBuffer(nil), stdout); err != nil {
t.Fatal(err)
}
if stdout.String() != `a = "42";`+"\n" {
t.Fatal("failed to format syntax string")
}
})
t.Run("stdin", func(t *testing.T) {
stdin := bytes.NewBufferString(`a="42"`)
stdout := bytes.NewBuffer(nil)
if err := format(formatOptions{}, stdin, stdout); err != nil {
t.Fatal(err)
}
if stdout.String() != `a = "42";`+"\n" {
t.Fatal("failed to format input")
}
})
}

View File

@ -9,5 +9,6 @@ func main() {
check := Args(Command("check", check), 0, 1) check := Args(Command("check", check), 0, 1)
show := Args(Command("show", show), 0, 1) show := Args(Command("show", show), 0, 1)
generate := Args(Command("generate", generate), 0, 1) generate := Args(Command("generate", generate), 0, 1)
Exec(Version(Group("treerack", checkSyntax, check, show, generate), version)) format := Command("format", format)
Exec(Version(Group("treerack", checkSyntax, check, show, generate, format), version))
} }

View File

@ -115,6 +115,27 @@ piped from standard input.
- --syntax-string string: specifies the syntax as an inline string. - --syntax-string string: specifies the syntax as an inline string.
- --help: Show help. - --help: Show help.
### treerack format
#### Synopsis:
```
treerack format [options]... [--] [syntax string]...
treerack format <subcommand>
```
#### Description:
input syntax. Accepts syntax from one or more files, inline syntax string or stdin. Use the --in-place option,
when formatting files in place, or print the formatted syntax to stdout.
#### Options:
- --in-place bool:
- --syntax string \[\*\]:
- --syntax-string string:
- --help: Show help.
### treerack version ### treerack version
Show version. Show version.