use wand for the command line tool
This commit is contained in:
parent
b2a2974aa2
commit
b4086951ee
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,4 +3,4 @@
|
||||
.coverprofile
|
||||
.coverprofile-cmd
|
||||
codecov
|
||||
cmd/treerack/treerack
|
||||
.build
|
||||
|
||||
30
Makefile
30
Makefile
@ -1,5 +1,7 @@
|
||||
sources = $(shell find . -name '*.go')
|
||||
sources = $(shell find . -name '*.go' | grep -v cmd/treerack/docreflect.gen.go)
|
||||
parsers = $(shell find . -name '*.treerack')
|
||||
release_date = $(shell git show -s --format=%cs HEAD)
|
||||
version = $(date)-$(shell git rev-parse --short HEAD)
|
||||
|
||||
.PHONY: cpu.out
|
||||
|
||||
@ -13,9 +15,16 @@ imports: $(sources)
|
||||
@echo imports
|
||||
@goimports -w $(sources)
|
||||
|
||||
build: $(sources)
|
||||
cmd/treerack/docreflect.gen.go: $(sources)
|
||||
go run scripts/docreflect.go > .build/docreflect.gen.go && \
|
||||
mv .build/docreflect.gen.go cmd/treerack || \
|
||||
rm .build/docreflect.gen.go
|
||||
|
||||
build: $(sources) cmd/treerack/readme.md cmd/treerack/docreflect.gen.go .build/treerack .build/treerack.1
|
||||
|
||||
.build/treerack:
|
||||
go build
|
||||
go build -o cmd/treerack/treerack ./cmd/treerack
|
||||
go build -o .build/treerack ./cmd/treerack
|
||||
|
||||
install: $(sources)
|
||||
go install ./cmd/treerack
|
||||
@ -91,6 +100,17 @@ check-generate: $(sources) $(parsers)
|
||||
@mv headexported.go.backup headexported.go
|
||||
@mv self/self.go.backup self/self.go
|
||||
|
||||
.build:
|
||||
mkdir -p .build
|
||||
|
||||
cmd/treerack/readme.md: $(sources) cmd/treerack/docreflect.gen.go
|
||||
go run scripts/cmdreadme.go ./cmd/treerack > cmd/treerack/readme.md || \
|
||||
rm cmd/treerack/readme.md
|
||||
|
||||
.build/treerack.1: $(sources) cmd/treerack/docreflect.gen.go
|
||||
go run scripts/man.go $(version) $(release_date) > .build/treerack.1 || \
|
||||
rm .build/treerack.1
|
||||
|
||||
check: build $(parsers)
|
||||
go test -test.short -run ^Test
|
||||
go test ./cmd/treerack -test.short -run ^Test
|
||||
@ -144,6 +164,10 @@ clean:
|
||||
rm -f cpu.out
|
||||
rm -f .coverprofile
|
||||
go clean -i ./...
|
||||
rm -f .build/treerack
|
||||
rm -f .build/treerack.1
|
||||
rm -f cmd/treerack/docreflect.gen.go
|
||||
rm -f cmd/treerack/readme.md
|
||||
|
||||
ci-trigger: deps checkfmt build checkall
|
||||
ifeq ($(TRAVIS_BRANCH)_$(TRAVIS_PULL_REQUEST), master_false)
|
||||
|
||||
@ -1,49 +1,50 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.squareroundforest.org/arpio/treerack"
|
||||
"io"
|
||||
)
|
||||
|
||||
type checkOptions struct {
|
||||
command *commandOptions
|
||||
syntax *fileOptions
|
||||
input *fileOptions
|
||||
|
||||
// Syntax specifies the filename of the syntax definition file.
|
||||
Syntax string
|
||||
|
||||
// SyntaxString specifies the syntax as an inline string.
|
||||
SyntaxString string
|
||||
|
||||
// Input specifies the filename of the input content to be validated.
|
||||
Input string
|
||||
|
||||
// InputString specifies the input content as an inline string.
|
||||
InputString string
|
||||
}
|
||||
|
||||
func check(args []string) int {
|
||||
var o checkOptions
|
||||
o.command = initOptions(checkUsage, checkExample, positionalInputUsage, args)
|
||||
o.syntax = &fileOptions{typ: "syntax", flagSet: o.command.flagSet, positionalDoc: positionalInputUsage}
|
||||
o.input = &fileOptions{typ: "input", flagSet: o.command.flagSet, positionalDoc: positionalInputUsage}
|
||||
|
||||
o.command.stringFlag(&o.syntax.inline, "syntax-string", syntaxStringUsage)
|
||||
o.command.stringFlag(&o.syntax.fileName, "syntax", syntaxFileUsage)
|
||||
|
||||
o.command.stringFlag(&o.input.inline, "input-string", inputStringUsage)
|
||||
o.command.stringFlag(&o.input.fileName, "input", inputFileUsage)
|
||||
|
||||
if o.command.help() {
|
||||
return 0
|
||||
}
|
||||
|
||||
if code := o.command.parseArgs(); code != 0 {
|
||||
return code
|
||||
}
|
||||
|
||||
s, code := o.syntax.openSyntax()
|
||||
if code != 0 {
|
||||
return code
|
||||
}
|
||||
|
||||
o.input.positional = o.command.flagSet.Args()
|
||||
input, code := o.input.open()
|
||||
if code != 0 {
|
||||
return code
|
||||
}
|
||||
|
||||
defer input.Close()
|
||||
|
||||
_, err := s.Parse(input)
|
||||
// check parses input content against the provided syntax definition and fails if the input does not match.
|
||||
// Syntax can be provided via a filename option or an inline string option. Input can be provided via a filename
|
||||
// option, a positional argument filename, an inline string option, or piped from standard input.
|
||||
func check(o checkOptions, stdin io.Reader, args ...string) error {
|
||||
syntax, finalizeSyntax, err := initInput(o.Syntax, o.SyntaxString, nil, nil)
|
||||
if err != nil {
|
||||
stderr(err)
|
||||
return -1
|
||||
return err
|
||||
}
|
||||
|
||||
return 0
|
||||
defer finalizeSyntax()
|
||||
input, finalizeInput, err := initInput(o.Input, o.InputString, stdin, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer finalizeInput()
|
||||
s := &treerack.Syntax{}
|
||||
if err := s.ReadSyntax(syntax); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.Parse(input)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1,235 +1,146 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
var checkFailureTests = []mainTest{
|
||||
{
|
||||
title: "invalid flag",
|
||||
args: []string{
|
||||
"treerack", "check", "-foo",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
"-input",
|
||||
"-input-string",
|
||||
wrapLines(positionalInputUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "multiple syntaxes",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax", "foo.treerack", "-syntax-string", `foo = "bar"`, "-input-string", "bar",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"only one syntax",
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
"-input",
|
||||
"-input-string",
|
||||
wrapLines(positionalInputUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "multiple inputs",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`, "-input", "foo.txt", "-input-string", "bar",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"only one input",
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
"-input",
|
||||
"-input-string",
|
||||
wrapLines(positionalInputUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "multiple inputs, positional",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`, "foo.txt", "bar.txt",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"only one input",
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
"-input",
|
||||
"-input-string",
|
||||
wrapLines(positionalInputUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "multiple inputs, positional and explicit file",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`, "-input", "foo.txt", "bar.txt",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"only one input",
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
"-input",
|
||||
"-input-string",
|
||||
wrapLines(positionalInputUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "no syntax",
|
||||
args: []string{
|
||||
"treerack", "check", "-input-string", "foo",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"missing syntax",
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
"-input",
|
||||
"-input-string",
|
||||
wrapLines(positionalInputUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "no input",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`,
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"missing input",
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
"-input",
|
||||
"-input-string",
|
||||
wrapLines(positionalInputUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "invalid syntax",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", "foo", "-input-string", "foo",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"parse failed",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "syntax file open fails",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax", "noexist.treerack", "-input-string", "foo",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"file",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "input file open fails",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`, "-input", "noexist.txt",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"file",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "invalid input",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`, "-input-string", "foo",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"parse failed",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var checkTests = []mainTest{
|
||||
{
|
||||
title: "syntax as file",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax", "foo_test.treerack", "-input-string", "bar",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "syntax as string",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`, "-input-string", "bar",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "input as stdin",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`,
|
||||
},
|
||||
stdin: "bar",
|
||||
},
|
||||
|
||||
{
|
||||
title: "input as file",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`, "-input", "bar_test.txt",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "input as positional",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`, "bar_test.txt",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "input as string",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax-string", `foo = "bar"`, "-input-string", "bar",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "explicit over stdin",
|
||||
args: []string{
|
||||
"treerack", "check", "-syntax", "foo_test.treerack", "-input-string", "bar",
|
||||
},
|
||||
stdin: "invalid",
|
||||
},
|
||||
}
|
||||
import (
|
||||
"bytes"
|
||||
"code.squareroundforest.org/arpio/treerack"
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheck(t *testing.T) {
|
||||
runMainTest(t, mainTest{
|
||||
title: "help",
|
||||
args: []string{
|
||||
"treerack", "check", "-help",
|
||||
},
|
||||
stdout: []string{
|
||||
wrapLines(checkUsage),
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
"-input",
|
||||
"-input-string",
|
||||
wrapLines(positionalInputUsage),
|
||||
wrapLines(checkExample),
|
||||
wrapLines(docRef),
|
||||
},
|
||||
t.Run("no syntax", func(t *testing.T) {
|
||||
o := checkOptions{Input: "bar_test.txt"}
|
||||
if err := check(o, nil); !errors.Is(err, errNoInput) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
runMainTest(t, checkFailureTests...)
|
||||
runMainTest(t, checkTests...)
|
||||
t.Run("too many syntaxes", func(t *testing.T) {
|
||||
o := checkOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
SyntaxString: `foo = "baz"`,
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
if err := check(o, nil); !errors.Is(err, errMultipleInputs) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("syntax file not found", func(t *testing.T) {
|
||||
o := checkOptions{
|
||||
Syntax: "no-file.treerack",
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
if err := check(o, nil); !os.IsNotExist(err) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid syntax definition", func(t *testing.T) {
|
||||
o := checkOptions{
|
||||
SyntaxString: `foo`,
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
var perr *treerack.ParseError
|
||||
if err := check(o, nil); !errors.As(err, &perr) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid syntax init", func(t *testing.T) {
|
||||
o := checkOptions{
|
||||
SyntaxString: `foo = "bar"; foo = "baz"`,
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
if err := check(o, nil); err == nil {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no input", func(t *testing.T) {
|
||||
o := checkOptions{Syntax: "foo_test.treerack"}
|
||||
|
||||
if err := check(o, nil); !errors.Is(err, errNoInput) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("too many inputs", func(t *testing.T) {
|
||||
o := checkOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
if err := check(o, nil, "baz_test.txt"); !errors.Is(err, errMultipleInputs) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty filename for input", func(t *testing.T) {
|
||||
o := checkOptions{Syntax: "foo_test.treerack"}
|
||||
if err := check(o, nil, ""); !errors.Is(err, errInvalidFilename) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("input file not found", func(t *testing.T) {
|
||||
o := checkOptions{Syntax: "foo_test.treerack"}
|
||||
if err := check(o, nil, "baz_test.txt"); !os.IsNotExist(err) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("input parse fail", func(t *testing.T) {
|
||||
o := checkOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
InputString: "baz",
|
||||
}
|
||||
|
||||
var perr *treerack.ParseError
|
||||
if err := check(o, nil); !errors.As(err, &perr) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("input parse success", func(t *testing.T) {
|
||||
o := checkOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
if err := check(o, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("input from string success", func(t *testing.T) {
|
||||
o := checkOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
InputString: "bar",
|
||||
}
|
||||
|
||||
if err := check(o, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("input from file success", func(t *testing.T) {
|
||||
o := checkOptions{Syntax: "foo_test.treerack"}
|
||||
if err := check(o, nil, "bar_test.txt"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("input from stdin success", func(t *testing.T) {
|
||||
o := checkOptions{Syntax: "foo_test.treerack"}
|
||||
buf := bytes.NewBufferString("bar")
|
||||
if err := check(o, buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,36 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.squareroundforest.org/arpio/treerack"
|
||||
"io"
|
||||
)
|
||||
|
||||
type checkSyntaxOptions struct {
|
||||
command *commandOptions
|
||||
syntax *fileOptions
|
||||
|
||||
// Syntax specifies the filename of the syntax definition file.
|
||||
Syntax string
|
||||
|
||||
// SyntaxString specifies the syntax as an inline string.
|
||||
SyntaxString string
|
||||
}
|
||||
|
||||
func checkSyntax(args []string) int {
|
||||
var o checkSyntaxOptions
|
||||
o.command = initOptions(checkSyntaxUsage, checkSyntaxExample, positionalSyntaxUsage, args)
|
||||
o.syntax = &fileOptions{typ: "syntax", flagSet: o.command.flagSet, positionalDoc: positionalSyntaxUsage}
|
||||
|
||||
o.command.stringFlag(&o.syntax.inline, "syntax-string", syntaxStringUsage)
|
||||
o.command.stringFlag(&o.syntax.fileName, "syntax", syntaxFileUsage)
|
||||
|
||||
if o.command.help() {
|
||||
return 0
|
||||
// checkSyntax validates a syntax definition. The syntax may be provided via a file path (using an option or a
|
||||
// positional argument), an inline string, or piped from standard input.
|
||||
func checkSyntax(o checkSyntaxOptions, stdin io.Reader, args ...string) error {
|
||||
syntax, finalize, err := initInput(o.Syntax, o.SyntaxString, stdin, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if code := o.command.parseArgs(); code != 0 {
|
||||
return code
|
||||
defer finalize()
|
||||
s := &treerack.Syntax{}
|
||||
if err := s.ReadSyntax(syntax); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.syntax.positional = o.command.flagSet.Args()
|
||||
s, code := o.syntax.openSyntax()
|
||||
if code != 0 {
|
||||
return code
|
||||
}
|
||||
|
||||
if err := s.Init(); err != nil {
|
||||
stderr(err)
|
||||
return -1
|
||||
}
|
||||
|
||||
return 0
|
||||
return s.Init()
|
||||
}
|
||||
|
||||
@ -1,166 +1,76 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
var checkSyntaxFailureTests = []mainTest{
|
||||
{
|
||||
title: "invalid flag",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "-foo",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
wrapLines(positionalSyntaxUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "multiple inputs",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "-syntax", "foo.treerack", "-syntax-string", `foo = "bar"`,
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"only one syntax",
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
wrapLines(positionalSyntaxUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "multiple inputs, positional",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "foo.treerack", "bar.treerack",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"only one syntax",
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
wrapLines(positionalSyntaxUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "multiple inputs, positional and explicit file",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "-syntax", "foo.treerack", "bar.treerack",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"only one syntax",
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
wrapLines(positionalSyntaxUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "no input",
|
||||
args: []string{
|
||||
"treerack", "check-syntax",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"missing syntax",
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
wrapLines(positionalSyntaxUsage),
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "invalid input",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "-syntax-string", "foo",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"parse failed",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "file open fails",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "-syntax", "noexist.treerack",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"file",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var checkSyntaxTests = []mainTest{
|
||||
{
|
||||
title: "syntax as stdin",
|
||||
args: []string{
|
||||
"treerack", "check-syntax",
|
||||
},
|
||||
stdin: `foo = "bar"`,
|
||||
},
|
||||
|
||||
{
|
||||
title: "syntax as file",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "-syntax", "foo_test.treerack",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "syntax as positional",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "foo_test.treerack",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "syntax as string",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "-syntax-string", `foo = "bar"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "explicit over stdin",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "-syntax", "foo_test.treerack",
|
||||
},
|
||||
stdin: "invalid",
|
||||
},
|
||||
|
||||
{
|
||||
title: "invalid syntax semantics",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "-syntax-string", `foo:alias = "bar"`,
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"root",
|
||||
},
|
||||
},
|
||||
}
|
||||
import (
|
||||
"bytes"
|
||||
"code.squareroundforest.org/arpio/treerack"
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckSyntax(t *testing.T) {
|
||||
runMainTest(t, mainTest{
|
||||
title: "help",
|
||||
args: []string{
|
||||
"treerack", "check-syntax", "-help",
|
||||
},
|
||||
stdout: []string{
|
||||
wrapLines(checkSyntaxUsage),
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
wrapLines(positionalSyntaxUsage),
|
||||
wrapLines(checkSyntaxExample),
|
||||
wrapLines(docRef),
|
||||
},
|
||||
t.Run("too many inputs", func(t *testing.T) {
|
||||
o := checkSyntaxOptions{Syntax: "foo_test.treerack", SyntaxString: `foo = "42"`}
|
||||
if err := checkSyntax(o, nil); !errors.Is(err, errMultipleInputs) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
runMainTest(t, checkSyntaxFailureTests...)
|
||||
runMainTest(t, checkSyntaxTests...)
|
||||
t.Run("empty filename", func(t *testing.T) {
|
||||
var o checkSyntaxOptions
|
||||
if err := checkSyntax(o, nil, ""); !errors.Is(err, errInvalidFilename) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("file not found", func(t *testing.T) {
|
||||
var o checkSyntaxOptions
|
||||
if err := checkSyntax(o, nil, "nofile.treerack"); !os.IsNotExist(err) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid syntax", func(t *testing.T) {
|
||||
var perr *treerack.ParseError
|
||||
o := checkSyntaxOptions{SyntaxString: "foo"}
|
||||
if err := checkSyntax(o, nil); !errors.As(err, &perr) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid syntax init", func(t *testing.T) {
|
||||
o := checkSyntaxOptions{SyntaxString: `foo = "42"; foo = "84"`}
|
||||
if err := checkSyntax(o, nil); err == nil {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
o := checkSyntaxOptions{Syntax: "foo_test.treerack"}
|
||||
if err := checkSyntax(o, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("from string success", func(t *testing.T) {
|
||||
o := checkSyntaxOptions{SyntaxString: `foo = "bar"`}
|
||||
if err := checkSyntax(o, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("syntax from file success", func(t *testing.T) {
|
||||
var o checkSyntaxOptions
|
||||
if err := checkSyntax(o, nil, "foo_test.treerack"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("syntax from stdin success", func(t *testing.T) {
|
||||
var o checkSyntaxOptions
|
||||
buf := bytes.NewBufferString(`foo = "bar"`)
|
||||
if err := checkSyntax(o, buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,106 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const summary = `treerack - parser generator - https://code.squareroundforest.org/arpio/treerack`
|
||||
|
||||
const commandsHelp = `Available commands:
|
||||
check validates an arbitrary input against a syntax definition
|
||||
show parses an arbitrary input with a syntax definition and prints the abstract syntax tree
|
||||
check-syntax validates a syntax definition
|
||||
generate generates a parser from a syntax definition
|
||||
help prints the current help
|
||||
|
||||
See more details about a particular command by calling:
|
||||
treerack <command> -help`
|
||||
|
||||
const docRef = `See more documentation about the definition syntax and the parser output at
|
||||
https://code.squareroundforest.org/arpio/treerack.`
|
||||
|
||||
const positionalSyntaxUsage = "The path to the syntax file is accepted as a positional argument."
|
||||
|
||||
const positionalInputUsage = "The path to the input file is accepted as a positional argument."
|
||||
|
||||
const syntaxFileUsage = "path to the syntax file in treerack format"
|
||||
|
||||
const syntaxStringUsage = "inline syntax in treerack format"
|
||||
|
||||
const inputFileUsage = "path to the input to be parsed"
|
||||
|
||||
const inputStringUsage = "inline input string to be parsed"
|
||||
|
||||
const packageNameUsage = `package name of the generated Go code`
|
||||
|
||||
const exportUsage = `when the export flag is set, the generated code will have exported symbols to allow using
|
||||
it as a separate package.`
|
||||
|
||||
const prettyUsage = `when the pretty flag is set, the AST will be pretty printed`
|
||||
|
||||
const indentUsage = `string used for indentation of the printed AST`
|
||||
|
||||
const checkUsage = `'treerack check' takes a syntax description from a file or inline string, an arbitrary piece
|
||||
of text from the standard input, or a file, or inline string, and parses the input text with the defined syntax.
|
||||
It returns non-zero exit code and prints the problem if the provided syntax is not valid or the input cannot be
|
||||
parsed with it.`
|
||||
|
||||
const checkExample = `Example:
|
||||
treerack check -syntax example.treerack foo.example`
|
||||
|
||||
const showUsage = `'treerack show' takes a syntax description from a file or inline string, an arbitrary piece
|
||||
of text from the standard input, or a file, or inline string, and parses the input text with the defined syntax.
|
||||
If it was successfully parsed, it prints the resulting abstract syntax tree (AST) in JSON format.`
|
||||
|
||||
const showExample = `Example:
|
||||
treerack show -syntax example.treerack foo.example`
|
||||
|
||||
const checkSyntaxUsage = `'treerack check-syntax' takes a syntax description from the standard input, or a file,
|
||||
or inline string, and validates it to check whether it represents a valid syntax. It returns with non-zero exit
|
||||
code and prints the problem if the syntax is not valid.`
|
||||
|
||||
const checkSyntaxExample = `Example:
|
||||
treerack check-syntax example.treerack`
|
||||
|
||||
const generateUsage = `'treerack generate' takes a syntax description from the standard input, or a file, or
|
||||
inline string, and generates parser code implementing the described syntax. It prints the parser code to the
|
||||
standard output.`
|
||||
|
||||
const generateExample = `Example:
|
||||
treerack generate example.treerack > parser.go`
|
||||
|
||||
const wrap = 72
|
||||
|
||||
func wrapLines(s string) string {
|
||||
s = strings.Replace(s, "\n", " ", -1)
|
||||
w := strings.Split(s, " ")
|
||||
|
||||
var l, ll []string
|
||||
for i := 0; i < len(w); i++ {
|
||||
ll = append(ll, w[i])
|
||||
lineLength := utf8.RuneCount([]byte(strings.Join(ll, " ")))
|
||||
if lineLength < wrap {
|
||||
continue
|
||||
}
|
||||
|
||||
if lineLength > wrap {
|
||||
ll = ll[:len(ll)-1]
|
||||
i--
|
||||
}
|
||||
|
||||
if len(ll) == 0 {
|
||||
l = append(l, w[i])
|
||||
i++
|
||||
} else {
|
||||
l = append(l, strings.Join(ll, " "))
|
||||
ll = nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(ll) > 0 {
|
||||
l = append(l, strings.Join(ll, " "))
|
||||
}
|
||||
|
||||
return strings.Join(l, "\n")
|
||||
}
|
||||
47
cmd/treerack/docreflect.gen.go
Normal file
47
cmd/treerack/docreflect.gen.go
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Generated with https://code.squareroundforest.org/arpio/docreflect
|
||||
*/
|
||||
|
||||
|
||||
package main
|
||||
import "code.squareroundforest.org/arpio/docreflect"
|
||||
func init() {
|
||||
docreflect.Register("main", "")
|
||||
docreflect.Register("main.check", "check parses input content against the provided syntax definition and fails if the input does not match.\nSyntax can be provided via a filename option or an inline string option. Input can be provided via a filename\noption, a positional argument filename, an inline string option, or piped from standard input.\n\nfunc(o, stdin, args)")
|
||||
docreflect.Register("main.checkOptions", "")
|
||||
docreflect.Register("main.checkOptions.Input", "Input specifies the filename of the input content to be validated.\n")
|
||||
docreflect.Register("main.checkOptions.InputString", "InputString specifies the input content as an inline string.\n")
|
||||
docreflect.Register("main.checkOptions.Syntax", "Syntax specifies the filename of the syntax definition file.\n")
|
||||
docreflect.Register("main.checkOptions.SyntaxString", "SyntaxString specifies the syntax as an inline string.\n")
|
||||
docreflect.Register("main.checkSyntax", "checkSyntax validates a syntax definition. The syntax may be provided via a file path (using an option or a\npositional argument), an inline string, or piped from standard input.\n\nfunc(o, stdin, args)")
|
||||
docreflect.Register("main.checkSyntaxOptions", "")
|
||||
docreflect.Register("main.checkSyntaxOptions.Syntax", "Syntax specifies the filename of the syntax definition file.\n")
|
||||
docreflect.Register("main.checkSyntaxOptions.SyntaxString", "SyntaxString specifies the syntax as an inline string.\n")
|
||||
docreflect.Register("main.errInvalidFilename", "")
|
||||
docreflect.Register("main.errMultipleInputs", "")
|
||||
docreflect.Register("main.errNoInput", "")
|
||||
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.Export", "Export determines whether the generated parse function is exported (visible outside its package).\n")
|
||||
docreflect.Register("main.generateOptions.PackageName", "PackageName specifies the package name for the generated code. Defaults to main.\n")
|
||||
docreflect.Register("main.generateOptions.Syntax", "Syntax specifies the filename of the syntax definition file.\n")
|
||||
docreflect.Register("main.generateOptions.SyntaxString", "SyntaxString specifies the syntax as an inline string.\n")
|
||||
docreflect.Register("main.initInput", "\nfunc(filename, stringValue, stdin, args)")
|
||||
docreflect.Register("main.main", "\nfunc()")
|
||||
docreflect.Register("main.mapNode", "\nfunc(n)")
|
||||
docreflect.Register("main.node", "")
|
||||
docreflect.Register("main.node.From", "")
|
||||
docreflect.Register("main.node.Name", "")
|
||||
docreflect.Register("main.node.Nodes", "")
|
||||
docreflect.Register("main.node.Text", "")
|
||||
docreflect.Register("main.node.To", "")
|
||||
docreflect.Register("main.noop", "\nfunc()")
|
||||
docreflect.Register("main.show", "show input content against a provided syntax definition and outputs the resulting AST (Abstract Syntax Tree)\nin JSON format. Syntax can be provided via a filename option or an inline string option. Input can be\nprovided via a filename option, a positional argument filename, an inline string option, or piped from\nstandard input.\n\nfunc(o, stdin, stdout, args)")
|
||||
docreflect.Register("main.showOptions", "")
|
||||
docreflect.Register("main.showOptions.Indent", "Indent specifies a custom indentation string for the output.\n")
|
||||
docreflect.Register("main.showOptions.Input", "Input specifies the filename of the input content to be validated.\n")
|
||||
docreflect.Register("main.showOptions.InputString", "InputString specifies the input content as an inline string.\n")
|
||||
docreflect.Register("main.showOptions.Pretty", "Pretty enables indented, human-readable output.\n")
|
||||
docreflect.Register("main.showOptions.Syntax", "Syntax specifies the filename of the syntax definition file.\n")
|
||||
docreflect.Register("main.showOptions.SyntaxString", "SyntaxString specifies the syntax as an inline string.\n")
|
||||
}
|
||||
@ -1,46 +1,52 @@
|
||||
package main
|
||||
|
||||
import "code.squareroundforest.org/arpio/treerack"
|
||||
import (
|
||||
"code.squareroundforest.org/arpio/treerack"
|
||||
"io"
|
||||
)
|
||||
|
||||
type generateOptions struct {
|
||||
command *commandOptions
|
||||
syntax *fileOptions
|
||||
packageName string
|
||||
export bool
|
||||
|
||||
// Syntax specifies the filename of the syntax definition file.
|
||||
Syntax string
|
||||
|
||||
// SyntaxString specifies the syntax as an inline string.
|
||||
SyntaxString string
|
||||
|
||||
// PackageName specifies the package name for the generated code. Defaults to main.
|
||||
PackageName string
|
||||
|
||||
// Export determines whether the generated parse function is exported (visible outside its package).
|
||||
Export bool
|
||||
}
|
||||
|
||||
func generate(args []string) int {
|
||||
var o generateOptions
|
||||
o.command = initOptions(generateUsage, generateExample, positionalSyntaxUsage, args)
|
||||
o.syntax = &fileOptions{typ: "syntax", flagSet: o.command.flagSet, positionalDoc: positionalSyntaxUsage}
|
||||
|
||||
o.command.boolFlag(&o.export, "export", exportUsage)
|
||||
o.command.stringFlag(&o.packageName, "package-name", packageNameUsage)
|
||||
o.command.stringFlag(&o.syntax.inline, "syntax-string", syntaxStringUsage)
|
||||
o.command.stringFlag(&o.syntax.fileName, "syntax", syntaxFileUsage)
|
||||
|
||||
if o.command.help() {
|
||||
return 0
|
||||
// generate generates Go code that can parse arbitrary input with the provided syntax, and can be used embedded
|
||||
// in an application.
|
||||
//
|
||||
// The syntax may be provided via a file path (using an option or a positional argument), an
|
||||
// inline string, or piped from standard input.
|
||||
func generate(o generateOptions, stdin io.Reader, stdout io.Writer, args ...string) error {
|
||||
syntax, finalizeSyntax, err := initInput(o.Syntax, o.SyntaxString, stdin, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if code := o.command.parseArgs(); code != 0 {
|
||||
return code
|
||||
defer finalizeSyntax()
|
||||
s := &treerack.Syntax{}
|
||||
if err := s.ReadSyntax(syntax); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.syntax.positional = o.command.flagSet.Args()
|
||||
s, code := o.syntax.openSyntax()
|
||||
if code != 0 {
|
||||
return code
|
||||
if err := s.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var g treerack.GeneratorOptions
|
||||
g.PackageName = o.packageName
|
||||
g.Export = o.export
|
||||
|
||||
if err := s.Generate(g, wout); err != nil {
|
||||
stderr(err)
|
||||
return -1
|
||||
var genOpt treerack.GeneratorOptions
|
||||
genOpt.PackageName = o.PackageName
|
||||
genOpt.Export = o.Export
|
||||
if err := s.Generate(genOpt, stdout); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,117 +1,155 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
var generateFailureTests = convertTests("generate", checkSyntaxFailureTests)
|
||||
|
||||
var generateTests = []mainTest{
|
||||
{
|
||||
title: "failing output",
|
||||
args: []string{
|
||||
"treerack", "generate", "-syntax-string", `foo = "bar"`,
|
||||
},
|
||||
failingOutput: true,
|
||||
exit: -1,
|
||||
},
|
||||
|
||||
{
|
||||
title: "syntax as stdin",
|
||||
args: []string{
|
||||
"treerack", "generate", "-export", "-package-name", "foo",
|
||||
},
|
||||
stdin: `foo = "bar"`,
|
||||
stdout: []string{
|
||||
"package foo",
|
||||
"func Parse",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "syntax as file",
|
||||
args: []string{
|
||||
"treerack", "generate", "-export", "-package-name", "foo", "-syntax", "foo_test.treerack",
|
||||
},
|
||||
stdout: []string{
|
||||
"package foo",
|
||||
"func Parse",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "syntax as positional",
|
||||
args: []string{
|
||||
"treerack", "generate", "-export", "-package-name", "foo", "foo_test.treerack",
|
||||
},
|
||||
stdout: []string{
|
||||
"package foo",
|
||||
"func Parse",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "syntax as string",
|
||||
args: []string{
|
||||
"treerack", "generate", "-export", "-package-name", "foo", "-syntax-string", `foo = "bar"`,
|
||||
},
|
||||
stdout: []string{
|
||||
"package foo",
|
||||
"func Parse",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "default package name",
|
||||
args: []string{
|
||||
"treerack", "generate", "-export", "-syntax-string", `foo = "bar"`,
|
||||
},
|
||||
stdout: []string{
|
||||
"package main",
|
||||
"func Parse",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "no export",
|
||||
args: []string{
|
||||
"treerack", "generate", "-package-name", "foo", "-syntax-string", `foo = "bar"`,
|
||||
},
|
||||
stdout: []string{
|
||||
"package foo",
|
||||
"func parse",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "explicit over stdin",
|
||||
args: []string{
|
||||
"treerack", "generate", "-export", "-package-name", "foo", "-syntax", "foo_test.treerack",
|
||||
},
|
||||
stdin: "invalid",
|
||||
stdout: []string{
|
||||
"package foo",
|
||||
"func Parse",
|
||||
},
|
||||
},
|
||||
}
|
||||
import (
|
||||
"bytes"
|
||||
"code.squareroundforest.org/arpio/treerack"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
runMainTest(t, mainTest{
|
||||
title: "help",
|
||||
args: []string{
|
||||
"treerack", "generate", "-help",
|
||||
},
|
||||
stdout: []string{
|
||||
wrapLines(generateUsage),
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
"-export",
|
||||
"-package-name",
|
||||
wrapLines(positionalSyntaxUsage),
|
||||
wrapLines(generateExample),
|
||||
wrapLines(docRef),
|
||||
},
|
||||
t.Run("too many inputs", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := generateOptions{Syntax: "foo_test.treerack", SyntaxString: `foo = "42"`}
|
||||
if err := generate(o, nil, &out); !errors.Is(err, errMultipleInputs) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
runMainTest(t, generateFailureTests...)
|
||||
runMainTest(t, generateTests...)
|
||||
t.Run("empty filename", func(t *testing.T) {
|
||||
var (
|
||||
o generateOptions
|
||||
out bytes.Buffer
|
||||
)
|
||||
|
||||
if err := generate(o, nil, &out, ""); !errors.Is(err, errInvalidFilename) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("file not found", func(t *testing.T) {
|
||||
var (
|
||||
o generateOptions
|
||||
out bytes.Buffer
|
||||
)
|
||||
|
||||
if err := generate(o, nil, &out, "nofile.treerack"); !os.IsNotExist(err) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid syntax", func(t *testing.T) {
|
||||
var (
|
||||
out bytes.Buffer
|
||||
perr *treerack.ParseError
|
||||
)
|
||||
|
||||
o := generateOptions{SyntaxString: "foo"}
|
||||
if err := generate(o, nil, &out); !errors.As(err, &perr) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid syntax init", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := generateOptions{SyntaxString: `foo = "42"; foo = "84"`}
|
||||
if err := generate(o, nil, &out); err == nil {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := generateOptions{Syntax: "foo_test.treerack"}
|
||||
if err := generate(o, nil, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out.String(), "package main") ||
|
||||
!strings.Contains(out.String(), "func parse") {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success string", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := generateOptions{SyntaxString: `foo = "bar"`}
|
||||
if err := generate(o, nil, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out.String(), "package main") ||
|
||||
!strings.Contains(out.String(), "func parse") {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success file", func(t *testing.T) {
|
||||
var (
|
||||
out bytes.Buffer
|
||||
o generateOptions
|
||||
)
|
||||
|
||||
if err := generate(o, nil, &out, "foo_test.treerack"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out.String(), "package main") ||
|
||||
!strings.Contains(out.String(), "func parse") {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("success stdin", func(t *testing.T) {
|
||||
var (
|
||||
out bytes.Buffer
|
||||
o generateOptions
|
||||
)
|
||||
|
||||
in := bytes.NewBufferString(`foo = "bar"`)
|
||||
if err := generate(o, in, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out.String(), "package main") ||
|
||||
!strings.Contains(out.String(), "func parse") {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("custom package name", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := generateOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
PackageName: "foo",
|
||||
}
|
||||
|
||||
if err := generate(o, nil, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out.String(), "package foo") ||
|
||||
!strings.Contains(out.String(), "func parse") {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("export", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := generateOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
Export: true,
|
||||
}
|
||||
|
||||
if err := generate(o, nil, &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !strings.Contains(out.String(), "package main") ||
|
||||
!strings.Contains(out.String(), "func Parse") {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
78
cmd/treerack/input.go
Normal file
78
cmd/treerack/input.go
Normal file
@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoInput = errors.New("input undefined")
|
||||
errMultipleInputs = errors.New("multiple inputs defined")
|
||||
errInvalidFilename = errors.New("invalid filename")
|
||||
)
|
||||
|
||||
func noop() {}
|
||||
|
||||
func initInput(
|
||||
filename, stringValue string, stdin io.Reader, args []string,
|
||||
) (input io.Reader, finalize func(), err error) {
|
||||
finalize = noop
|
||||
|
||||
var inputCount int
|
||||
if filename != "" {
|
||||
inputCount++
|
||||
}
|
||||
|
||||
if stringValue != "" {
|
||||
inputCount++
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
inputCount++
|
||||
}
|
||||
|
||||
if inputCount > 1 {
|
||||
err = errMultipleInputs
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) > 0 && args[0] == "" {
|
||||
err = errInvalidFilename
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
filename = args[0]
|
||||
}
|
||||
|
||||
switch {
|
||||
case filename != "":
|
||||
var f io.ReadCloser
|
||||
f, err = os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
finalize = func() {
|
||||
if err := f.Close(); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
input = f
|
||||
case stringValue != "":
|
||||
input = bytes.NewBufferString(stringValue)
|
||||
default:
|
||||
if stdin == nil {
|
||||
err = errNoInput
|
||||
return
|
||||
}
|
||||
|
||||
input = stdin
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type exitFunc func(int)
|
||||
|
||||
var (
|
||||
isTest bool
|
||||
rin io.Reader = os.Stdin
|
||||
|
||||
wout io.Writer = os.Stdout
|
||||
werr io.Writer = os.Stderr
|
||||
|
||||
exit exitFunc = func(code int) {
|
||||
os.Exit(code)
|
||||
}
|
||||
)
|
||||
|
||||
func stdout(a ...interface{}) {
|
||||
fmt.Fprintln(wout, a...)
|
||||
}
|
||||
|
||||
func stderr(a ...interface{}) {
|
||||
fmt.Fprintln(werr, a...)
|
||||
}
|
||||
@ -1,47 +1,11 @@
|
||||
package main
|
||||
|
||||
import "os"
|
||||
|
||||
func mainHelp() {
|
||||
stdout(summary)
|
||||
stdout()
|
||||
stdout(commandsHelp)
|
||||
stdout()
|
||||
stdout(docRef)
|
||||
}
|
||||
import . "code.squareroundforest.org/arpio/wand"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) == 1 {
|
||||
stderr("missing command")
|
||||
stderr()
|
||||
stderr(commandsHelp)
|
||||
stderr()
|
||||
stderr(docRef)
|
||||
exit(-1)
|
||||
return
|
||||
}
|
||||
|
||||
var cmd func([]string) int
|
||||
|
||||
switch os.Args[1] {
|
||||
case "check-syntax":
|
||||
cmd = checkSyntax
|
||||
case "generate":
|
||||
cmd = generate
|
||||
case "check":
|
||||
cmd = check
|
||||
case "show":
|
||||
cmd = show
|
||||
case "help", "-help":
|
||||
mainHelp()
|
||||
return
|
||||
default:
|
||||
stderr("invalid command")
|
||||
stderr()
|
||||
stderr(commandsHelp)
|
||||
exit(-1)
|
||||
return
|
||||
}
|
||||
|
||||
exit(cmd(os.Args[2:]))
|
||||
checkSyntax := Args(Command("check-syntax", checkSyntax), 0, 1)
|
||||
check := Args(Command("check", check), 0, 1)
|
||||
show := Args(Command("show", show), 0, 1)
|
||||
generate := Args(Command("generate", generate), 0, 1)
|
||||
Exec(Group("treerack", checkSyntax, check, show, generate))
|
||||
}
|
||||
|
||||
@ -1,232 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mainTest struct {
|
||||
title string
|
||||
args []string
|
||||
failingOutput bool
|
||||
exit int
|
||||
stdin string
|
||||
stdout []string
|
||||
stderr []string
|
||||
}
|
||||
|
||||
type failingWriter struct{}
|
||||
|
||||
var errWriteFailed = errors.New("write failed")
|
||||
|
||||
func (w failingWriter) Write([]byte) (int, error) {
|
||||
return 0, errWriteFailed
|
||||
}
|
||||
|
||||
func init() {
|
||||
isTest = true
|
||||
}
|
||||
|
||||
func convertTest(cmd string, t mainTest) mainTest {
|
||||
args := make([]string, len(t.args))
|
||||
copy(args, t.args)
|
||||
args[1] = cmd
|
||||
t.args = args
|
||||
return t
|
||||
}
|
||||
|
||||
func convertTests(cmd string, t []mainTest) []mainTest {
|
||||
tt := make([]mainTest, len(t))
|
||||
for i := range t {
|
||||
tt[i] = convertTest(cmd, t[i])
|
||||
}
|
||||
|
||||
return tt
|
||||
}
|
||||
|
||||
func mockArgs(args ...string) (reset func()) {
|
||||
original := os.Args
|
||||
os.Args = args
|
||||
reset = func() {
|
||||
os.Args = original
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func mockStdin(in string) (reset func()) {
|
||||
original := rin
|
||||
|
||||
if in == "" {
|
||||
rin = nil
|
||||
} else {
|
||||
rin = bytes.NewBufferString(in)
|
||||
}
|
||||
|
||||
reset = func() {
|
||||
rin = original
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func mockOutput(w *io.Writer, failing bool) (out fmt.Stringer, reset func()) {
|
||||
original := *w
|
||||
reset = func() { *w = original }
|
||||
|
||||
if failing {
|
||||
*w = failingWriter{}
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
*w = &buf
|
||||
out = &buf
|
||||
return
|
||||
}
|
||||
|
||||
func mockStdout() (out fmt.Stringer, reset func()) {
|
||||
return mockOutput(&wout, false)
|
||||
}
|
||||
|
||||
func mockStderr() (out fmt.Stringer, reset func()) {
|
||||
return mockOutput(&werr, false)
|
||||
}
|
||||
|
||||
func mockFailingOutput() (reset func()) {
|
||||
_, reset = mockOutput(&wout, true)
|
||||
return
|
||||
}
|
||||
|
||||
func mockExit() (code *int, reset func()) {
|
||||
var exitCode int
|
||||
code = &exitCode
|
||||
original := exit
|
||||
exit = func(c int) { exitCode = c }
|
||||
reset = func() { exit = original }
|
||||
return
|
||||
}
|
||||
|
||||
func (mt mainTest) run(t *testing.T) {
|
||||
test := func(t *testing.T) {
|
||||
defer mockArgs(mt.args...)()
|
||||
|
||||
defer mockStdin(mt.stdin)()
|
||||
|
||||
var stdout fmt.Stringer
|
||||
if mt.failingOutput {
|
||||
defer mockFailingOutput()()
|
||||
} else {
|
||||
var reset func()
|
||||
stdout, reset = mockStdout()
|
||||
defer reset()
|
||||
}
|
||||
|
||||
stderr, resetStderr := mockStderr()
|
||||
defer resetStderr()
|
||||
|
||||
code, resetExit := mockExit()
|
||||
defer resetExit()
|
||||
|
||||
main()
|
||||
|
||||
if *code != mt.exit {
|
||||
t.Error("invalid exit code")
|
||||
}
|
||||
|
||||
if stdout != nil {
|
||||
var failed bool
|
||||
for i := range mt.stdout {
|
||||
if !strings.Contains(stdout.String(), mt.stdout[i]) {
|
||||
t.Error("invalid output")
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
|
||||
if failed {
|
||||
t.Log(stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
var failed bool
|
||||
for i := range mt.stderr {
|
||||
if !strings.Contains(stderr.String(), mt.stderr[i]) {
|
||||
t.Error("invalid error output")
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
|
||||
if failed {
|
||||
t.Log(stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
if mt.title == "" {
|
||||
test(t)
|
||||
} else {
|
||||
t.Run(mt.title, test)
|
||||
}
|
||||
}
|
||||
|
||||
func runMainTest(t *testing.T, mt ...mainTest) {
|
||||
for i := range mt {
|
||||
mt[i].run(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingCommand(t *testing.T) {
|
||||
runMainTest(t,
|
||||
mainTest{
|
||||
args: []string{"treerack"},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"missing command",
|
||||
commandsHelp,
|
||||
docRef,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestInvalidCommand(t *testing.T) {
|
||||
runMainTest(t,
|
||||
mainTest{
|
||||
args: []string{
|
||||
"treerack", "foo",
|
||||
},
|
||||
exit: -1,
|
||||
stderr: []string{
|
||||
"invalid command",
|
||||
commandsHelp,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestHelp(t *testing.T) {
|
||||
runMainTest(t,
|
||||
mainTest{
|
||||
title: "without dash",
|
||||
args: []string{
|
||||
"treerack", "help",
|
||||
},
|
||||
stdout: []string{
|
||||
summary, commandsHelp, docRef,
|
||||
},
|
||||
},
|
||||
mainTest{
|
||||
title: "with dash",
|
||||
args: []string{
|
||||
"treerack", "-help",
|
||||
},
|
||||
stdout: []string{
|
||||
summary, commandsHelp, docRef,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -1,124 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.squareroundforest.org/arpio/treerack"
|
||||
"flag"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
type fileOptions struct {
|
||||
typ string
|
||||
inline string
|
||||
fileName string
|
||||
positional []string
|
||||
flagSet *flag.FlagSet
|
||||
positionalDoc string
|
||||
}
|
||||
|
||||
func (o *fileOptions) multipleInputsError() {
|
||||
stderr("only one", o.typ, "is allowed")
|
||||
stderr()
|
||||
stderr("Options:")
|
||||
o.flagSet.PrintDefaults()
|
||||
stderr()
|
||||
stderr(wrapLines(o.positionalDoc))
|
||||
}
|
||||
|
||||
func (o *fileOptions) missingInputError() {
|
||||
stderr("missing", o.typ)
|
||||
stderr()
|
||||
stderr("Options:")
|
||||
o.flagSet.PrintDefaults()
|
||||
stderr()
|
||||
stderr(wrapLines(o.positionalDoc))
|
||||
}
|
||||
|
||||
func (o *fileOptions) getSource() (hasInput bool, fileName string, inline string, code int) {
|
||||
if len(o.positional) > 1 {
|
||||
o.multipleInputsError()
|
||||
code = -1
|
||||
return
|
||||
}
|
||||
|
||||
hasPositional := len(o.positional) == 1
|
||||
hasFile := o.fileName != ""
|
||||
hasInline := o.inline != ""
|
||||
|
||||
var has bool
|
||||
for _, h := range []bool{hasPositional, hasFile, hasInline} {
|
||||
if h && has {
|
||||
o.multipleInputsError()
|
||||
code = -1
|
||||
return
|
||||
}
|
||||
|
||||
has = h
|
||||
}
|
||||
|
||||
switch {
|
||||
case hasPositional:
|
||||
fileName = o.positional[0]
|
||||
return
|
||||
case hasFile:
|
||||
fileName = o.fileName
|
||||
return
|
||||
case hasInline:
|
||||
inline = o.inline
|
||||
return
|
||||
}
|
||||
|
||||
// check input last to allow explicit input in non-TTY environments:
|
||||
hasInput = isTest && rin != nil || !isTest && !terminal.IsTerminal(0)
|
||||
if !hasInput {
|
||||
o.missingInputError()
|
||||
code = -1
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *fileOptions) open() (io.ReadCloser, int) {
|
||||
hasInput, fileName, inline, code := o.getSource()
|
||||
if code != 0 {
|
||||
return nil, code
|
||||
}
|
||||
|
||||
var r io.ReadCloser
|
||||
if hasInput {
|
||||
r = ioutil.NopCloser(rin)
|
||||
} else if fileName != "" {
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
stderr(err)
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
r = f
|
||||
} else {
|
||||
r = ioutil.NopCloser(bytes.NewBufferString(inline))
|
||||
}
|
||||
|
||||
return r, 0
|
||||
}
|
||||
|
||||
func (o *fileOptions) openSyntax() (*treerack.Syntax, int) {
|
||||
input, code := o.open()
|
||||
if code != 0 {
|
||||
return nil, code
|
||||
}
|
||||
|
||||
defer input.Close()
|
||||
|
||||
s := &treerack.Syntax{}
|
||||
if err := s.ReadSyntax(input); err != nil {
|
||||
stderr(err)
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
return s, 0
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
package main
|
||||
|
||||
import "flag"
|
||||
|
||||
type commandOptions struct {
|
||||
usage string
|
||||
example string
|
||||
args []string
|
||||
flagSet *flag.FlagSet
|
||||
positionalDoc string
|
||||
}
|
||||
|
||||
func initOptions(usage, example, positionalDoc string, args []string) *commandOptions {
|
||||
var o commandOptions
|
||||
|
||||
o.usage = wrapLines(usage)
|
||||
o.example = wrapLines(example)
|
||||
o.positionalDoc = wrapLines(positionalDoc)
|
||||
o.args = args
|
||||
|
||||
o.flagSet = flag.NewFlagSet("", flag.ContinueOnError)
|
||||
o.flagSet.Usage = func() {}
|
||||
o.flagSet.SetOutput(werr)
|
||||
|
||||
return &o
|
||||
}
|
||||
|
||||
func (o *commandOptions) boolFlag(v *bool, name, usage string) {
|
||||
usage = wrapLines(usage)
|
||||
o.flagSet.BoolVar(v, name, *v, usage)
|
||||
}
|
||||
|
||||
func (o *commandOptions) stringFlag(v *string, name, usage string) {
|
||||
usage = wrapLines(usage)
|
||||
o.flagSet.StringVar(v, name, *v, usage)
|
||||
}
|
||||
|
||||
func (o *commandOptions) flagError() {
|
||||
stderr()
|
||||
stderr("Options:")
|
||||
o.flagSet.PrintDefaults()
|
||||
stderr()
|
||||
stderr(o.positionalDoc)
|
||||
}
|
||||
|
||||
func (o *commandOptions) parseArgs() (exit int) {
|
||||
if err := o.flagSet.Parse(o.args); err != nil {
|
||||
o.flagError()
|
||||
exit = -1
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (o *commandOptions) printHelp() {
|
||||
stdout(o.usage)
|
||||
stdout()
|
||||
|
||||
stdout("Options:")
|
||||
o.flagSet.SetOutput(wout)
|
||||
o.flagSet.PrintDefaults()
|
||||
stdout()
|
||||
stdout(o.positionalDoc)
|
||||
|
||||
stdout()
|
||||
stdout(o.example)
|
||||
stdout()
|
||||
stdout(wrapLines(docRef))
|
||||
}
|
||||
|
||||
func (o *commandOptions) help() bool {
|
||||
if len(o.args) == 0 || o.args[0] != "-help" {
|
||||
return false
|
||||
}
|
||||
|
||||
o.printHelp()
|
||||
return true
|
||||
}
|
||||
134
cmd/treerack/readme.md
Normal file
134
cmd/treerack/readme.md
Normal file
@ -0,0 +1,134 @@
|
||||
# treerack
|
||||
|
||||
## Synopsis:
|
||||
|
||||
```
|
||||
treerack <subcommand>
|
||||
```
|
||||
|
||||
## Options:
|
||||
|
||||
- --help: Show help.
|
||||
|
||||
## Subcommands:
|
||||
|
||||
Show help for each subcommand by calling \<command\> help or \<command\> --help.
|
||||
|
||||
### treerack check-syntax
|
||||
|
||||
#### Synopsis:
|
||||
|
||||
```
|
||||
treerack check-syntax [options]... [--] [args string]...
|
||||
treerack check-syntax <subcommand>
|
||||
```
|
||||
|
||||
Expecting max 1 total number of arguments.
|
||||
|
||||
#### Description:
|
||||
|
||||
validates a syntax definition. The syntax may be provided via a file path (using an option or a positional
|
||||
argument), an inline string, or piped from standard input.
|
||||
|
||||
#### Options:
|
||||
|
||||
- --syntax string: specifies the filename of the syntax definition file.
|
||||
- --syntax-string string: specifies the syntax as an inline string.
|
||||
- --help: Show help.
|
||||
|
||||
### treerack check
|
||||
|
||||
#### Synopsis:
|
||||
|
||||
```
|
||||
treerack check [options]... [--] [args string]...
|
||||
treerack check <subcommand>
|
||||
```
|
||||
|
||||
Expecting max 1 total number of arguments.
|
||||
|
||||
#### Description:
|
||||
|
||||
parses input content against the provided syntax definition and fails if the input does not match. Syntax can be
|
||||
provided via a filename option or an inline string option. Input can be provided via a filename option, a
|
||||
positional argument filename, an inline string option, or piped from standard input.
|
||||
|
||||
#### Options:
|
||||
|
||||
- --input string: specifies the filename of the input content to be validated.
|
||||
- --input-string string: specifies the input content as an inline string.
|
||||
- --syntax string: specifies the filename of the syntax definition file.
|
||||
- --syntax-string string: specifies the syntax as an inline string.
|
||||
- --help: Show help.
|
||||
|
||||
### treerack show
|
||||
|
||||
#### Synopsis:
|
||||
|
||||
```
|
||||
treerack show [options]... [--] [args string]...
|
||||
treerack show <subcommand>
|
||||
```
|
||||
|
||||
Expecting max 1 total number of arguments.
|
||||
|
||||
#### Description:
|
||||
|
||||
input content against a provided syntax definition and outputs the resulting AST (Abstract Syntax Tree) in JSON
|
||||
format. Syntax can be provided via a filename option or an inline string option. Input can be provided via a
|
||||
filename option, a positional argument filename, an inline string option, or piped from standard input.
|
||||
|
||||
#### Options:
|
||||
|
||||
- --indent string: specifies a custom indentation string for the output.
|
||||
- --input string: specifies the filename of the input content to be validated.
|
||||
- --input-string string: specifies the input content as an inline string.
|
||||
- --pretty bool: enables indented, human-readable output.
|
||||
- --syntax string: specifies the filename of the syntax definition file.
|
||||
- --syntax-string string: specifies the syntax as an inline string.
|
||||
- --help: Show help.
|
||||
|
||||
### treerack generate
|
||||
|
||||
#### Synopsis:
|
||||
|
||||
```
|
||||
treerack generate [options]... [--] [args string]...
|
||||
treerack generate <subcommand>
|
||||
```
|
||||
|
||||
Expecting max 1 total number of arguments.
|
||||
|
||||
#### Description:
|
||||
|
||||
generates Go code that can parse arbitrary input with the provided syntax, and can be used embedded in an
|
||||
application.
|
||||
|
||||
The syntax may be provided via a file path (using an option or a positional argument), an inline string, or
|
||||
piped from standard input.
|
||||
|
||||
#### Options:
|
||||
|
||||
- --export bool: determines whether the generated parse function is exported (visible outside its package).
|
||||
- --package-name string: specifies the package name for the generated code. Defaults to main.
|
||||
- --syntax string: specifies the filename of the syntax definition file.
|
||||
- --syntax-string string: specifies the syntax as an inline string.
|
||||
- --help: Show help.
|
||||
|
||||
## Environment variables:
|
||||
|
||||
Every command line option's value can also be provided as an environment variable. Environment variable names
|
||||
need to use snake casing like myapp\_foo\_bar\_baz or MYAPP\_FOO\_BAR\_BAZ, or other casing that doesn't include the
|
||||
'-' dash character, and they need to be prefixed with the name of the application, as in the base name of the
|
||||
command.
|
||||
|
||||
When both the environment variable and the command line option is defined, the command line option overrides the
|
||||
environment variable. Multiple values for the same environment variable can be defined by concatenating the
|
||||
values with the ':' separator character. When overriding multiple values with command line options, all the
|
||||
environment values of the same field are dropped.
|
||||
|
||||
### Example environment variable:
|
||||
|
||||
```
|
||||
TREERACK_SYNTAX=42
|
||||
```
|
||||
@ -3,25 +3,39 @@ package main
|
||||
import (
|
||||
"code.squareroundforest.org/arpio/treerack"
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type showOptions struct {
|
||||
command *commandOptions
|
||||
syntax *fileOptions
|
||||
input *fileOptions
|
||||
pretty bool
|
||||
indent string
|
||||
|
||||
// Syntax specifies the filename of the syntax definition file.
|
||||
Syntax string
|
||||
|
||||
// SyntaxString specifies the syntax as an inline string.
|
||||
SyntaxString string
|
||||
|
||||
// Input specifies the filename of the input content to be validated.
|
||||
Input string
|
||||
|
||||
// InputString specifies the input content as an inline string.
|
||||
InputString string
|
||||
|
||||
// Pretty enables indented, human-readable output.
|
||||
Pretty bool
|
||||
|
||||
// Indent specifies a custom indentation string for the output.
|
||||
Indent string
|
||||
}
|
||||
|
||||
type node struct {
|
||||
Name string `json:"name"`
|
||||
From int `json:"from"`
|
||||
To int `json:"to"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Nodes []*node `json:"nodes,omitempty"`
|
||||
Name string `json:"name"`
|
||||
From int `json:"from"`
|
||||
To int `json:"to"`
|
||||
Text string `json:"text,omitempty"`
|
||||
Nodes []node `json:"nodes,omitempty"`
|
||||
}
|
||||
|
||||
func mapNode(n *treerack.Node) *node {
|
||||
func mapNode(n *treerack.Node) node {
|
||||
var nn node
|
||||
nn.Name = n.Name
|
||||
nn.From = n.From
|
||||
@ -29,76 +43,67 @@ func mapNode(n *treerack.Node) *node {
|
||||
|
||||
if len(n.Nodes) == 0 {
|
||||
nn.Text = n.Text()
|
||||
return &nn
|
||||
return nn
|
||||
}
|
||||
|
||||
for i := range n.Nodes {
|
||||
nn.Nodes = append(nn.Nodes, mapNode(n.Nodes[i]))
|
||||
}
|
||||
|
||||
return &nn
|
||||
return nn
|
||||
}
|
||||
|
||||
func show(args []string) int {
|
||||
var o showOptions
|
||||
o.command = initOptions(showUsage, showExample, positionalInputUsage, args)
|
||||
o.syntax = &fileOptions{typ: "syntax", flagSet: o.command.flagSet, positionalDoc: positionalInputUsage}
|
||||
o.input = &fileOptions{typ: "input", flagSet: o.command.flagSet, positionalDoc: positionalInputUsage}
|
||||
|
||||
o.command.stringFlag(&o.syntax.inline, "syntax-string", syntaxStringUsage)
|
||||
o.command.stringFlag(&o.syntax.fileName, "syntax", syntaxFileUsage)
|
||||
|
||||
o.command.stringFlag(&o.input.inline, "input-string", inputStringUsage)
|
||||
o.command.stringFlag(&o.input.fileName, "input", inputFileUsage)
|
||||
|
||||
o.command.boolFlag(&o.pretty, "pretty", prettyUsage)
|
||||
o.command.stringFlag(&o.indent, "indent", indentUsage)
|
||||
|
||||
if o.command.help() {
|
||||
return 0
|
||||
// show input content against a provided syntax definition and outputs the resulting AST (Abstract Syntax Tree)
|
||||
// in JSON format. Syntax can be provided via a filename option or an inline string option. Input can be
|
||||
// provided via a filename option, a positional argument filename, an inline string option, or piped from
|
||||
// standard input.
|
||||
func show(o showOptions, stdin io.Reader, stdout io.Writer, args ...string) error {
|
||||
syntax, finalizeSyntax, err := initInput(o.Syntax, o.SyntaxString, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if code := o.command.parseArgs(); code != 0 {
|
||||
return code
|
||||
defer finalizeSyntax()
|
||||
input, finalizeInput, err := initInput(o.Input, o.InputString, stdin, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, code := o.syntax.openSyntax()
|
||||
if code != 0 {
|
||||
return code
|
||||
defer finalizeInput()
|
||||
s := &treerack.Syntax{}
|
||||
if err := s.ReadSyntax(syntax); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.input.positional = o.command.flagSet.Args()
|
||||
input, code := o.input.open()
|
||||
if code != 0 {
|
||||
return code
|
||||
if err := s.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer input.Close()
|
||||
|
||||
n, err := s.Parse(input)
|
||||
if err != nil {
|
||||
stderr(err)
|
||||
return -1
|
||||
return err
|
||||
}
|
||||
|
||||
nn := mapNode(n)
|
||||
|
||||
marshal := json.Marshal
|
||||
if o.pretty || o.indent != "" {
|
||||
if o.indent == "" {
|
||||
o.indent = " "
|
||||
encode := json.Marshal
|
||||
if o.Pretty || o.Indent != "" {
|
||||
if o.Indent == "" {
|
||||
o.Indent = " "
|
||||
}
|
||||
|
||||
marshal = func(n interface{}) ([]byte, error) {
|
||||
return json.MarshalIndent(n, "", o.indent)
|
||||
encode = func(a any) ([]byte, error) {
|
||||
return json.MarshalIndent(a, "", o.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := marshal(nn)
|
||||
b, err := encode(nn)
|
||||
if err != nil {
|
||||
stderr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
stdout(string(b))
|
||||
return 0
|
||||
if _, err := stdout.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,144 +1,232 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
var showFailureTests = convertTests("show", checkFailureTests)
|
||||
|
||||
var showTests = []mainTest{
|
||||
{
|
||||
title: "syntax as file",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax", "foo_test.treerack", "-input-string", "bar",
|
||||
},
|
||||
stdout: []string{
|
||||
`"name":"foo"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "syntax as string",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax-string", `foo = "bar"`, "-input-string", "bar",
|
||||
},
|
||||
stdout: []string{
|
||||
`"name":"foo"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "input as stdin",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax-string", `foo = "bar"`,
|
||||
},
|
||||
stdin: "bar",
|
||||
stdout: []string{
|
||||
`"name":"foo"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "input as file",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax-string", `foo = "bar"`, "-input", "bar_test.txt",
|
||||
},
|
||||
stdout: []string{
|
||||
`"name":"foo"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "input as positional",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax-string", `foo = "bar"`, "bar_test.txt",
|
||||
},
|
||||
stdout: []string{
|
||||
`"name":"foo"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "input as string",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax-string", `foo = "bar"`, "-input-string", "bar",
|
||||
},
|
||||
stdout: []string{
|
||||
`"name":"foo"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "explicit over stdin",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax", "foo_test.treerack", "-input-string", "bar",
|
||||
},
|
||||
stdin: "invalid",
|
||||
stdout: []string{
|
||||
`"name":"foo"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "pretty",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax-string", `foo = "bar"`, "-input-string", "bar", "-pretty",
|
||||
},
|
||||
stdout: []string{
|
||||
` "name": "foo"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "pretty and indent",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax-string", `foo = "bar"`, "-input-string", "bar", "-pretty", "-indent", "xx",
|
||||
},
|
||||
stdout: []string{
|
||||
`xx"name": "foo"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "indent without pretty",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax-string", `foo = "bar"`, "-input-string", "bar", "-pretty", "-indent", "xx",
|
||||
},
|
||||
stdout: []string{
|
||||
`xx"name": "foo"`,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: "with child nodes",
|
||||
args: []string{
|
||||
"treerack", "show", "-syntax-string", `foo = "bar"; doc = foo`, "-input-string", "bar",
|
||||
},
|
||||
stdout: []string{
|
||||
`"nodes":[`,
|
||||
`"text":"bar"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
import (
|
||||
"bytes"
|
||||
"code.squareroundforest.org/arpio/treerack"
|
||||
"errors"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestShow(t *testing.T) {
|
||||
runMainTest(t, mainTest{
|
||||
title: "help",
|
||||
args: []string{
|
||||
"treerack", "show", "-help",
|
||||
},
|
||||
stdout: []string{
|
||||
wrapLines(showUsage),
|
||||
"-syntax",
|
||||
"-syntax-string",
|
||||
"-input",
|
||||
"-input-string",
|
||||
"-pretty",
|
||||
"-indent",
|
||||
wrapLines(positionalInputUsage),
|
||||
wrapLines(showExample),
|
||||
wrapLines(docRef),
|
||||
},
|
||||
t.Run("no syntax", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{Input: "bar_test.txt"}
|
||||
if err := show(o, nil, &out); !errors.Is(err, errNoInput) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
runMainTest(t, showFailureTests...)
|
||||
runMainTest(t, showTests...)
|
||||
t.Run("too many syntaxes", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
SyntaxString: `foo = "baz"`,
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
if err := show(o, nil, &out); !errors.Is(err, errMultipleInputs) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("syntax file not found", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
Syntax: "no-file.treerack",
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
if err := show(o, nil, &out); !os.IsNotExist(err) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid syntax definition", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
SyntaxString: `foo`,
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
var perr *treerack.ParseError
|
||||
if err := show(o, nil, &out); !errors.As(err, &perr) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid syntax init", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
SyntaxString: `foo = "bar"; foo = "baz"`,
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
if err := show(o, nil, &out); err == nil {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no input", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{Syntax: "foo_test.treerack"}
|
||||
|
||||
if err := show(o, nil, &out); !errors.Is(err, errNoInput) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("too many inputs", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
if err := show(o, nil, &out, "baz_test.txt"); !errors.Is(err, errMultipleInputs) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty filename for input", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{Syntax: "foo_test.treerack"}
|
||||
if err := show(o, nil, &out, ""); !errors.Is(err, errInvalidFilename) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("input file not found", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{Syntax: "foo_test.treerack"}
|
||||
if err := show(o, nil, &out, "baz_test.txt"); !os.IsNotExist(err) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("input parse fail", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
InputString: "baz",
|
||||
}
|
||||
|
||||
var perr *treerack.ParseError
|
||||
if err := show(o, nil, &out); !errors.As(err, &perr) {
|
||||
t.Fatal()
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("show", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
Input: "bar_test.txt",
|
||||
}
|
||||
|
||||
if err := show(o, nil, &out); err != nil {
|
||||
t.Fatal(nil)
|
||||
}
|
||||
|
||||
if out.String() != `{"name":"foo","from":0,"to":3,"text":"bar"}` {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("show string", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
InputString: "bar",
|
||||
}
|
||||
|
||||
if err := show(o, nil, &out); err != nil {
|
||||
t.Fatal(nil)
|
||||
}
|
||||
|
||||
if out.String() != `{"name":"foo","from":0,"to":3,"text":"bar"}` {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("show file", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
}
|
||||
|
||||
if err := show(o, nil, &out, "bar_test.txt"); err != nil {
|
||||
t.Fatal(nil)
|
||||
}
|
||||
|
||||
if out.String() != `{"name":"foo","from":0,"to":3,"text":"bar"}` {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("show stdin", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{Syntax: "foo_test.treerack"}
|
||||
in := bytes.NewBufferString("bar")
|
||||
if err := show(o, in, &out); err != nil {
|
||||
t.Fatal(nil)
|
||||
}
|
||||
|
||||
if out.String() != `{"name":"foo","from":0,"to":3,"text":"bar"}` {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("indent", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
Input: "bar_test.txt",
|
||||
Pretty: true,
|
||||
}
|
||||
|
||||
if err := show(o, nil, &out); err != nil {
|
||||
t.Fatal(nil)
|
||||
}
|
||||
|
||||
const expect = "{\n \"name\": \"foo\",\n \"from\": 0,\n \"to\": 3,\n \"text\": \"bar\"\n}"
|
||||
if out.String() != expect {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("custom indent", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
Input: "bar_test.txt",
|
||||
Indent: "xx",
|
||||
}
|
||||
|
||||
if err := show(o, nil, &out); err != nil {
|
||||
t.Fatal(nil)
|
||||
}
|
||||
|
||||
if out.String() != "{\nxx\"name\": \"foo\",\nxx\"from\": 0,\nxx\"to\": 3,\nxx\"text\": \"bar\"\n}" {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("redundant custom indent", func(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
o := showOptions{
|
||||
Syntax: "foo_test.treerack",
|
||||
Input: "bar_test.txt",
|
||||
Pretty: true,
|
||||
Indent: "xx",
|
||||
}
|
||||
|
||||
if err := show(o, nil, &out); err != nil {
|
||||
t.Fatal(nil)
|
||||
}
|
||||
|
||||
if out.String() != "{\nxx\"name\": \"foo\",\nxx\"from\": 0,\nxx\"to\": 3,\nxx\"text\": \"bar\"\n}" {
|
||||
t.Fatal(out.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
22
go.mod
22
go.mod
@ -1,12 +1,20 @@
|
||||
module code.squareroundforest.org/arpio/treerack
|
||||
|
||||
go 1.24.6
|
||||
|
||||
require golang.org/x/crypto v0.41.0
|
||||
go 1.25.3
|
||||
|
||||
require (
|
||||
code.squareroundforest.org/arpio/notation v0.0.0-20241225183158-af3bd591a174 // indirect
|
||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
code.squareroundforest.org/arpio/wand v0.0.0-20260113225451-514cd3375d96
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
code.squareroundforest.org/arpio/bind v0.0.0-20251125135123-0de6ad6e67f2 // indirect
|
||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20260113222846-40bd1879753e // indirect
|
||||
code.squareroundforest.org/arpio/html v0.0.0-20251103020946-e262eca50ac9 // indirect
|
||||
code.squareroundforest.org/arpio/notation v0.0.0-20251101123932-5f5c05ee0239 // indirect
|
||||
code.squareroundforest.org/arpio/textedit v0.0.0-20251209222254-5a3e22b886be // indirect
|
||||
code.squareroundforest.org/arpio/textfmt v0.0.0-20251207234108-fed32c8bbe18 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
)
|
||||
|
||||
40
go.sum
40
go.sum
@ -1,10 +1,34 @@
|
||||
code.squareroundforest.org/arpio/notation v0.0.0-20241225183158-af3bd591a174 h1:DKMSagVY3uyRhJ4ohiwQzNnR6CWdVKLkg97A8eQGxQU=
|
||||
code.squareroundforest.org/arpio/notation v0.0.0-20241225183158-af3bd591a174/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY=
|
||||
code.squareroundforest.org/arpio/bind v0.0.0-20251105181644-3443251be2d5 h1:SIgLIawD6Vv7rAvUobpVshLshdwFEJ0NOUrWpheS088=
|
||||
code.squareroundforest.org/arpio/bind v0.0.0-20251105181644-3443251be2d5/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
||||
code.squareroundforest.org/arpio/bind v0.0.0-20251125135123-0de6ad6e67f2 h1:zEztr5eSD/V3lzKPcRAxNprobhHMd3w6Dw3oIbjNrrk=
|
||||
code.squareroundforest.org/arpio/bind v0.0.0-20251125135123-0de6ad6e67f2/go.mod h1:tTCmCwFABKNm3PO0Dclsp4zWhNQFTfg9+uSrgoarZFI=
|
||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20251031192707-01c5ff18fab1 h1:bJi41U5yGQykg6jVlD2AdWiznvx3Jg7ZpzEU85syOXw=
|
||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20251031192707-01c5ff18fab1/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA=
|
||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20260113222846-40bd1879753e h1:Z+TXQtCxNhHUgsBSYsatNGBCRtGibRcsEbZjk1LImCQ=
|
||||
code.squareroundforest.org/arpio/docreflect v0.0.0-20260113222846-40bd1879753e/go.mod h1:/3xQI36oJG8qLBxT2fSS61P5/+i1T64fTX9GHRh8XhA=
|
||||
code.squareroundforest.org/arpio/html v0.0.0-20251103020946-e262eca50ac9 h1:b7voJlwe0jKH568X+O7b/JTAUrHLTSKNSSL+hhV2Q/Q=
|
||||
code.squareroundforest.org/arpio/html v0.0.0-20251103020946-e262eca50ac9/go.mod h1:hq+2CENEd4bVSZnOdq38FUFOJJnF3OTQRv78qMGkNlE=
|
||||
code.squareroundforest.org/arpio/notation v0.0.0-20251101123932-5f5c05ee0239 h1:JvLVMuvF2laxXkIZbHC1/0xtKyKndAwIHbIIWkHqTzc=
|
||||
code.squareroundforest.org/arpio/notation v0.0.0-20251101123932-5f5c05ee0239/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY=
|
||||
code.squareroundforest.org/arpio/textedit v0.0.0-20251207224821-c75c3965789f h1:gomu8xTD953IkL3M528qVEuZ2z93C2I6Hr4vyIwE7kI=
|
||||
code.squareroundforest.org/arpio/textedit v0.0.0-20251207224821-c75c3965789f/go.mod h1:nXdFdxdI69JrkIT97f+AEE4OgplmxbgNFZC5j7gsdqs=
|
||||
code.squareroundforest.org/arpio/textedit v0.0.0-20251209222254-5a3e22b886be h1:hy7tbsf8Fzl0UzBUNXRottKtCg3GvVI7Hmaf28Qoias=
|
||||
code.squareroundforest.org/arpio/textedit v0.0.0-20251209222254-5a3e22b886be/go.mod h1:nXdFdxdI69JrkIT97f+AEE4OgplmxbgNFZC5j7gsdqs=
|
||||
code.squareroundforest.org/arpio/textfmt v0.0.0-20251207234108-fed32c8bbe18 h1:2aa62CYm9ld5SNoFxWzE2wUN0xjVWQ+xieoeFantdg4=
|
||||
code.squareroundforest.org/arpio/textfmt v0.0.0-20251207234108-fed32c8bbe18/go.mod h1:+0G3gufMAP8SCEIrDT1D/DaVOSfjS8EwPTBs5vfxqQg=
|
||||
code.squareroundforest.org/arpio/wand v0.0.0-20260108202216-ba493e77d610 h1:kgDcz4+PMq5iyd3r80vcZsNfphfaRIBf9B+D7B4vYfM=
|
||||
code.squareroundforest.org/arpio/wand v0.0.0-20260108202216-ba493e77d610/go.mod h1:rYqrSmdkBlKjGwEPzzWAIRQKQJCpkdzG7vDiL6Fux9Y=
|
||||
code.squareroundforest.org/arpio/wand v0.0.0-20260113225451-514cd3375d96 h1:RqFGMfQznU7ivTLS8/Qj0AantFbEHSAy6U/B4xoSO88=
|
||||
code.squareroundforest.org/arpio/wand v0.0.0-20260113225451-514cd3375d96/go.mod h1:fPxs3LeGPxRMWUIXgBcdszk3a8d1TRqSHSVs5VL28Rc=
|
||||
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
|
||||
14
scripts/cmdreadme.go
Normal file
14
scripts/cmdreadme.go
Normal file
@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.squareroundforest.org/arpio/wand/tools"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var o tools.MarkdownOptions
|
||||
if err := tools.Markdown(os.Stdout, o, os.Args[1]); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
15
scripts/docreflect.go
Normal file
15
scripts/docreflect.go
Normal file
@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.squareroundforest.org/arpio/wand/tools"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
const pkg = "code.squareroundforest.org/arpio/treerack/cmd/treerack"
|
||||
o := tools.DocreflectOptions{Main: true}
|
||||
if err := tools.Docreflect(o, os.Stdout, "main", pkg); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
18
scripts/man.go
Normal file
18
scripts/man.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.squareroundforest.org/arpio/wand/tools"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
o := tools.ManOptions{
|
||||
Version: os.Args[1],
|
||||
DateString: os.Args[2],
|
||||
}
|
||||
|
||||
if err := tools.Man(os.Stdout, o, "./cmd/treerack"); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
16
syntax.go
16
syntax.go
@ -103,10 +103,6 @@ func isValidSymbol(n string) bool {
|
||||
|
||||
}
|
||||
|
||||
// func (pe *ParseError) Verbose() string {
|
||||
// return ""
|
||||
// }
|
||||
|
||||
func intsContain(is []int, i int) bool {
|
||||
for _, ii := range is {
|
||||
if ii == i {
|
||||
@ -286,6 +282,18 @@ func (s *Syntax) ReadSyntax(r io.Reader) error {
|
||||
}
|
||||
|
||||
sn, err := self.Parse(r)
|
||||
|
||||
var sperr *self.ParseError
|
||||
if errors.As(err, &sperr) {
|
||||
var perr ParseError
|
||||
perr.Input = sperr.Input
|
||||
perr.Offset = sperr.Offset
|
||||
perr.Line = sperr.Line
|
||||
perr.Column = sperr.Column
|
||||
perr.Definition = sperr.Definition
|
||||
return &perr
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user