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
|
||||||
.coverprofile-cmd
|
.coverprofile-cmd
|
||||||
codecov
|
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')
|
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
|
.PHONY: cpu.out
|
||||||
|
|
||||||
@ -13,9 +15,16 @@ imports: $(sources)
|
|||||||
@echo imports
|
@echo imports
|
||||||
@goimports -w $(sources)
|
@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
|
||||||
go build -o cmd/treerack/treerack ./cmd/treerack
|
go build -o .build/treerack ./cmd/treerack
|
||||||
|
|
||||||
install: $(sources)
|
install: $(sources)
|
||||||
go install ./cmd/treerack
|
go install ./cmd/treerack
|
||||||
@ -91,6 +100,17 @@ check-generate: $(sources) $(parsers)
|
|||||||
@mv headexported.go.backup headexported.go
|
@mv headexported.go.backup headexported.go
|
||||||
@mv self/self.go.backup self/self.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)
|
check: build $(parsers)
|
||||||
go test -test.short -run ^Test
|
go test -test.short -run ^Test
|
||||||
go test ./cmd/treerack -test.short -run ^Test
|
go test ./cmd/treerack -test.short -run ^Test
|
||||||
@ -144,6 +164,10 @@ clean:
|
|||||||
rm -f cpu.out
|
rm -f cpu.out
|
||||||
rm -f .coverprofile
|
rm -f .coverprofile
|
||||||
go clean -i ./...
|
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
|
ci-trigger: deps checkfmt build checkall
|
||||||
ifeq ($(TRAVIS_BRANCH)_$(TRAVIS_PULL_REQUEST), master_false)
|
ifeq ($(TRAVIS_BRANCH)_$(TRAVIS_PULL_REQUEST), master_false)
|
||||||
|
|||||||
@ -1,49 +1,50 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.squareroundforest.org/arpio/treerack"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
type checkOptions struct {
|
type checkOptions struct {
|
||||||
command *commandOptions
|
|
||||||
syntax *fileOptions
|
// Syntax specifies the filename of the syntax definition file.
|
||||||
input *fileOptions
|
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 {
|
// check parses input content against the provided syntax definition and fails if the input does not match.
|
||||||
var o checkOptions
|
// Syntax can be provided via a filename option or an inline string option. Input can be provided via a filename
|
||||||
o.command = initOptions(checkUsage, checkExample, positionalInputUsage, args)
|
// option, a positional argument filename, an inline string option, or piped from standard input.
|
||||||
o.syntax = &fileOptions{typ: "syntax", flagSet: o.command.flagSet, positionalDoc: positionalInputUsage}
|
func check(o checkOptions, stdin io.Reader, args ...string) error {
|
||||||
o.input = &fileOptions{typ: "input", flagSet: o.command.flagSet, positionalDoc: positionalInputUsage}
|
syntax, finalizeSyntax, err := initInput(o.Syntax, o.SyntaxString, nil, nil)
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stderr(err)
|
return err
|
||||||
return -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
package main
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"bytes"
|
||||||
var checkFailureTests = []mainTest{
|
"code.squareroundforest.org/arpio/treerack"
|
||||||
{
|
"errors"
|
||||||
title: "invalid flag",
|
"os"
|
||||||
args: []string{
|
"testing"
|
||||||
"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",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheck(t *testing.T) {
|
func TestCheck(t *testing.T) {
|
||||||
runMainTest(t, mainTest{
|
t.Run("no syntax", func(t *testing.T) {
|
||||||
title: "help",
|
o := checkOptions{Input: "bar_test.txt"}
|
||||||
args: []string{
|
if err := check(o, nil); !errors.Is(err, errNoInput) {
|
||||||
"treerack", "check", "-help",
|
t.Fatal()
|
||||||
},
|
}
|
||||||
stdout: []string{
|
|
||||||
wrapLines(checkUsage),
|
|
||||||
"-syntax",
|
|
||||||
"-syntax-string",
|
|
||||||
"-input",
|
|
||||||
"-input-string",
|
|
||||||
wrapLines(positionalInputUsage),
|
|
||||||
wrapLines(checkExample),
|
|
||||||
wrapLines(docRef),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
runMainTest(t, checkFailureTests...)
|
t.Run("too many syntaxes", func(t *testing.T) {
|
||||||
runMainTest(t, checkTests...)
|
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
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.squareroundforest.org/arpio/treerack"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
type checkSyntaxOptions struct {
|
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 {
|
// checkSyntax validates a syntax definition. The syntax may be provided via a file path (using an option or a
|
||||||
var o checkSyntaxOptions
|
// positional argument), an inline string, or piped from standard input.
|
||||||
o.command = initOptions(checkSyntaxUsage, checkSyntaxExample, positionalSyntaxUsage, args)
|
func checkSyntax(o checkSyntaxOptions, stdin io.Reader, args ...string) error {
|
||||||
o.syntax = &fileOptions{typ: "syntax", flagSet: o.command.flagSet, positionalDoc: positionalSyntaxUsage}
|
syntax, finalize, err := initInput(o.Syntax, o.SyntaxString, stdin, args)
|
||||||
|
if err != nil {
|
||||||
o.command.stringFlag(&o.syntax.inline, "syntax-string", syntaxStringUsage)
|
return err
|
||||||
o.command.stringFlag(&o.syntax.fileName, "syntax", syntaxFileUsage)
|
|
||||||
|
|
||||||
if o.command.help() {
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := o.command.parseArgs(); code != 0 {
|
defer finalize()
|
||||||
return code
|
s := &treerack.Syntax{}
|
||||||
|
if err := s.ReadSyntax(syntax); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.syntax.positional = o.command.flagSet.Args()
|
return s.Init()
|
||||||
s, code := o.syntax.openSyntax()
|
|
||||||
if code != 0 {
|
|
||||||
return code
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.Init(); err != nil {
|
|
||||||
stderr(err)
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,166 +1,76 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"bytes"
|
||||||
var checkSyntaxFailureTests = []mainTest{
|
"code.squareroundforest.org/arpio/treerack"
|
||||||
{
|
"errors"
|
||||||
title: "invalid flag",
|
"os"
|
||||||
args: []string{
|
"testing"
|
||||||
"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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckSyntax(t *testing.T) {
|
func TestCheckSyntax(t *testing.T) {
|
||||||
runMainTest(t, mainTest{
|
t.Run("too many inputs", func(t *testing.T) {
|
||||||
title: "help",
|
o := checkSyntaxOptions{Syntax: "foo_test.treerack", SyntaxString: `foo = "42"`}
|
||||||
args: []string{
|
if err := checkSyntax(o, nil); !errors.Is(err, errMultipleInputs) {
|
||||||
"treerack", "check-syntax", "-help",
|
t.Fatal()
|
||||||
},
|
}
|
||||||
stdout: []string{
|
|
||||||
wrapLines(checkSyntaxUsage),
|
|
||||||
"-syntax",
|
|
||||||
"-syntax-string",
|
|
||||||
wrapLines(positionalSyntaxUsage),
|
|
||||||
wrapLines(checkSyntaxExample),
|
|
||||||
wrapLines(docRef),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
runMainTest(t, checkSyntaxFailureTests...)
|
t.Run("empty filename", func(t *testing.T) {
|
||||||
runMainTest(t, checkSyntaxTests...)
|
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
|
package main
|
||||||
|
|
||||||
import "code.squareroundforest.org/arpio/treerack"
|
import (
|
||||||
|
"code.squareroundforest.org/arpio/treerack"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
type generateOptions struct {
|
type generateOptions struct {
|
||||||
command *commandOptions
|
|
||||||
syntax *fileOptions
|
// Syntax specifies the filename of the syntax definition file.
|
||||||
packageName string
|
Syntax string
|
||||||
export bool
|
|
||||||
|
// 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 {
|
// generate generates Go code that can parse arbitrary input with the provided syntax, and can be used embedded
|
||||||
var o generateOptions
|
// in an application.
|
||||||
o.command = initOptions(generateUsage, generateExample, positionalSyntaxUsage, args)
|
//
|
||||||
o.syntax = &fileOptions{typ: "syntax", flagSet: o.command.flagSet, positionalDoc: positionalSyntaxUsage}
|
// The syntax may be provided via a file path (using an option or a positional argument), an
|
||||||
|
// inline string, or piped from standard input.
|
||||||
o.command.boolFlag(&o.export, "export", exportUsage)
|
func generate(o generateOptions, stdin io.Reader, stdout io.Writer, args ...string) error {
|
||||||
o.command.stringFlag(&o.packageName, "package-name", packageNameUsage)
|
syntax, finalizeSyntax, err := initInput(o.Syntax, o.SyntaxString, stdin, args)
|
||||||
o.command.stringFlag(&o.syntax.inline, "syntax-string", syntaxStringUsage)
|
if err != nil {
|
||||||
o.command.stringFlag(&o.syntax.fileName, "syntax", syntaxFileUsage)
|
return err
|
||||||
|
|
||||||
if o.command.help() {
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := o.command.parseArgs(); code != 0 {
|
defer finalizeSyntax()
|
||||||
return code
|
s := &treerack.Syntax{}
|
||||||
|
if err := s.ReadSyntax(syntax); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.syntax.positional = o.command.flagSet.Args()
|
if err := s.Init(); err != nil {
|
||||||
s, code := o.syntax.openSyntax()
|
return err
|
||||||
if code != 0 {
|
|
||||||
return code
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var g treerack.GeneratorOptions
|
var genOpt treerack.GeneratorOptions
|
||||||
g.PackageName = o.packageName
|
genOpt.PackageName = o.PackageName
|
||||||
g.Export = o.export
|
genOpt.Export = o.Export
|
||||||
|
if err := s.Generate(genOpt, stdout); err != nil {
|
||||||
if err := s.Generate(g, wout); err != nil {
|
return err
|
||||||
stderr(err)
|
|
||||||
return -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,117 +1,155 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"bytes"
|
||||||
var generateFailureTests = convertTests("generate", checkSyntaxFailureTests)
|
"code.squareroundforest.org/arpio/treerack"
|
||||||
|
"errors"
|
||||||
var generateTests = []mainTest{
|
"os"
|
||||||
{
|
"strings"
|
||||||
title: "failing output",
|
"testing"
|
||||||
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",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerate(t *testing.T) {
|
func TestGenerate(t *testing.T) {
|
||||||
runMainTest(t, mainTest{
|
t.Run("too many inputs", func(t *testing.T) {
|
||||||
title: "help",
|
var out bytes.Buffer
|
||||||
args: []string{
|
o := generateOptions{Syntax: "foo_test.treerack", SyntaxString: `foo = "42"`}
|
||||||
"treerack", "generate", "-help",
|
if err := generate(o, nil, &out); !errors.Is(err, errMultipleInputs) {
|
||||||
},
|
t.Fatal()
|
||||||
stdout: []string{
|
}
|
||||||
wrapLines(generateUsage),
|
|
||||||
"-syntax",
|
|
||||||
"-syntax-string",
|
|
||||||
"-export",
|
|
||||||
"-package-name",
|
|
||||||
wrapLines(positionalSyntaxUsage),
|
|
||||||
wrapLines(generateExample),
|
|
||||||
wrapLines(docRef),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
runMainTest(t, generateFailureTests...)
|
t.Run("empty filename", func(t *testing.T) {
|
||||||
runMainTest(t, generateTests...)
|
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
|
package main
|
||||||
|
|
||||||
import "os"
|
import . "code.squareroundforest.org/arpio/wand"
|
||||||
|
|
||||||
func mainHelp() {
|
|
||||||
stdout(summary)
|
|
||||||
stdout()
|
|
||||||
stdout(commandsHelp)
|
|
||||||
stdout()
|
|
||||||
stdout(docRef)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) == 1 {
|
checkSyntax := Args(Command("check-syntax", checkSyntax), 0, 1)
|
||||||
stderr("missing command")
|
check := Args(Command("check", check), 0, 1)
|
||||||
stderr()
|
show := Args(Command("show", show), 0, 1)
|
||||||
stderr(commandsHelp)
|
generate := Args(Command("generate", generate), 0, 1)
|
||||||
stderr()
|
Exec(Group("treerack", checkSyntax, check, show, generate))
|
||||||
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:]))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,14 +3,28 @@ package main
|
|||||||
import (
|
import (
|
||||||
"code.squareroundforest.org/arpio/treerack"
|
"code.squareroundforest.org/arpio/treerack"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type showOptions struct {
|
type showOptions struct {
|
||||||
command *commandOptions
|
|
||||||
syntax *fileOptions
|
// Syntax specifies the filename of the syntax definition file.
|
||||||
input *fileOptions
|
Syntax string
|
||||||
pretty bool
|
|
||||||
indent 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 {
|
type node struct {
|
||||||
@ -18,10 +32,10 @@ type node struct {
|
|||||||
From int `json:"from"`
|
From int `json:"from"`
|
||||||
To int `json:"to"`
|
To int `json:"to"`
|
||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitempty"`
|
||||||
Nodes []*node `json:"nodes,omitempty"`
|
Nodes []node `json:"nodes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapNode(n *treerack.Node) *node {
|
func mapNode(n *treerack.Node) node {
|
||||||
var nn node
|
var nn node
|
||||||
nn.Name = n.Name
|
nn.Name = n.Name
|
||||||
nn.From = n.From
|
nn.From = n.From
|
||||||
@ -29,76 +43,67 @@ func mapNode(n *treerack.Node) *node {
|
|||||||
|
|
||||||
if len(n.Nodes) == 0 {
|
if len(n.Nodes) == 0 {
|
||||||
nn.Text = n.Text()
|
nn.Text = n.Text()
|
||||||
return &nn
|
return nn
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range n.Nodes {
|
for i := range n.Nodes {
|
||||||
nn.Nodes = append(nn.Nodes, mapNode(n.Nodes[i]))
|
nn.Nodes = append(nn.Nodes, mapNode(n.Nodes[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &nn
|
return nn
|
||||||
}
|
}
|
||||||
|
|
||||||
func show(args []string) int {
|
// show input content against a provided syntax definition and outputs the resulting AST (Abstract Syntax Tree)
|
||||||
var o showOptions
|
// in JSON format. Syntax can be provided via a filename option or an inline string option. Input can be
|
||||||
o.command = initOptions(showUsage, showExample, positionalInputUsage, args)
|
// provided via a filename option, a positional argument filename, an inline string option, or piped from
|
||||||
o.syntax = &fileOptions{typ: "syntax", flagSet: o.command.flagSet, positionalDoc: positionalInputUsage}
|
// standard input.
|
||||||
o.input = &fileOptions{typ: "input", flagSet: o.command.flagSet, positionalDoc: positionalInputUsage}
|
func show(o showOptions, stdin io.Reader, stdout io.Writer, args ...string) error {
|
||||||
|
syntax, finalizeSyntax, err := initInput(o.Syntax, o.SyntaxString, nil, nil)
|
||||||
o.command.stringFlag(&o.syntax.inline, "syntax-string", syntaxStringUsage)
|
if err != nil {
|
||||||
o.command.stringFlag(&o.syntax.fileName, "syntax", syntaxFileUsage)
|
return err
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if code := o.command.parseArgs(); code != 0 {
|
defer finalizeSyntax()
|
||||||
return code
|
input, finalizeInput, err := initInput(o.Input, o.InputString, stdin, args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
s, code := o.syntax.openSyntax()
|
defer finalizeInput()
|
||||||
if code != 0 {
|
s := &treerack.Syntax{}
|
||||||
return code
|
if err := s.ReadSyntax(syntax); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o.input.positional = o.command.flagSet.Args()
|
if err := s.Init(); err != nil {
|
||||||
input, code := o.input.open()
|
return err
|
||||||
if code != 0 {
|
|
||||||
return code
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defer input.Close()
|
|
||||||
|
|
||||||
n, err := s.Parse(input)
|
n, err := s.Parse(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stderr(err)
|
return err
|
||||||
return -1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nn := mapNode(n)
|
nn := mapNode(n)
|
||||||
|
encode := json.Marshal
|
||||||
marshal := json.Marshal
|
if o.Pretty || o.Indent != "" {
|
||||||
if o.pretty || o.indent != "" {
|
if o.Indent == "" {
|
||||||
if o.indent == "" {
|
o.Indent = " "
|
||||||
o.indent = " "
|
|
||||||
}
|
}
|
||||||
|
|
||||||
marshal = func(n interface{}) ([]byte, error) {
|
encode = func(a any) ([]byte, error) {
|
||||||
return json.MarshalIndent(n, "", o.indent)
|
return json.MarshalIndent(a, "", o.Indent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := marshal(nn)
|
b, err := encode(nn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
stderr(err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout(string(b))
|
if _, err := stdout.Write(b); err != nil {
|
||||||
return 0
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,144 +1,232 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"bytes"
|
||||||
var showFailureTests = convertTests("show", checkFailureTests)
|
"code.squareroundforest.org/arpio/treerack"
|
||||||
|
"errors"
|
||||||
var showTests = []mainTest{
|
"os"
|
||||||
{
|
"testing"
|
||||||
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"`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShow(t *testing.T) {
|
func TestShow(t *testing.T) {
|
||||||
runMainTest(t, mainTest{
|
t.Run("no syntax", func(t *testing.T) {
|
||||||
title: "help",
|
var out bytes.Buffer
|
||||||
args: []string{
|
o := showOptions{Input: "bar_test.txt"}
|
||||||
"treerack", "show", "-help",
|
if err := show(o, nil, &out); !errors.Is(err, errNoInput) {
|
||||||
},
|
t.Fatal()
|
||||||
stdout: []string{
|
}
|
||||||
wrapLines(showUsage),
|
|
||||||
"-syntax",
|
|
||||||
"-syntax-string",
|
|
||||||
"-input",
|
|
||||||
"-input-string",
|
|
||||||
"-pretty",
|
|
||||||
"-indent",
|
|
||||||
wrapLines(positionalInputUsage),
|
|
||||||
wrapLines(showExample),
|
|
||||||
wrapLines(docRef),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
runMainTest(t, showFailureTests...)
|
t.Run("too many syntaxes", func(t *testing.T) {
|
||||||
runMainTest(t, showTests...)
|
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
|
module code.squareroundforest.org/arpio/treerack
|
||||||
|
|
||||||
go 1.24.6
|
go 1.25.3
|
||||||
|
|
||||||
require golang.org/x/crypto v0.41.0
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
code.squareroundforest.org/arpio/notation v0.0.0-20241225183158-af3bd591a174 // indirect
|
code.squareroundforest.org/arpio/wand v0.0.0-20260113225451-514cd3375d96
|
||||||
github.com/iancoleman/strcase v0.3.0 // indirect
|
github.com/iancoleman/strcase v0.3.0
|
||||||
golang.org/x/sys v0.35.0 // indirect
|
)
|
||||||
golang.org/x/term v0.34.0 // indirect
|
|
||||||
|
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/bind v0.0.0-20251105181644-3443251be2d5 h1:SIgLIawD6Vv7rAvUobpVshLshdwFEJ0NOUrWpheS088=
|
||||||
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/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 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
|
||||||
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
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/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
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 {
|
func intsContain(is []int, i int) bool {
|
||||||
for _, ii := range is {
|
for _, ii := range is {
|
||||||
if ii == i {
|
if ii == i {
|
||||||
@ -286,6 +282,18 @@ func (s *Syntax) ReadSyntax(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sn, err := self.Parse(r)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user