Working commit to preserve refactoring

pull/66/head
rwxrob 2 years ago
parent a50179bb1d
commit 51d622e1c1
No known key found for this signature in database
GPG Key ID: 2B9111F33082AE77

@ -295,6 +295,18 @@ want the specific reasons.
also consistent with the capital "Z" import name of the `bonzai`
package.
* **Regular Expressions Over Usage Notation.**
Bonzai takes a fundamentally different approach to the command line
and usage documentation. Any command line is a minimal domain specific
language. As such, usage notation simple is not enough. Regular
expressions allow the quick understanding of what is allowed and
should become mandatory learning in a world of minimal domain specific
grammars. Only the most basic regular expressions are required to
produce rich usage strings for any Bonzai command. However, those
wanting traditional usage notation can easily override the
`DefaultUsageFunc` with their own.
## Style Guidelines
* Everything through `go fmt` or equiv, no exceptions

@ -4,8 +4,8 @@ import (
"fmt"
"strings"
Z "github.com/rwxrob/bonzai"
"github.com/rwxrob/bonzai/inc/help"
"github.com/rwxrob/bonzai/help"
Z "github.com/rwxrob/bonzai/z"
"github.com/rwxrob/term"
)

@ -1,66 +0,0 @@
// Copyright 2022 Robert S. Muhlestein.
// SPDX-License-Identifier: Apache-2.0
package Z_test
import (
"fmt"
"os"
Z "github.com/rwxrob/bonzai"
)
func ExampleArgsFrom() {
fmt.Printf("%q\n", Z.ArgsFrom(`greet hi french`))
fmt.Printf("%q\n", Z.ArgsFrom(`greet hi french `))
// Output:
// ["greet" "hi" "french"]
// ["greet" "hi" "french" ""]
}
func ExampleArgsOrIn_read_Nil() {
orig := os.Stdin
defer func() { os.Stdin = orig }()
os.Stdin, _ = os.Open(`testdata/in`)
fmt.Println(Z.ArgsOrIn(nil))
// Output:
// some thing
}
func ExampleArgsOrIn_read_Zero_Args() {
orig := os.Stdin
defer func() { os.Stdin = orig }()
os.Stdin, _ = os.Open(`testdata/in`)
fmt.Println(Z.ArgsOrIn([]string{}))
// Output:
// some thing
}
func ExampleArgsOrIn_args_Joined() {
fmt.Println(Z.ArgsOrIn([]string{"some", "thing"}))
// Output:
// some thing
}
func ExampleEsc() {
fmt.Println(Z.Esc("|&;()<>![]"))
fmt.Printf("%q", Z.Esc(" \n\r"))
// Output:
// \|\&\;\(\)\<\>\!\[\]
// "\\ \\\n\\\r"
}
func ExampleEscAll() {
list := []string{"so!me", "<here>", "other&"}
fmt.Println(Z.EscAll(list))
// Output:
// [so\!me \<here\> other\&]
}

@ -1,24 +1,3 @@
module github.com/rwxrob/Z
module github.com/rwxrob/bonzai
go 1.18
require (
github.com/rwxrob/bonzai v0.0.39
github.com/rwxrob/config v0.3.1
github.com/rwxrob/fn v0.3.0
github.com/rwxrob/fs v0.4.3
github.com/rwxrob/json v0.4.1
github.com/rwxrob/structs v0.5.0
github.com/rwxrob/term v0.1.5
golang.org/x/mod v0.5.1
)
require (
github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/rwxrob/to v0.2.1 // indirect
github.com/rwxrob/y2j v0.3.1 // indirect
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 // indirect
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

@ -1,37 +0,0 @@
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rwxrob/bonzai v0.0.39 h1:EJ1aVVVwnm4mdPKJEeCLI3wVttgbD8ulggXKZiwEO4w=
github.com/rwxrob/bonzai v0.0.39/go.mod h1:PyKG44H68o3DZ2Xh6ouViGW9IAbYZVw0OYqLMdCM1RI=
github.com/rwxrob/config v0.3.1 h1:YwnPEuEFDZz7gD5ZJwYkYEF/oSjvM9ryXV6oK+aDvJc=
github.com/rwxrob/config v0.3.1/go.mod h1:aqV/tWGH+Tz2ciADZDDYoodAniZ2iQTm8WZNM0wRyCA=
github.com/rwxrob/fn v0.3.0 h1:R4kcZhInEc9Fn3lsWbn3O6ZOoZ/D43Y1l3SS5Nxm1wc=
github.com/rwxrob/fn v0.3.0/go.mod h1:omPqOqEB+dDna09z5pi5YFxq4IZqDvv3wFPUCES5LvY=
github.com/rwxrob/fs v0.4.3 h1:ntu9TZnk7NHd1Yen+p4+xruBmkQMugKtFU0OLfAMa+M=
github.com/rwxrob/fs v0.4.3/go.mod h1:vO8AeluD7rnrO7zC54745xTEBFgHPUpHL0hbp1NnsVo=
github.com/rwxrob/json v0.4.1 h1:b4ToZe4mrQO8rRL/kRFglzZszyZZnGv6JRHj6jrI3f4=
github.com/rwxrob/json v0.4.1/go.mod h1:DU3TQKCWY4bK7sQ0wu80cRmTs96b6M//OYvT7Eg2mJA=
github.com/rwxrob/structs v0.5.0 h1:pjLsfyYHS+gB1CtzRj3H39wRYL4lI5pTpFf8kl91guw=
github.com/rwxrob/structs v0.5.0/go.mod h1:2gIte2ZI9zUok6q6F3v3l41ZXo7Zg5Kf1GUTP2+pXyQ=
github.com/rwxrob/term v0.1.5 h1:jKvNgEYlsT6sLRqIuR9ITcNNh4woGkWxcAe71pPJReQ=
github.com/rwxrob/term v0.1.5/go.mod h1:IVE7hG+VQlM4R+kS4pY6uhfMHoG0QECrZF7d7bKcdsk=
github.com/rwxrob/to v0.2.1 h1:ZYBNEa8LJT5VDQUHm2wSwiRf61xSqU87UqbYvitV3eY=
github.com/rwxrob/to v0.2.1/go.mod h1:8qdgCWkh50Avs8sRpV6/P7lAQgVf3KLRSKMZahV/W48=
github.com/rwxrob/y2j v0.3.1 h1:qOCU7J6g0Q/7KlLAabCMLx6/wG1/NelG6QTOVpESAQg=
github.com/rwxrob/y2j v0.3.1/go.mod h1:/3eS+LPnOF1F2VfoqZr3Upkr8q4ByziAi3eB6FIgzoE=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY=
golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -1,52 +0,0 @@
package help
/*
var Help = &Z.Cmd{
Name: `help`,
Aliases: []string{"h"},
Call: func(x *Z.Cmd, args ...string) error {
var buf string
buf += util.Emph("**NAME**", 0, -1) + "\n " + x.Title() + "\n\n"
buf += util.Emph("**SYNOPSIS**", 0, -1) + "\n " + x.Name + " " + x.Usage + "\n\n"
if len(x.Commands.M) > 0 {
buf += util.Emph("**COMMANDS**", 0, -1) + "\n" + x.Titles(7, 20) + "\n\n"
}
if len(x.Description) > 0 {
buf +=
util.Emph("**DESCRIPTION**", 0, -1) + "\n" +
util.Emph(x.Description, 7, 65) + "\n\n"
}
if x.Source != "" || x.Issues != "" || x.Site != "" {
buf += util.Emph("**LINKS**", 0, -1) + "\n"
if x.Site != "" {
buf += " Site: " + x.Site + "\n"
}
if x.Source != "" {
buf += " Source: " + x.Source + "\n"
}
if x.Issues != "" {
buf += " Issues: " + x.Issues + "\n"
}
buf += "\n"
}
if x.Copyright != "" {
buf += util.Emph("**LEGAL**", 0, -1) + "\n" + util.Indent(x.Legal(), 7) + "\n\n"
}
return buf
},
}
*/

@ -4,33 +4,32 @@
package help
import (
"log"
"fmt"
"unicode"
Z "github.com/rwxrob/bonzai"
"github.com/rwxrob/bonzai/comp"
Z "github.com/rwxrob/bonzai/z"
"github.com/rwxrob/fn/filt"
"github.com/rwxrob/fn/maps"
"github.com/rwxrob/term"
"github.com/rwxrob/to"
)
var OBracketed = term.Under
var CBracketed = term.Reset
// Cmd provides help documentation for the caller allowing the specific
// section of help wanted to be passed as a tab-completable parameter.
var Cmd = &Z.Cmd{
Name: `help`,
Name: `help`,
Summary: `display help similar to man page format`,
Params: []string{
"name", "title", "summary", "params", "commands", "description",
"examples", "legal", "copyright", "license", "version",
},
Completer: helpCompleter,
Call: func(caller *Z.Cmd, args ...string) error {
section := "all"
if len(args) > 0 {
section = args[0]
Call: func(x *Z.Cmd, args ...string) error {
// TODO detect if local web help is preferred over terminal
if len(args) == 0 {
args = append(args, "all")
}
log.Printf("would show help about %v %v", caller.Name, section)
return nil
},
}
@ -62,26 +61,188 @@ func helpCompleter(x comp.Command, args ...string) []string {
return filt.HasPrefix(list, args[0])
}
// Render renders the incoming Help markup string for a curses terminal
// detecting if the terminal is interactive and if not rendering as
// plain text instead.
func Render(in string) string {
out := ""
for i := 0; i < len([]rune(in)); i++ {
cur := in[i]
switch cur {
// <bracketed>
case '<':
out += OBracketed
// ForTerminal converts the collective help documentation of the given
// command into curses terminal-friendly output which minics UNIX man
// pages as much as possible. Documentation text is expected to be in
// standard BonzaiMark markup (see Format).
//
// If the special "all" section is passed all sections will be
// displayed.
//
// If the "less" pager is detected and the terminal is interactive
// (stdout is to a terminal/tty) will call Z.SysExec to transfer control
// to it and send the output to it making it virtual indistinguishable
// from "man" page output.
//
// If the terminal is non-interactive, simple prints as
// 80-column-wrapped plain text.
func ForTerminal(x *Z.Cmd, section string) {
switch section {
case "name":
fmt.Println(x.Name)
case "summary":
fmt.Println(x.Summary)
case "all":
fmt.Println(Format("**NAME**"))
fmt.Println(to.IndentWrapped(x.Title(), 7, 80) + "\n")
fmt.Println(Format("**SYNOPSIS**"))
// if commands [COMMAND]
// if commands and parameters [COMMAND|PARAM]
// if just parameters [PARAM]
//fmt.Println(to.IndentWrapped(
fmt.Println(Format("**PARAMS**"))
fmt.Println(Format("**COMMANDS**"))
//Format("**"+x.Name+"** ")+x.(), 7, 80) + "\n")
fmt.Println(Format("**DESCRIPTION**"))
//ForTerminal(x, "synopsis")
default:
if v, has := x.Other[section]; has {
fmt.Println(Format(v))
}
}
//"title", "summary", "params", "commands", "description",
//"examples", "legal", "copyright", "license", "version",
}
// Format is called by Render when producing terminal formatted output
// containing all the BonzaiMark help documentation for a given Bonzai
// command. BonzaiMark is a very minimal subset of CommonMark.
//
// Initial and trailing blank lines and lines with only whitespace will
// be stripped as well as the initial number of spaces for the first
// line of indentation. This allows markup strings to be written in very
// readable ways even when embedded within source code (preferably with
// backtick string literal notation).
//
// Any line beginning with at least four spaces (after trimming
// indentation) will be kept verbatim.
//
// Emphasis will be applied as possible if the following markup is
// detected:
//
// *italic*
// **bold**
// ***bolditalic***
// <under> (brackets remain)
//
// Note that the format of the emphasis might not always be as
// specifically named. For example, most terminal do not support italic
// fonts and so will instead underline *italic* text, so (as specified
// in HTML5 for <i>, for example) these format names should be taken to
// mean their semantic equivalents.
//
// For historic reasons, the following environment variables will be
// detected and used to replace the specified escapes (see rwxrob/term
// package for details):
//
// LESS_TERMCAP_mb
// LESS_TERMCAP_md
// LESS_TERMCAP_me
// LESS_TERMCAP_se
// LESS_TERMCAP_so
// LESS_TERMCAP_ue
// LESS_TERMCAP_us
//
func Format(markup string) string {
out := to.Dedented(markup)
out = to.Wrapped(out, 80)
out = Emphasize(out)
return out
}
// Emphasize replaces minimal Markdown-like syntax with *Italic*,
// **Bold**, ***BoldItalic***, and <bracketed>
func Emphasize(buf string) string {
// italic = `<italic>`
// bold = `<bold>`
// bolditalic = `<bolditalic>`
// reset = `<reset>`
term.EmphFromLess()
nbuf := []rune{}
prev := ' '
opentok := false
otok := ""
closetok := false
ctok := ""
for i := 0; i < len([]rune(buf)); i++ {
r := []rune(buf)[i]
if r == '<' {
nbuf = append(nbuf, '<')
nbuf = append(nbuf, []rune(term.Under)...)
for {
i++
r = rune(buf[i])
if r == '>' {
i++
break
}
nbuf = append(nbuf, r)
}
nbuf = append(nbuf, []rune(term.Reset)...)
nbuf = append(nbuf, '>')
i--
continue
}
if r != '*' {
if opentok {
tokval := " "
if !unicode.IsSpace(r) {
switch otok {
case "*":
tokval = term.Italic
case "**":
tokval = term.Bold
case "***":
tokval = term.BoldItalic
}
} else {
tokval = otok
}
nbuf = append(nbuf, []rune(tokval)...)
opentok = false
otok = ""
}
if closetok {
nbuf = append(nbuf, []rune(term.Reset)...) // practical, not perfect
ctok = ""
closetok = false
}
out += CBracketed
prev = r
nbuf = append(nbuf, r)
continue
}
// everything else for '*'
if unicode.IsSpace(prev) || opentok {
opentok = true
otok += string(r)
continue
}
// only closer conditions remain
if !unicode.IsSpace(prev) {
closetok = true
ctok += string(r)
continue
}
// nothing special
closetok = false
nbuf = append(nbuf, r)
}
// for tokens at the end of a block
if closetok {
nbuf = append(nbuf, []rune(term.Reset)...)
}
return string(out)
}
func parseBracketed() {
return string(nbuf)
}

@ -1,13 +1,103 @@
package help
package help_test
import (
"fmt"
"testing"
"os"
"github.com/rwxrob/bonzai/help"
Z "github.com/rwxrob/bonzai/z"
"github.com/rwxrob/term"
"github.com/rwxrob/term/esc"
)
func TestRender_bracketed(t *testing.T) {
oBracketed = "<>"
cBracketed = "</>"
out := render([]rune("<bracket - ed>"))
fmt.Println(out)
func ExampleFormat_remove_Initial_Blanks() {
fmt.Printf("%q\n", help.Format("\n \n\n \n some"))
// Output:
// "some"
}
func ExampleFormat_wrapping() {
fmt.Println(help.Format(`
Here is a bunch of stuff just to fill the line beyond 80 columns so that it will wrap when it is supposed to and right now
as well if there was a hard return in the middle of a line.
`))
// Output:
// Here is a bunch of stuff just to fill the line beyond 80 columns so that it will
// wrap when it is supposed to and right now
// as well if there was a hard return in the middle of a line.
}
func ExampleEmphasize() {
/*
export LESS_TERMCAP_mb="" # magenta
export LESS_TERMCAP_md="" # yellow
export LESS_TERMCAP_me="" # "0m"
export LESS_TERMCAP_se="" # "0m"
export LESS_TERMCAP_so="" # blue
export LESS_TERMCAP_ue="" # "0m"
export LESS_TERMCAP_us="" # underline
*/
os.Setenv("LESS_TERMCAP_mb", esc.Magenta)
os.Setenv("LESS_TERMCAP_md", esc.Yellow)
os.Setenv("LESS_TERMCAP_me", esc.Reset)
os.Setenv("LESS_TERMCAP_se", esc.Reset)
os.Setenv("LESS_TERMCAP_so", esc.Blue)
os.Setenv("LESS_TERMCAP_ue", esc.Reset)
os.Setenv("LESS_TERMCAP_us", esc.Under)
term.EmphFromLess()
fmt.Printf("%q\n", help.Emphasize("*italic*"))
fmt.Printf("%q\n", help.Emphasize("**bold**"))
fmt.Printf("%q\n", help.Emphasize("**bolditalic**"))
fmt.Printf("%q\n", help.Emphasize("<under>"))
// Output:
// "\x1b[4mitalic\x1b[0m"
// "\x1b[33mbold\x1b[0m"
// "\x1b[33mbolditalic\x1b[0m"
// "<\x1b[4munder\x1b[0m>"
}
func ExampleCmd_name() {
x := &Z.Cmd{
Name: "foo",
}
help.ForTerminal(x, "name")
// Output:
// foo
}
func ExampleCmd_summary() {
x := &Z.Cmd{
Summary: `foo all the things`,
}
help.ForTerminal(x, "summary")
// Output:
// foo all the things
}
func ExampleCmd_other() {
x := &Z.Cmd{
Other: map[string]string{"foo": `some foo text`},
}
help.ForTerminal(x, "foo")
// Output:
// some foo text
}
func ExampleCmd_all_Commands_and_Params() {
x := &Z.Cmd{
Name: `foo`,
Params: []string{"p1", "p2"},
Version: "v0.1.0",
Summary: `foo all the things`,
Other: map[string]string{"foo": `some foo text`},
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
}
help.ForTerminal(x, "all")
// Output:
// some foo text
}

@ -1,6 +0,0 @@
package z
// keep only compound expressions here
var WS = I{' ', '\n', '\t', '\r'}
var Digit = R{0, 9}

@ -1,182 +0,0 @@
// Copyright 2022 Robert S. Muhlestein.
// SPDX-License-Identifier: Apache-2.0
/*
Package z (often imported as "is") defines the Bonzai Parsing Expression
Grammar Notation (BPEGN) (aka "Bonzai Scanner Expect" language)
implemented entirely using Go types (mostly slices and structs). BPEGN
can be 100% transpiled to and from the Parsing Expression Grammer
Notation (PEGN). Code in one grammar and use the bonzai command to
easily generate the other.
Nodes and Expressions
Nodes (z.Nd) indicate something to be captured as a part of the resulting
abstract syntax tree. They are functionally equivalent to parenthesis in
regular expressions but with the obvious advantage of capturing a rooted
node tree instead of an array. Expressions (z.X) indicate a sequence to
be scanned but not captured unless the expression itself is within
a node (z.Nd).
Tokens
The BPEGN token strings are contained in the "tk" package can be used as
is. See the "tk" package for details.
Parameterized Struct and Set Slice Expressions
BPEGN uses Go structs and slices to represent scannable expressions with
obvious similarities to regular expressions. These expressions have
one-to-parity with PEGN expressions. Slices represent sets of
possibilities. Structs provide parameters for more complex expressions
and are are guaranteed never to change allowing them to be dependably
used in assignment without struct field names using Go's inline
composable syntax. Some editors may need configuring to allow this since
in general practice this can create subtle (but substantial) foot-guns
for maintainers.
Look-Ahead and Advancing Expressions
"Advancing" expressions will advance the scan to the end of the
expression match. "Look-ahead" expressions simply check for a match but
do not advance the scan. Developers should take careful note of the
difference in the documentation.
Composites
Composites are compound expressions composed of others. They represent
the tokens and classes from PEGN and other grammars and are designed to
simplify grammar development at a higher level. Pull requests are
welcome for missing, commonly used composite candidates.
Hook Functions
Hook functions are not strictly an expression type and are declared in
the scan package itself (to avoid a cyclical import dependency since it
is passed a scan.R). A Hook is passed the *scan.R and can return an error.
See scan.Hook for more information.
*/
package z
// ------------------------------- core -------------------------------
// P ("parse") is a named sequence of expressions that will be parsed
// and captured as a new Node and added to the scan.R.Nodes field
// effectively turning the scan.R into a parser as well. The first
// string must always be the name which can be any valid Go string. If
// any expression fails to match the scan fails. Otherwise, a new
// scan.Node is added under the current node and the scan proceeds.
// Nodes must either contain other nodes or no nodes at all. If the
// first item in the sequence after the name is not also a node (z.P)
// then the node is marked as "edge" (or "leaf") and any nodes detected
// further in the sequence will cause the scan to fail with a syntax
// error.
type P []any
// X ("expression") is a sequence of expressions. If any are not the
// scan fails. (Equal to (?foo) in regular expressions.)
type X []any
// ------------------------------- sets -------------------------------
// Y ("yes") is a set of positive lookahead expressions. If any are
// seen at the current cursor position the scan will proceed without
// consuming them (unlike z.O and z.I). If none are found the scan
// fails. (Equal to ampersand (&) in
// PEGN.)
type Y []any
// N ("not") is a set of negative lookahead expressions. If any are seen
// at the current cursor position the scan will fail and the scan is
// never advanced. This is useful when everything from one expression is
// wanted except for a few negative exceptions. (Equal to exclamation
// point (!) in PEGN.)
type N []any
// I ("in","include") is a set of advancing expressions. If any
// expression in the slice is found the scan advances to the end of that
// expression and continues. If none of the expressions is found the
// scan fails. Evaluation of expressions is always left to right
// allowing parser developers to prioritize common expressions at the
// beginning of the slice.
type I []any
// O is a set of optional advancing expressions. If any expression is
// found the scan is advanced (unlike is.It, which does not advance).
type O []any
// T ("to") is a set of advancing expressions that mark an exclusive
// boundary at which the scan should stop returning a cursor just before
// the boundary.
type T []any
// Ti ("to inclusive") is an inclusive version of z.T returning
// a cursor that points to the last rune of the boundary itself.
type Ti []any
// --------------------------- parameterized --------------------------
// MM ("minmax") is a parameterized advancing expression that matches an
// inclusive minimum and maximum count of the given expression (This).
type MM struct {
Min int
Max int
This any
}
// M ("min") is a parameterized advancing expression that matches an
// inclusive minimum number of the given expression item (This). Use
// within is.It to disable advancement.
type M struct {
Min int
This any
}
// M1 is shorthand for z.M{1,This}.
type M1 struct{ This any }
// C is a parameterized advancing expression that matches an exact count
// of the given expression (This). Use within is.It to disable
// advancement.
type C struct {
N int
This any
}
// C2 is shorthand for z.C{2,This}.
type C2 struct{ This any }
// C3 is shorthand for z.C{3,This}.
type C3 struct{ This any }
// C4 is shorthand for z.C{4,This}.
type C4 struct{ This any }
// C5 is shorthand for z.C{5,This}.
type C5 struct{ This any }
// C6 is shorthand for z.C{6,This}.
type C6 struct{ This any }
// C7 is shorthand for z.C{7,This}.
type C7 struct{ This any }
// C8 is shorthand for z.C{8,This}.
type C8 struct{ This any }
// C9 is shorthand for z.C{9,This}.
type C9 struct{ This any }
// A is short for z.C{tk.ANY}. (Mnemonic: "any", "asterisk")
type A struct {
N int
}
// R ("range") is a parameterized advancing expression that matches
// a single Unicode code point (rune, int32) from an inclusive
// consecutive set from First to Last (First,Last). (Mnemonic: "range",
// "rune")
type R struct {
First rune
Last rune
}

@ -1,3 +0,0 @@
# Bonzai Mark (Simple Subset of CommonMark)
<https://github.com/pegn/spec>

@ -1,383 +0,0 @@
package mark
import (
"bufio"
"log"
"os"
"strings"
"unicode"
"github.com/rwxrob/bonzai/term/esc"
)
// OpenItalic opens italic emphasis. Default: ANSI italic.
var OpenItalic = esc.Italic
// CloseItalic closes italic emphasis. Default: ANSI reset.
var CloseItalic = esc.Reset
// OpenBold opens bold emphasis. Default: ANSI bold.
var OpenBold = esc.Bold
// CloseBold closes bold emphasis. Default: ANSI reset.
var CloseBold = esc.Reset
// OpenBoldItalic opens bold italic emphasis. Default: ANSI bold italic.
var OpenBoldItalic = esc.BoldItalic
// CloseBoldItalic closes bold italic emphasis. Default: ANSI reset.
var CloseBoldItalic = esc.Reset
// OpenUnderline open underline emphasis. Default: ANSI underline.
var OpenUnderline = esc.Underline
// CloseUnderline closes underline emphasis. Default: ANSI reset.
var CloseUnderline = esc.Reset
/*
func init() {
if !term.IsTerminal() {
reset = ""
italic = ""
bold = ""
bolditalic = ""
underline = ""
return
}
emphFromLess()
}
*/
func emphFromLess() {
var x string
x = os.Getenv("LESS_TERMCAP_us")
if x != "" {
OpenItalic = x
}
x = os.Getenv("LESS_TERMCAP_md")
if x != "" {
OpenBold = x
}
x = os.Getenv("LESS_TERMCAP_mb")
if x != "" {
OpenBoldItalic = x
}
x = os.Getenv("LESS_TERMCAP_us")
if x != "" {
OpenUnderline = x
}
}
// FormatWrapped takes a command documentation format string (an
// extremely limited version of Markdown that is also Godoc friendly)
// and transforms it as follows:
//
// * Initial and trailing blank lines are removed.
//
// * Indentation is removed - the number of spaces preceeding the first
// word of the first line are ignored in every line (including raw
// text blocks).
//
// * Raw text ignored - any line beginning with four or more spaces
// (after convenience indentation is removed) will be kept as it is
// exactly (code examples, etc.) but should never exceed 80 characters
// (including the spaces).
//
// * Blocks are unwrapped - any non-blank (without three or less initial
// spaces) will be trimmed line following a line will be joined to the
// preceding line recursively (unless hard break).
//
// * Hard breaks kept - like Markdown any line that ends with two or
// more spaces will automatically force a line return.
//
// * URL links argument names and anything else within angle brackets
// (<url>), will trigger underline in both text blocks
// and usage sections.
//
// * Italic, Bold, and BoldItalic inline emphasis using one, two, or
// three stars respectively will be observed and cannot be intermixed
// or intra-word. Each opener must be preceded by a UNICODE space (or
// nothing) and followed by a non-space rune. Each closer must be
// preceded by a non-space rune and followed by a UNICODE space (or
// nothing).
//
// For historic reasons the following environment variables will be
// observed if found (and also provide color support for the less pager
// utility):
//
// * Italic LESS_TERMCAP_so
// * Bold LESS_TERMCAP_md
// * BoldItalic LESS_TERMCAP_mb
// * Underline LESS_TERMCAP_us
//
func FormatWrapped(input string, indent, width int) (output string) {
// this scanner could be waaaay more lexy
// but suits the need and clear to read
var strip int
var blockbuf string
// standard state machine approach
inblock := false
inraw := false
inhard := false
gotindent := false
scanner := bufio.NewScanner(strings.NewReader(input))
for scanner.Scan() {
txt := scanner.Text()
trimmed := strings.TrimSpace(txt)
// ignore blank lines
if !(inraw || inblock) && len(trimmed) == 0 {
continue
}
// infer the indent to strip for every line
if !gotindent && len(trimmed) > 0 {
for i, v := range txt {
if v != ' ' {
strip = i
gotindent = true
break
}
}
}
// strip convenience indent
if len(txt) >= strip {
txt = txt[strip:]
}
// raw block start
if !inblock && !inraw && len(txt) > 4 && txt[0:4] == " " && len(trimmed) > 0 {
inraw = true
output += "\n\n" + txt
continue
}
// in raw block
if inraw && len(txt) > 4 {
output += "\n" + txt
continue
}
// raw block end
if inraw && len(trimmed) == 0 {
inraw = false
continue
}
// another block line, join it
if inblock && len(trimmed) > 0 {
if len(txt) >= 2 && txt[len(txt)-2:] == " " {
inhard = true
}
space := " "
if inhard {
space = "\n"
}
blockbuf += space + trimmed
continue
}
// beginning of a new block
if !inblock && len(trimmed) > 0 {
inhard = false
inblock = true
if len(txt) >= 2 && txt[len(txt)-2:] == " " {
inhard = true
}
blockbuf = trimmed
continue
}
// end block
if inblock && len(trimmed) == 0 {
inblock = false
output += "\n\n" + Format(Wrap(blockbuf, width-strip-4))
continue
}
}
// flush last block
if inblock {
output += "\n\n" + Format(Wrap(blockbuf, width-strip-4))
}
output = Indent(strings.TrimSpace(output), indent)
return
}
// Format replaces minimal Markdown-like syntax with *Italic*,
// **Bold**, ***BoldItalic***, and <bracketed>
func Format(buf string) string {
// TODO add functional parser, for fun
log.Println("would Format")
return buf
}
// Indent indents each line the set number of spaces.
func Indent(buf string, spaces int) string {
nbuf := ""
scanner := bufio.NewScanner(strings.NewReader(buf))
scanner.Scan()
for n := 0; n < spaces; n++ {
nbuf += " "
}
nbuf += scanner.Text()
for scanner.Scan() {
nbuf += "\n"
for n := 0; n < spaces; n++ {
nbuf += " "
}
nbuf += scanner.Text()
}
return nbuf
}
// Wrapped is the same as Format but without any emphasis.
func Wrapped(input string, indent, width int) (output string) {
// this scanner could be waaaay more lexy
// but suits the need and clear to read
var strip int
var blockbuf string
// standard state machine approach
inblock := false
inraw := false
inhard := false
gotindent := false
scanner := bufio.NewScanner(strings.NewReader(input))
for scanner.Scan() {
txt := scanner.Text()
trimmed := strings.TrimSpace(txt)
// ignore blank lines
if !(inraw || inblock) && len(trimmed) == 0 {
continue
}
// infer the indent to strip for every line
if !gotindent && len(trimmed) > 0 {
for i, v := range txt {
if v != ' ' {
strip = i
gotindent = true
break
}
}
}
// strip convenience indent
if len(txt) >= strip {
txt = txt[strip:]
}
// raw block start
if !inblock && !inraw && len(txt) > 4 && txt[0:4] == " " && len(trimmed) > 0 {
inraw = true
output += "\n\n" + txt
continue
}
// in raw block
if inraw && len(txt) > 4 {
output += "\n" + txt
continue
}
// raw block end
if inraw && len(trimmed) == 0 {
inraw = false
continue
}
// another block line, join it
if inblock && len(trimmed) > 0 {
if len(txt) >= 2 && txt[len(txt)-2:] == " " {
inhard = true
}
space := " "
if inhard {
space = "\n"
}
blockbuf += space + trimmed
continue
}
// beginning of a new block
if !inblock && len(trimmed) > 0 {
inhard = false
inblock = true
if len(txt) >= 2 && txt[len(txt)-2:] == " " {
inhard = true
}
blockbuf = trimmed
continue
}
// end block
if inblock && len(trimmed) == 0 {
inblock = false
output += "\n\n" + Wrap(blockbuf, width-strip-4)
continue
}
}
// flush last block
if inblock {
output += "\n\n" + Wrap(blockbuf, width-strip-4)
}
output = Indent(strings.TrimSpace(output), indent)
return
}
// peekWord returns the runes up to the next space.
func peekWord(buf []rune, start int) []rune {
word := []rune{}
for _, r := range buf[start:] {
if unicode.IsSpace(r) {
break
}
word = append(word, r)
}
return word
}
// Wrap wraps the string to the given width using spaces to separate
// words. If passed a negative width will effectively join all words in
// the buffer into a single line with no wrapping.
func Wrap(buf string, width int) string {
if width == 0 {
return buf
}
nbuf := ""
curwidth := 0
for i, r := range []rune(buf) {
// hard breaks always as is
if r == '\n' {
nbuf += "\n"
curwidth = 0
continue
}
if unicode.IsSpace(r) {
// FIXME: don't peek every word, only after passed width
// change the space to a '\n' in the buffer slice directly
next := peekWord([]rune(buf), i+1)
if width > 0 && (curwidth+len(next)+1) > width {
nbuf += "\n"
curwidth = 0
continue
}
}
nbuf += string(r)
curwidth++
}
return nbuf
}

@ -1,104 +0,0 @@
package mark
import (
"testing"
)
func TestFormat(t *testing.T) {
want := []string{
OpenItalic + "Italic" + CloseItalic,
OpenBold + "Bold" + CloseBold,
OpenBoldItalic + "BoldItalic" + CloseBoldItalic,
"<" + OpenUnderline + "bracketed" + CloseUnderline + ">",
}
args := []string{"*Italic*", "**Bold**", "***BoldItalic***", "<bracketed>"}
for i, arg := range args {
t.Logf("testing: %v\n", arg)
got := Format(arg)
if got != want[i] {
t.Errorf("\nwant: %q\ngot: %q\n", want[i], got)
}
}
}
func TestFormatWrapped(t *testing.T) {
text := `
Something *easy* to write here that can be indented however you like
and wrapped and have each line indented and with <code>:
This will not be messed with.
Nor this.
So it's a lot like a **simple** version of Markdown that only supports
what is likely going to be used in stuff similar to man pages.
Let's try a hard
return.`
want := " Something " + OpenItalic + "easy" + CloseItalic + " to write here that can be indented however\n you like and wrapped and have each line indented and with\n <" + OpenUnderline + "code" + CloseUnderline + ">:\n \n This will not be messed with.\n Nor this.\n \n So it's a lot like a " + OpenBold + "simple" + CloseBold + " version of Markdown that only\n supports what is likely going to be used in stuff similar to\n man pages.\n \n Let's try a hard\n return."
got := FormatWrapped(text, 5, 70)
t.Log("\n" + got)
if want != got {
t.Errorf("\nwant:\n%q\ngot:\n%q\n", want, got)
}
}
func TestWrapped(t *testing.T) {
text := `
Something *easy* to write here that can be indented however you like
and wrapped and have each line indented and with <code>:
This will not be messed with.
Nor this.
So it's a lot like a **simple** version of Markdown that only supports
what is likely going to be used in stuff similar to man pages.
Let's try a hard
return.`
want := " Something *easy* to write here that can be indented however\n you like and wrapped and have each line indented and with\n <code>:\n \n This will not be messed with.\n Nor this.\n \n So it's a lot like a **simple** version of Markdown that only\n supports what is likely going to be used in stuff similar to\n man pages.\n \n Let's try a hard\n return."
got := Wrapped(text, 5, 70)
t.Log("\n" + got)
if want != got {
t.Errorf("\nwant:\n%q\ngot:\n%q\n", want, got)
}
}
func TestPeekWord(t *testing.T) {
var buf []rune
var word string
buf = []rune(`some thing`)
word = string(peekWord(buf, 0))
t.Logf("%q", word)
if word != "some" {
t.Fail()
}
word = string(peekWord(buf, 5))
t.Logf("%q", word)
if word != "thing" {
t.Fail()
}
word = string(peekWord(buf, 4))
t.Logf("%q", word)
if word != "" {
t.Fail()
}
}
func TestWrap(t *testing.T) {
buf := "Here's a string that's not long."
want := "Here's a\nstring\nthat's not\nlong."
got := Wrap(buf, 10)
if want != got {
t.Errorf("\nwant: %q\ngot: %q\n", want, got)
}
}
func TestWrap_none(t *testing.T) {
if Wrap("some thing", 0) != "some thing" {
t.Fail()
}
}

@ -1 +0,0 @@
package ast

@ -1,36 +0,0 @@
# BonzaiMark (v1.0.0-alpha) mark.bonzai.dev
# Copyright 2022 Robert S. Muhlestein
# SPDX-License-Identifier: Apache-2.0
# <-- "contains nodes"
# <- "does not contain nodes"
# Must be parsed in three passes:
#
# 1. Unindent every line
# 1. Chop into blocks
# 1. Parse each block
Indented <-- FirstLine Lines*
FirstLine <- Indentation (!EndLine .)*
_Indent <-- # number of spaces before first nongraphic
Block <-- Bulleted / Numbered / Paragraph / Verbatim
EndBlock <- EndLine{2,} / EOD
Paragraph <-- (!EndBlock <Span>)+ EndBlock
Bulleted <-- (!EndLine '*' SP <Span*>)+
Numbered <-- (!EndLine '1.' SP <Span*>)+
Verbatim <-- (!EndLine SP{4} <ugraphic*> EndLine)+
Span <- BoldItalic / Bold / Italic / Bracketed / Plain
BoldItalic <-- ('***' !ws) (!'***' ugraphic)+ (!ws '***')
Bold <-- ('**' !ws) (!'**' ugraphic)+ (!ws '**')
Italic <-- ('*' !ws) (!'*' ugraphic)+ (!ws '*')
Bracketed <-- ('<' !ws) <(!'>' ugraphic)+> (!ws '>')
Plain <-- ugraphic+
EndLine <- LF / CRLF / CR
EOD <- # end of document

@ -1,75 +0,0 @@
package mark
import (
"github.com/rwxrob/scan"
z "github.com/rwxrob/scan/is"
"github.com/rwxrob/scan/pg"
"github.com/rwxrob/structs/tree"
)
func Parse(in any) (*tree.E[string], error) {
names := []string{
`Grammar`, // 1
`Bulleted`, // 2
`Numbered`, // ...
`Verbatim`,
`Paragraph`,
`BoldItalic`,
`Bold`,
`Italic`,
`Bracketed`,
`Plain`,
}
s := scan.New(names)
s.X(Grammar)
s.Print()
s.Tree.Root.Print()
return nil, nil
}
func Grammar(s *scan.R) bool { return s.X(z.P{1, z.X{Block, pg.EndLine}}) }
func Block(s *scan.R) bool {
return s.X(z.X{z.I{Bulleted, Numbered, Paragraph, Verbatim}, EndBlock})
}
func EndBlock(s *scan.R) bool { return s.X(z.I{z.M{2, pg.EndLine}, scan.EOD}) }
func Bulleted(s *scan.R) bool {
return s.X(z.P{2, z.M1{z.X{z.N{pg.EndLine}, '*', ' ', z.M0{Span}}}})
}
func Numbered(s *scan.R) bool {
return s.X(z.P{3, z.M1{z.X{z.N{pg.EndLine}, "1.", ' ', z.M0{Span}}}})
}
func Verbatim(s *scan.R) bool {
return s.X(z.P{4, z.M1{z.X{z.N{pg.EndLine}, z.C{4, ' '}, z.M1{pg.UGraphic}}}})
}
func Paragraph(s *scan.R) bool {
return s.X(z.P{4, z.M1{z.X{z.N{z.I{z.C{2, pg.EndLine}, scan.EOD}}, Span}}})
}
func Span(s *scan.R) bool { return s.X(z.I{BoldItalic, Bold, Italic, Bracketed, Plain}) }
func BoldItalic(s *scan.R) bool {
return s.X(z.P{6, z.X{"***", z.N{pg.WS}, z.M1{Plain}, z.N{pg.WS}, "***"}})
}
func Bold(s *scan.R) bool {
return s.X(z.P{7, z.X{"**", z.N{pg.WS}, z.M1{Plain}, z.N{pg.WS}, "**"}})
}
func Italic(s *scan.R) bool {
return s.X(z.P{8, z.X{"*", z.N{pg.WS}, z.M1{Plain}, z.N{pg.WS}, "*"}})
}
func Bracketed(s *scan.R) bool {
//return s.X(z.P{9, z.X{'<', z.N{pg.WS}, z.M1{Plain}, z.N{pg.WS}, '>'}})
return s.X('<', z.P{9, z.X{z.M1{z.X{z.N{'>'}, pg.UGraphic}}}}, '>')
}
func Plain(s *scan.R) bool { return s.X(z.P{10, z.M1{pg.UGraphic}}) }

@ -1,28 +0,0 @@
package mark_test
import (
"github.com/rwxrob/bonzai/mark"
"github.com/rwxrob/scan"
)
func ExamplePlain() {
s := scan.New("some thing here")
//s.TraceX()
s.X(mark.Plain)
s.Print()
s.Tree.Root.Print()
// Output:
// <EOD>
// {"T":1,"N":[{"T":10,"V":"some thing here"}]}
}
func ExampleBracketed() {
s := scan.New("<thing>")
s.TraceX()
s.X(mark.Bracketed)
s.Print()
s.Tree.Root.Print()
// Output:
// <EOD>
// {"T":1,"N":[{"T":10,"V":"some thing here"}]}
}

@ -1,46 +0,0 @@
package scan
import (
"fmt"
"log"
)
// LogPanic is used with defer to trap any panic and log it.
func (s *R) LogPanic() {
r := recover()
if r != nil {
log.Printf("%v at %v", r, s)
}
}
// LogPanic is used with defer to trap any panic and print it.
func (s *R) PrintPanic() {
r := recover()
if r != nil {
fmt.Printf("%v at %v", r, s)
}
}
// --------------------------- PEGN support ---------------------------
// The following are directly correlated to supported PEGN expressions
// and are intended to be generated from PEGN grammars specifically
// (altough other languages generators could easily be adapted).
// Rune matches the exact rune specified or panics.
func (s *R) Rune(r rune) {
if s.Cur.Rune != r {
panic(fmt.Sprintf("expected %q", r))
}
s.Scan()
}
// Str iterates over all of the runes in the string as if Rune were
// called on each.
func (s *R) Str(v string) {
for _, v := range []rune(v) {
if v != s.Cur.Rune {
panic(fmt.Sprintf("expected %q", v))
}
s.Scan()
}
}

@ -1,52 +0,0 @@
package scan_test
import "github.com/rwxrob/scan"
func ExampleR_Any() {
s, _ := scan.New("so")
s.Print()
s.Any(2)
s.Print()
// Output:
// U+0073 's' 1,1-1 (1-1)
// <EOD>
}
func ExampleR_Rune() {
s, _ := scan.New("some thing")
//s.Any(3)
s.Rune('s')
s.Rune('o')
s.Rune('m')
s.Print() // same as Any(3)
// Output:
// U+0065 'e' 1,4-4 (4-4)
}
func ExampleR_Rune_fail() {
s, _ := scan.New("some thing")
defer s.PrintPanic()
s.Rune('s')
s.Rune('o')
s.Rune('m')
s.Rune('e')
s.Rune('\t')
// Output:
// expected '\t' at U+0020 ' ' 1,5-5 (5-5)
}
func ExampleR_Str() {
s, _ := scan.New("some thing")
s.Str("som")
s.Print()
// Output:
// U+0065 'e' 1,4-4 (4-4)
}
func ExampleR_Str_fail() {
s, _ := scan.New("some thing")
defer s.PrintPanic()
s.Str("some\t")
// Output:
// expected '\t' at U+0020 ' ' 1,5-5 (5-5)
}

@ -1,549 +0,0 @@
package scan
/*
// Parse creates a new Node reference from the string returned by Look.
func (s *R) Parse(to *Cur) *Node {
n := new(Node)
n.V = s.Look(to)
return n
}
// ParseSlice creates a new Node reference from the string returned by
// LookSlice.
func (s *R) ParseSlice(b *Cur, e *Cur) *Node {
n := new(Node)
n.V = s.LookSlice(b, e)
return n
}
// The Expect method is primarily designed for ease of use quickly by
// allowing BPEGN expressions to be coded much faster than traditional
// parser functions. In fact, entire applications can be generated
// automatically from BPEGN, PEGN, ABNF, EBNF, or any other meta syntax
// language by adding additional scan.Hooks to do work besides just
// parsing what is scanned. Generate the BPEGN Go expressions to pass to
// Expect and code the hook callbacks. That's it. Coding in BPEGN comes
// naturally when putting together a parser quickly and without the
// cognitive overhead of switching between grammars. The BPEGN
// expressions passed to Expect are 100% Go. For full documentation of
// BPEGN expressions see the z ("is") package and the source of this
// Expect method.
//
// Warning: While it is nothing for most developers to be concerned
// with, the Expect method does do a fair amount of functional recursion
// for the sake of simplicity and to support BPEGN syntax. Those wishing
// to gain the maximum performance should consider using other scan.R
// methods instead in such cases. Developers are encouraged to do their
// own benchmarking and perhaps start with BPEGN until they can create
// more optimized parsers when and if necessary. Most will discover
// other more substantial bottlenecks. The Bonzai project places
// priority on speed and quality of developer delivery over run-time
// performance. Delivery time is far more costly than the minimal gains
// in run-time performance. "Premature optimization is the root of all
// evil," as they say.
func (s *R) Expect(expr any) (*Cur, error) {
s.Last = s.Cur
// please keep the most common expressions types at the top
switch v := expr.(type) {
case rune: // ------------------------------------------------------
if s.Cur.Rune != v {
err := s.ErrorExpected(v)
return nil, err
}
s.Scan()
return s.Last, nil
case tk.Token: // --------------------------------------------------
switch v {
case tk.ANY: // A, A1
s.Scan()
case tk.A2:
s.ScanN(2)
case tk.A3:
s.ScanN(3)
case tk.A4:
s.ScanN(4)
case tk.A5:
s.ScanN(5)
case tk.A6:
s.ScanN(6)
case tk.A7:
s.ScanN(7)
case tk.A8:
s.ScanN(8)
case tk.A9:
s.ScanN(9)
}
return s.Last, nil
case string: // ----------------------------------------------------
if v == "" {
return s.Mark(), nil
}
// avoid the temptation to look directly at bytes since it would
// allow none runes to be passed within "strings"
for _, v := range []rune(v) {
if v != s.Cur.Rune {
return nil, s.ErrorExpected(v)
}
s.Scan()
}
return s.Last, nil
case z.X: // -----------------------------------------------------
var err error
b := s.Mark()
m := s.Mark()
for _, i := range v {
m, err = s.Expect(i)
if err != nil {
s.Jump(b)
return nil, err
}
}
return m, nil
case z.O: // -------------------------------------------------------
for _, i := range v {
m, _ := s.Expect(i)
if m != nil {
return m, nil
}
default:
return fmt.Errorf("scanner: unsupported input type: %t", i)
}
s.BufLen = len(s.Buf)
if s.BufLen == 0 {
return fmt.Errorf("scanner: no input")
}
return err
}
/*
// Parse creates a new Node reference from the string returned by Look.
func (s *R) Parse(to *Cur) *Node {
n := new(Node)
n.V = s.Look(to)
return n
}
// ParseSlice creates a new Node reference from the string returned by
// LookSlice.
func (s *R) ParseSlice(b *Cur, e *Cur) *Node {
n := new(Node)
n.V = s.LookSlice(b, e)
return n
}
// Look returns a string containing all the bytes from the current
// scanner cursor position ahead or behind to the passed cursor
// position. Neither the internal nor the passed cursor position is
// changed. Also see Peek and LookSlice.
func (s *R) Look(to *Cur) string {
if to.Byte < s.Cur.Byte {
return string(s.Buf[to.Byte:s.Cur.Next])
}
return string(s.Buf[s.Cur.Byte:to.Next])
}
// LookSlice returns a string containing all the bytes from the first
// cursor up to the second cursor. Neither cursor position is changed.
func (s *R) LookSlice(beg *Cur, end *Cur) string {
return string(s.Buf[beg.Byte:end.Next])
}
// The Expect method is primarily designed for ease of use quickly by
// allowing BPEGN expressions to be coded much faster than traditional
// parser functions. In fact, entire applications can be generated
// automatically from BPEGN, PEGN, ABNF, EBNF, or any other meta syntax
// language by adding additional scan.Hooks to do work besides just
// parsing what is scanned. Generate the BPEGN Go expressions to pass to
// Expect and code the hook callbacks. That's it. Coding in BPEGN comes
// naturally when putting together a parser quickly and without the
// cognitive overhead of switching between grammars. The BPEGN
// expressions passed to Expect are 100% Go. For full documentation of
// BPEGN expressions see the z ("is") package and the source of this
// Expect method.
//
// Warning: While it is nothing for most developers to be concerned
// with, the Expect method does do a fair amount of functional recursion
// for the sake of simplicity and to support BPEGN syntax. Those wishing
// to gain the maximum performance should consider using other scan.R
// methods instead in such cases. Developers are encouraged to do their
// own benchmarking and perhaps start with BPEGN until they can create
// more optimized parsers when and if necessary. Most will discover
// other more substantial bottlenecks. The Bonzai project places
// priority on speed and quality of developer delivery over run-time
// performance. Delivery time is far more costly than the minimal gains
// in run-time performance. "Premature optimization is the root of all
// evil," as they say.
func (s *R) Expect(expr any) (*Cur, error) {
s.Last = s.Cur
// please keep the most common expressions types at the top
switch v := expr.(type) {
case rune: // ------------------------------------------------------
if s.Cur.Rune != v {
err := s.ErrorExpected(v)
return nil, err
}
s.Scan()
return s.Last, nil
case tk.Token: // --------------------------------------------------
switch v {
case tk.ANY: // A, A1
s.Scan()
case tk.A2:
s.ScanN(2)
case tk.A3:
s.ScanN(3)
case tk.A4:
s.ScanN(4)
case tk.A5:
s.ScanN(5)
case tk.A6:
s.ScanN(6)
case tk.A7:
s.ScanN(7)
case tk.A8:
s.ScanN(8)
case tk.A9:
s.ScanN(9)
}
return s.Last, nil
case string: // ----------------------------------------------------
if v == "" {
return s.Mark(), nil
}
// avoid the temptation to look directly at bytes since it would
// allow none runes to be passed within "strings"
for _, v := range []rune(v) {
if v != s.Cur.Rune {
return nil, s.ErrorExpected(v)
}
s.Scan()
}
return s.Last, nil
case z.X: // -----------------------------------------------------
var err error
b := s.Mark()
m := s.Mark()
for _, i := range v {
m, err = s.Expect(i)
if err != nil {
s.Jump(b)
return nil, err
}
}
return m, nil
case z.O: // -------------------------------------------------------
for _, i := range v {
m, _ := s.Expect(i)
if m != nil {
return m, nil
}
}
case z.Ti: // -----------------------------------------------------
back := s.Mark()
for s.Cur.Rune != tk.EOD {
for _, i := range v {
m, _ := s.Expect(i)
if m != nil {
return m, nil
}
}
s.Scan()
}
s.Jump(back)
return nil, s.ErrorExpected(v)
case z.T: // -----------------------------------------------------
m := s.Mark()
b4 := s.Mark()
for s.Cur.Rune != tk.EOD {
for _, i := range v {
b := s.Mark()
c, _ := s.Expect(i)
if c != nil {
s.Jump(b)
return b4, nil
}
}
b4 = s.Mark()
s.Scan()
}
s.Jump(m)
return nil, s.ErrorExpected(v)
case z.Y: // ----------------------------------------------------
var m *Cur
b := s.Mark()
for _, i := range v {
m, _ = s.Expect(i)
if m != nil {
break
}
}
if m == nil {
return nil, s.ErrorExpected(v)
}
s.Jump(b)
return b, nil
case z.N: // ----------------------------------------------------
m := s.Mark()
for _, i := range v {
if c, _ := s.Expect(i); c != nil {
s.Jump(m)
err := s.ErrorExpected(v, i)
return nil, err
}
}
return m, nil
case z.I: // -----------------------------------------------------
var m *Cur
for _, i := range v {
var err error
last := s.Mark()
m, err = s.Expect(i)
if err == nil {
break
}
s.Jump(last)
}
if m == nil {
return nil, s.ErrorExpected(v)
}
return m, nil
case z.MM: // ----------------------------------------------------
c := 0
last := s.Mark()
var err error
var m, end *Cur
for {
m, err = s.Expect(v.This)
if err != nil {
break
}
last = m
c++
}
if c == 0 && v.Min == 0 {
if end == nil {
end = last
}
}
if !(v.Min <= c && c <= v.Max) {
s.Jump(last)
return nil, s.ErrorExpected(v)
}
return end, nil
case z.M1: // ----------------------------------------------------
m := s.Mark()
c, err := s.Expect(z.M{1, v.This})
if err != nil {
s.Jump(m)
return nil, s.ErrorExpected(v)
}
return c, nil
case z.M: // ----------------------------------------------------
c := 0
last := s.Mark()
var err error
var m *Cur
for {
m, err = s.Expect(v.This)
if err != nil {
break
}
last = m
c++
}
if c < v.Min {
s.Jump(last)
return nil, s.ErrorExpected(v)
}
return last, nil
case z.A: // ----------------------------------------------------
for n := 0; n < v.N; n++ {
s.Scan()
}
s.Scan()
return s.Last, nil
// see rune for A2-9
case z.R: // ----------------------------------------------------
if !(v.First <= s.Cur.Rune && s.Cur.Rune <= v.Last) {
err := s.ErrorExpected(v)
return nil, err
}
s.Scan()
return s.Last, nil
case z.C: // ------------------------------------------------------
return s.expcount(v.N, v.This)
case z.C2:
return s.expcount(2, v.This)
case z.C3:
return s.expcount(3, v.This)
case z.C4:
return s.expcount(4, v.This)
case z.C5:
return s.expcount(5, v.This)
case z.C6:
return s.expcount(6, v.This)
case z.C7:
return s.expcount(7, v.This)
case z.C8:
return s.expcount(8, v.This)
case z.C9:
return s.expcount(9, v.This)
case Hook: // ------------------------------------------------------
if err := v(s); err != nil {
return nil, s.ErrorExpected(v, err)
}
return s.Cur, nil
case func(r *R) error:
if err := v(s); err != nil {
return nil, s.ErrorExpected(v, err)
}
return s.Cur, nil
}
return nil, fmt.Errorf("unknown expression (%T)", expr)
}
// handles all C* expressions
func (s *R) expcount(n int, expr any) (*Cur, error) {
b := s.Mark()
m := s.Mark()
for i := 0; i < n; i++ {
m, _ := s.Expect(expr)
if m == nil {
s.Jump(b)
return nil, s.ErrorExpected(expr)
}
}
return m, nil
}
// ErrorExpected returns a verbose, one-line error describing what was
// expected when it encountered whatever the scanner last scanned. All
// expression types are supported. See Expect.
func (s *R) ErrorExpected(this any, args ...any) error {
var msg string
but := fmt.Sprintf(` at %v`, s)
if s.Cur != nil && s.Cur.Rune == tk.EOD && s.Cur.Len == 0 {
runes := `runes`
if s.Cur.Pos.Rune == 1 {
runes = `rune`
}
but = fmt.Sprintf(`, exceeded data length (%v %v)`,
s.Cur.Pos.Rune, runes)
}
switch v := this.(type) {
case rune: // otherwise will use uint32
msg = fmt.Sprintf(`expected rune %q`, v)
case z.Y:
if len(v) > 1 {
msg = fmt.Sprintf(`expected one of %q`, v)
} else {
msg = fmt.Sprintf(`expected %q`, v[0])
}
case z.N:
msg = fmt.Sprintf(`unexpected %q`, args[0])
case z.I:
str := `expected one of %q`
msg = fmt.Sprintf(str, v)
case z.X:
//str := `expected %q in sequence %q at %v beginning`
//msg = fmt.Sprintf(str, args[0], v, args[1])
str := `expected %q in sequence`
msg = fmt.Sprintf(str, v)
case z.O:
str := `expected an optional %v`
msg = fmt.Sprintf(str, v)
case z.M1:
str := `expected one or more %q`
msg = fmt.Sprintf(str, v.This)
case z.M:
str := `expected min %v of %q`
msg = fmt.Sprintf(str, v.Min, v.This)
case z.MM:
str := `expected min %v, max %v of %q`
msg = fmt.Sprintf(str, v.Min, v.Max, v.This)
case z.R:
str := `expected range [%v-%v]`
msg = fmt.Sprintf(str, string(v.First), string(v.Last))
case z.Ti:
str := `%q not found`
if len(v) > 1 {
str = `none of %q found`
}
msg = fmt.Sprintf(str, v)
case z.T:
msg = fmt.Sprintf(`none of %q found`, v)
case Hook:
msg = fmt.Sprintf("%v: %v", strings.ToLower(util.FuncName(v)), args[0])
case func(s *R) error:
msg = fmt.Sprintf("%v: %v", strings.ToLower(util.FuncName(v)), args[0])
default:
msg = fmt.Sprintf(`expected %T %q`, v, v)
}
return errors.New(msg + but)
}
// --------------------------- new functions --------------------------
func (s *R) Any(n int) bool {
for i := 0; i < n; i++ {
s.Scan()
}
return true
}
func (s *R) Str(strs ...string) {
for _, it := range strs {
for _, r := range []rune(it) {
if r != s.Cur.Rune {
panic(fmt.Sprintf("expecting %q", r))
}
s.Scan()
}
}
}
func (s *R) Opt(strs ...string) {
s.Snap()
OUT:
for _, it := range strs {
for _, r := range []rune(it) {
if r != s.Cur.Rune {
s.Back()
continue OUT
}
s.Scan()
}
}
}

@ -1,619 +0,0 @@
package scan_test
import (
"fmt"
"log"
"os"
"github.com/rwxrob/bonzai/scan"
z "github.com/rwxrob/bonzai/scan/is"
"github.com/rwxrob/bonzai/scan/tk"
)
/*
func ExampleNew_string() {
s, err := scan.New("some thing")
if err != nil {
fmt.Println(err)
}
s.Print()
s.ScanN(3)
s.Print()
//Output:
// U+0073 's' 1,1-1 (1-1)
// U+0065 'e' 1,4-4 (4-4)
}
func ExampleNew_bytes() {
s, err := scan.New([]byte{'s', 'o', 'm'})
if err != nil {
fmt.Println(err)
}
s.Print()
s.ScanN(3)
s.Print()
//Output:
// U+0073 's' 1,1-1 (1-1)
// <EOD>
}
func ExampleNew_reader() {
r := strings.NewReader("some thing")
s, err := scan.New(r)
if err != nil {
fmt.Println(err)
}
s.Print()
s.ScanN(3)
s.Print()
//Output:
// U+0073 's' 1,1-1 (1-1)
// U+0065 'e' 1,4-4 (4-4)
}
func ExampleInit() {
s, err := scan.New("some thing")
if err != nil {
fmt.Println(err)
}
s.Init("other stuff entirely")
s.Print()
s.ScanN(3)
s.Print()
s.Scan()
s.Print()
//Output:
// U+006F 'o' 1,1-1 (1-1)
// U+0065 'e' 1,4-4 (4-4)
// U+0072 'r' 1,5-5 (5-5)
}
func ExampleMark() {
s, err := scan.New("some thing")
if err != nil {
fmt.Println(err)
}
m := s.Mark()
fmt.Println(s.Cur != m)
// Output:
// true
}
func ExampleJump() {
s1, _ := scan.New("some thing")
s1.ScanN(5)
s1.Print() // t
s2, _ := scan.New("other thing")
s2.ScanN(6)
s2.Print() // t
s1.Jump(s2.Cur) // WRONG, must be same source buffer
s1.Print()
s3, _ := scan.New("some thing") // identical
s3.ScanN(6)
s3.Print() // h
s1.Jump(s3.Cur)
s1.Print()
s3.ScanN(2)
s1.Jump(s3.Cur)
s1.Print()
s3.Print()
// Output:
// U+0074 't' 1,6-6 (6-6)
// U+0074 't' 1,7-7 (7-7)
// U+0074 't' 1,7-7 (7-7)
// U+0068 'h' 1,7-7 (7-7)
// U+0068 'h' 1,7-7 (7-7)
// U+006E 'n' 1,9-9 (9-9)
// U+006E 'n' 1,9-9 (9-9)
}
func ExampleNewLine() {
s, _ := scan.New("some thing")
s.Print()
s.NewLine()
s.Print()
// Output:
// U+0073 's' 1,1-1 (1-1)
// U+0073 's' 2,1-1 (1-1)
}
/*
func ExampleR_Expect_parse_Single_Success() {
const FOO = `FOO`
s, _ := scan.New("some thing")
//c, _ := s.Expect(z.P{"some", ' ', z.I{'t', 'T'}})// FIXME
c, _ := s.Expect(z.P{FOO, "some", ' ', 't'})
c.Print() // same as "some t", points at 'h'
s.Print()
// Output:
// U+0074 't' 1,6-6 (6-6)
// U+0068 'h' 1,7-7 (7-7)
}
*/
/*
func ExampleR_Expect_parse_Success_One_Deep() {
const P = `PHRASE`
const S = `STRING`
const R = `RUNE`
s, _ := scan.New("some thing")
c, _ := s.Expect(z.P{P, z.P{S, "some"}, z.P{R, ' '}, z.P{R, 't'}})
c.Print() // same as "some t", points at 't'
//s.Print() // advances to 'h
for _, v := range s.Nodes {
fmt.Println(v)
}
// Output:
// U+0074 't' 1,6-6 (6-6)
// U+0068 'h' 1,7-7 (7-7)
// not null
}
func ExampleR_Expect_parse_Succes_Mixed() {
const P = `PHRASE`
const R = `RUNE`
s, _ := scan.New("some thing")
c, e := s.Expect(z.P{P, "some", z.P{R, ' '}, "thing"})
fmt.Println(e)
c.Print() // same as "some t", points at 'h'
s.Print()
//s.CurNode.Print()
//fmt.Println(s.CurNode == s.Nodes[0])
// Output:
// U+0068 'h' 1,7-7 (7-7)
// U+0068 'h' 1,7-7 (7-7)
// {"T":"FOO","V":"some t"}
// true
}
*/
func ExampleErrorExpected() {
s, _ := scan.New("some thing")
fmt.Println(s.ErrorExpected("foo"))
fmt.Println(s.ErrorExpected('f'))
fmt.Println(s.ErrorExpected([]byte{'f', 'o', 'o'}))
// Output:
// expected string "foo" at U+0073 's' 1,1-1 (1-1)
// expected rune 'f' at U+0073 's' 1,1-1 (1-1)
// expected []uint8 "foo" at U+0073 's' 1,1-1 (1-1)
}
func ExampleExpect_string() {
s, _ := scan.New("some thing")
c, _ := s.Expect("som")
c.Print() // same as s.ScanN(2), last is 'm'
s.Print() // point to next scan 'e'
// Output:
// U+006D 'm' 1,3-3 (3-3)
// U+0065 'e' 1,4-4 (4-4)
}
func ExampleExpect_compound_Expr_String() {
s, _ := scan.New("some thing")
c, _ := s.Expect(z.X{"some ", "thin"})
c.Print()
s.Print()
// Output:
// U+006E 'n' 1,9-9 (9-9)
// U+0067 'g' 1,10-10 (10-10)
}
func ExampleExpect_compound_Expr_Rune() {
s, _ := scan.New("some thing")
c, _ := s.Expect(z.X{"some", ' ', "thin"})
c.Print()
s.Print()
// Output:
// U+006E 'n' 1,9-9 (9-9)
// U+0067 'g' 1,10-10 (10-10)
}
func ExampleExpect_it_Success() {
s, _ := scan.New("some thing")
c, _ := s.Expect(z.Y{"some"})
c.Print() // even though true, not moved
s.Print() // scanner also not moved
// Output:
// U+0073 's' 1,1-1 (1-1)
// U+0073 's' 1,1-1 (1-1)
}
func ExampleExpect_it_Success_Middle() {
s, _ := scan.New("some thing")
c, _ := s.Expect(z.X{"some", z.Y{' '}})
c.Print() // advanced up to (but not including) ' '
s.Print() // scanner also not moved
// Output:
// U+0020 ' ' 1,5-5 (5-5)
// U+0020 ' ' 1,5-5 (5-5)
}
func ExampleExpect_it_Fail() {
s, _ := scan.New("some thing")
_, err := s.Expect(z.X{"some", z.Y{"thing"}})
fmt.Println(err)
s.Print() // but scanner did get "some" so advanced
// Output:
// expected "thing" at U+0020 ' ' 1,5-5 (5-5)
// U+0073 's' 1,1-1 (1-1)
}
func ExampleExpect_it_Fail_X() {
s, _ := scan.New("some thing")
_, err := s.Expect(z.X{"some", z.Y{"thing"}})
fmt.Println(err)
s.Print() // but scanner did get "some" so advanced
// Output:
// expected "thing" at U+0020 ' ' 1,5-5 (5-5)
// U+0073 's' 1,1-1 (1-1)
}
func ExampleExpect_not_Success() {
s, _ := scan.New("some thing")
c, _ := s.Expect(z.N{"foo"})
c.Print() // not advanced, but also not <nil>
s.Print() // not advanced at all
// Output:
// U+0073 's' 1,1-1 (1-1)
// U+0073 's' 1,1-1 (1-1)
}
func ExampleExpect_not_Fail() {
s, _ := scan.New("some thing")
_, err := s.Expect(z.N{"some"})
fmt.Println(err)
s.Print() // not advanced at all
// Output:
// unexpected "some" at U+0073 's' 1,1-1 (1-1)
// U+0073 's' 1,1-1 (1-1)
}
func ExampleExpect_not_X_Fail() {
s, _ := scan.New("some thing wonderful")
_, err := s.Expect(z.X{z.N{'s'}, 'o'})
fmt.Println(err) // sees the s so fails
s.Print() // not advanced
// Output:
// unexpected 's' at U+0073 's' 1,1-1 (1-1)
// U+0073 's' 1,1-1 (1-1)
}
func ExampleExpect_not_X_Success() {
s, _ := scan.New("some thing wonderful")
c, _ := s.Expect(z.X{z.N{`n`}, z.I{`som`}})
c.Print() // pointing to last in match 'm'
s.Print() // advanced to next after match 'e'
// Output:
// U+006D 'm' 1,3-3 (3-3)
// U+0065 'e' 1,4-4 (4-4)
}
func ExampleExpect_to_Success_Mid() {
s, _ := scan.New("some thing wonderful")
c, _ := s.Expect(z.T{"wo"})
c.Print() // "wo" not inc, same as "some thing ", so ' '
s.Print() // advances to 'w'
// Output:
// U+0020 ' ' 1,11-11 (11-11)
// U+0077 'w' 1,12-12 (12-12)
}
func ExampleExpect_avoid_Not_with_In() {
s, _ := scan.New("some thing")
s.Snap()
c, _ := s.Expect(z.I{z.N{'s'}, z.R{'a', 'z'}})
c.Print() // unexpected success
s.Print() // advanced to 'o'
s.Back()
// use z.X instead
_, err := s.Expect(z.X{z.N{'s'}, z.R{'a', 'z'}})
fmt.Println(err)
s.Print() // not advanced
// Output:
// U+0073 's' 1,1-1 (1-1)
// U+006F 'o' 1,2-2 (2-2)
// unexpected 's' at U+0073 's' 1,1-1 (1-1)
// U+0073 's' 1,1-1 (1-1)
}
/*
func ExampleExpect_seq_Success() {
s, _ := scan.New("some thing")
c, _ := s.Expect(z.P{"some", ' ', "thin"})
c.Print() // same as "some thin", points at 'n'
s.Print() // advanced to 'g'
// Output:
// U+006E 'n' 1,9-9 (9-9)
// U+0067 'g' 1,10-10 (10-10)
}
*/
func ExampleExpect_seq_Fail() {
s, _ := scan.New("some thing")
_, err := s.Expect(z.X{"some", "thin"})
fmt.Println(err)
s.Print() // not advanced at all
// Output:
// expected rune 't' at U+0020 ' ' 1,5-5 (5-5)
// U+0073 's' 1,1-1 (1-1)
}
func ExampleExpect_seq_Not_Success() {
s, _ := scan.New("some thing")
c, _ := s.Expect(z.X{"some", ` `, z.N{`T`}, "thin"})
c.Print() // same as "some thin"
s.Print() // advanced to next after ('g')
// Output:
// U+006E 'n' 1,9-9 (9-9)
// U+0067 'g' 1,10-10 (10-10)
}
func ExampleExpect_seq_Not_Fail() {
s, _ := scan.New("some Thing")
_, err := s.Expect(z.X{"some", ' ', z.N{`T`}, "ignored"})
fmt.Println(err)
s.Print() // not advanced at all
// Output:
// unexpected "T" at U+0054 'T' 1,6-6 (6-6)
// U+0073 's' 1,1-1 (1-1)
}
func ExampleExpect_token_ANY() {
s, _ := scan.New("some thing wonderful")
c, _ := s.Expect(tk.ANY)
c.Print() // same as `s` or s.Scan()
s.Print() // advances
c, _ = s.Expect(tk.A)
c.Print() // same as `o` or s.Scan()
s.Print() // advances
c, _ = s.Expect(tk.A1)
// we'll skip tk.A2 - tk.A8
s.Print()
c, _ = s.Expect(tk.A9)
s.Print() // should advance 9 to pos 13
// Output:
// U+0073 's' 1,1-1 (1-1)
// U+006F 'o' 1,2-2 (2-2)
// U+006F 'o' 1,2-2 (2-2)
// U+006D 'm' 1,3-3 (3-3)
// U+0065 'e' 1,4-4 (4-4)
// U+006F 'o' 1,13-13 (13-13)
}
func ExampleExpect_any_Success() {
s, _ := scan.New("some thing")
c, _ := s.Expect(z.A{5})
c.Print() // same as "some "
s.Print() // advanced to next after ('t')
// Output:
// U+0074 't' 1,6-6 (6-6)
// U+0068 'h' 1,7-7 (7-7)
}
func ExampleExpect_o_Optional_Success() {
s, _ := scan.New("some thing")
//c, _ := s.Expect(z.O{"thing", "some"})
c, _ := s.Expect("some")
c.Print() // same as "some", points to 'e'
s.Print() // advances to space ' '
// Output:
// U+0065 'e' 1,4-4 (4-4)
// U+0020 ' ' 1,5-5 (5-5)
}
func ExampleExpect_minimum_One() {
s, _ := scan.New("sommme thing")
start := s.Mark()
s.ScanN(2)
c, _ := s.Expect(z.M1{'m'}) // goggles up all three
c.Print()
s.Print()
s.Jump(start)
c, _ = s.Expect(z.M1{'s'}) // yep, just one
c.Print()
s.Print()
// Output:
// U+006D 'm' 1,5-5 (5-5)
// U+0065 'e' 1,6-6 (6-6)
// U+0073 's' 1,1-1 (1-1)
// U+006F 'o' 1,2-2 (2-2)
}
func ExampleExpect_minimum() {
s, _ := scan.New("sssoommme thing")
c, _ := s.Expect(z.M{2, 's'})
c.Print() // needs 2, but will consume all three to last 's'
s.Print() // advances to next after ('o')
// Output:
// U+0073 's' 1,3-3 (3-3)
// U+006F 'o' 1,4-4 (4-4)
}
func ExampleExpect_mMx() {
s, _ := scan.New("sommme thing")
s.Snap()
s.ScanN(2)
s.Print()
s.Expect(z.MM{1, 3, 'm'}) // goggles up all three
s.Print()
s.Back()
s.Expect(z.MM{1, 3, 's'}) // yep, at least one
s.Print()
_, err := s.Expect(z.MM{1, 3, 'X'}) // nope
fmt.Println(err)
// Output:
// U+006D 'm' 1,3-3 (3-3)
// U+0065 'e' 1,6-6 (6-6)
// U+006F 'o' 1,2-2 (2-2)
// expected min 1, max 3 of 'X' at U+006F 'o' 1,2-2 (2-2)
}
func ExampleExpect_c() {
s, _ := scan.New("sommme thing")
s.Snap()
s.ScanN(2)
s.Print()
s.Expect(z.C{3, 'm'}) // goggles up all three
s.Print()
s.Back()
s.Expect(z.C{1, 's'}) // yes, but silly since 's' is easier
s.Print()
_, err := s.Expect(z.C{3, 'X'}) // nope
fmt.Println(err)
// Output:
// U+006D 'm' 1,3-3 (3-3)
// U+0065 'e' 1,6-6 (6-6)
// U+006F 'o' 1,2-2 (2-2)
// expected rune 'X' at U+006F 'o' 1,2-2 (2-2)
}
func ExampleExpect_rng() {
s, _ := scan.New("some thing")
s.Scan()
c1, _ := s.Expect(z.R{'l', 'p'})
c1.Print()
s.Print()
// Output:
// U+006F 'o' 1,2-2 (2-2)
// U+006D 'm' 1,3-3 (3-3)
}
func FailHook(s *scan.R) error { return fmt.Errorf("imma fail") }
func ExampleExpect_hook() {
// plain function signature
WouldSave := scan.Hook(func(s *scan.R) error {
fmt.Println("would save")
return nil
})
// as scan.Hook
WouldScan := scan.Hook(func(s *scan.R) error {
s.Scan()
return nil
})
// FailHook defined outside of Example function (see source)
s, _ := scan.New("some thing")
s.Scan()
s.Expect(WouldSave)
s.Print() // hook didn't advance
s.Expect(WouldScan)
s.Print() // hook advanced scan by one
_, e := s.Expect(FailHook)
fmt.Println(e)
// Output:
// would save
// U+006F 'o' 1,2-2 (2-2)
// U+006D 'm' 1,3-3 (3-3)
// failhook: imma fail at U+006D 'm' 1,3-3 (3-3)
}
func ExampleExpect_to_Success() {
s, _ := scan.New("some thing")
c, _ := s.Expect(z.T{'e'})
c.Print() // same as "som", points to 'm'
s.Print() // scanned next after ('e')
// Output:
// U+006D 'm' 1,3-3 (3-3)
// U+0065 'e' 1,4-4 (4-4)
}
func ExampleExpect_to_Inclusive() {
s, _ := scan.New("some thing")
c, _ := s.Expect(z.Ti{'e'})
c.Print() // same as "some", points to 'e'
s.Print() // scanned next after (' ')
// Output:
// U+0065 'e' 1,4-4 (4-4)
// U+0020 ' ' 1,5-5 (5-5)
}
func ExampleSnap() {
s, _ := scan.New("some thing")
s.ScanN(3)
s.Snap()
s.Print()
s.ScanN(4)
s.Print()
s.Back()
s.Print()
// Output:
// U+0065 'e' 1,4-4 (4-4)
// U+0069 'i' 1,8-8 (8-8)
// U+0065 'e' 1,4-4 (4-4)
}
func ExampleScan() {
defer log.SetOutput(os.Stderr)
defer log.SetFlags(log.Flags())
log.SetOutput(os.Stdout)
log.SetFlags(0)
s, _ := scan.New(`s😈me thing`)
s.Scan()
s.Print()
s.Scan()
s.Print()
s.Log()
// Output:
// U+1F608 '😈' 1,2-2 (2-2)
// U+006D 'm' 1,3-6 (3-6)
// U+006D 'm' 1,3-6 (3-6)
}
func ExampleStr() {
s, _ := scan.New("some thing")
s.Str("some")
s.Print()
s.Str(" ", "th")
s.Print()
// Output:
// U+0020 ' ' 1,5-5 (5-5)
// U+0069 'i' 1,8-8 (8-8)
}
func ExampleAny() {
s, _ := scan.New("some thing")
s.Any(4)
s.Print()
// Output:
// U+0020 ' ' 1,5-5 (5-5)
}
func ExampleOpt() {
s, _ := scan.New("some thing")
defer s.PrintPanic()
s.Opt("S", "s")
s.Print()
// Output:
// U+006F 'o' 1,2-2 (2-2)
}
func Example_all() {
s, _ := scan.New("some thing")
defer s.PrintPanic()
s.Opt("S", "s")
s.Str("ome", " ", "thi")
s.Print()
// Output:
// U+006E 'n' 1,9-9 (9-9)
}
func Example() {
s, _ := scan.New("some thing")
defer s.PrintPanic()
s.Opt("S", "s")
s.Str("ome", " ", "thi")
s.Print()
}

@ -1,44 +0,0 @@
package scan
import "github.com/rwxrob/structs/tree"
// FIXME These currently do not create the needed Node structures
// properly
// Slice is like "string" but marks the beginning and ending in the
// buffer string data instead of containing the actual data.
type Slice struct {
Beg *Cur
End *Cur
}
// Beg begins a new Node containing a Slice at the current location and
// pushes onto Parsing.
func (s *R) Beg(t int) {
if s.CurNode == nil {
tr := tree.New[*Slice]([]string{})
s.CurNode = tr.Root
s.Trees = append(s.Trees, tr)
}
sl := &Slice{Beg: s.Mark()}
n := s.CurNode.Add(t, sl)
s.Parsing.Push(n)
}
// End ends a new Node at the current location and pops it off of
// Parsing placing adding it under the node in the current tree. End is
// automatically implied when the end of data is reached for every Beg
// that has not yet been closed.
func (s *R) End() {
if s.CurNode == nil {
s.Warn(`no current node open, possibly forgot Beg? at %v`, s.Cur)
return
}
n := s.Parsing.Pop()
n.V.End = s.Mark()
if n.P != nil {
s.CurNode = n.P
}
}

@ -31,6 +31,7 @@ import (
"github.com/rwxrob/fn"
"github.com/rwxrob/fs/file"
"github.com/rwxrob/term"
"github.com/rwxrob/to"
)
func init() {
@ -66,6 +67,74 @@ var ExeName string
// will be assumed to be string arguments to prepend. See Run.
var Commands map[string][]any
// UsageText is used for one-line UsageErrors. It's exported to allow
// for different languages.
var UsageText = `usage`
// DefaultUsageFunc is the default first-class function assigned to
// every Cmd that does not already define one. It is used to return
// a usage summary. Generally, it should only return a single line (even
// if that line is very long). Developers are encouraged to refer users
// to their chosen help command rather than producing usually long usage
// lines. If only the word "usage" needs to be changed (for a given
// language) consider UsageText instead. Note that most developers will
// simply change the Usage string when they do not want the default
// inferred usage string.
var DefaultUsageFunc = InferredUsage
// InferredUsage returns a single line of text summarizing only the
// Commands (less any Hidden commands), Params, and Aliases. If a Cmd
// is currently in an invalid state (Params without Call, no Call and no
// Commands) a string beginning with ERROR and wrapped in braces ({}) is
// returned instead. The string depends on the current language (see
// lang.go). Note that aliases does not include package Z.Aliases.
func InferredUsage(x *Cmd) string {
if x.Call == nil && x.Commands == nil {
// FIXME: replace with string var from lang.go
return "{ERROR: neither Call nor Commands defined}"
}
if x.Call == nil && x.Params != nil {
// FIXME: replace with string var from lang.go
return "{ERROR: Params without Call: " + strings.Join(x.Params, ", ") + "}"
}
var params string
if x.Params != nil {
params = to.UsageGroup(x.Params)
switch x.MinParm {
case 0:
params = params + "?"
case 1:
params = params
default:
params = fmt.Sprintf("%v{%v,}", params, x.MinParm)
}
}
var names string
if x.Commands != nil {
var snames []string
for _, x := range x.Commands {
snames = append(snames, x.UsageNames())
}
if len(snames) > 0 {
names = to.UsageGroup(snames)
}
}
if params != "" && names != "" {
return "(" + params + "|" + names + ")"
}
if params != "" {
return params
}
return names
}
// Run infers the name of the command to run from the ExeName looked up
// in the Commands delegates accordingly, prepending any arguments
// provided in the CmdRun. Run produces an "unmapped multicall command"

@ -0,0 +1,203 @@
// Copyright 2022 Robert S. Muhlestein.
// SPDX-License-Identifier: Apache-2.0
package Z_test
import (
"fmt"
"os"
Z "github.com/rwxrob/bonzai/z"
)
func ExampleArgsFrom() {
fmt.Printf("%q\n", Z.ArgsFrom(`greet hi french`))
fmt.Printf("%q\n", Z.ArgsFrom(`greet hi french `))
// Output:
// ["greet" "hi" "french"]
// ["greet" "hi" "french" ""]
}
func ExampleArgsOrIn_read_Nil() {
orig := os.Stdin
defer func() { os.Stdin = orig }()
os.Stdin, _ = os.Open(`testdata/in`)
fmt.Println(Z.ArgsOrIn(nil))
// Output:
// some thing
}
func ExampleArgsOrIn_read_Zero_Args() {
orig := os.Stdin
defer func() { os.Stdin = orig }()
os.Stdin, _ = os.Open(`testdata/in`)
fmt.Println(Z.ArgsOrIn([]string{}))
// Output:
// some thing
}
func ExampleArgsOrIn_args_Joined() {
fmt.Println(Z.ArgsOrIn([]string{"some", "thing"}))
// Output:
// some thing
}
func ExampleEsc() {
fmt.Println(Z.Esc("|&;()<>![]"))
fmt.Printf("%q", Z.Esc(" \n\r"))
// Output:
// \|\&\;\(\)\<\>\!\[\]
// "\\ \\\n\\\r"
}
func ExampleEscAll() {
list := []string{"so!me", "<here>", "other&"}
fmt.Println(Z.EscAll(list))
// Output:
// [so\!me \<here\> other\&]
}
func ExampleInferredUsage_optional_Param() {
x := &Z.Cmd{
Params: []string{"p1", "p2"},
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
}
fmt.Println(Z.InferredUsage(x))
// Output:
// (p1|p2)?
}
func ExampleInferredUsage_min_One_Param() {
x := &Z.Cmd{
Params: []string{"p1", "p2"},
MinParm: 1,
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
}
fmt.Println(Z.InferredUsage(x))
// Output:
// (p1|p2)
}
func ExampleInferredUsage_min_3_Param() {
x := &Z.Cmd{
Params: []string{"p1", "p2"},
MinParm: 3,
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
}
fmt.Println(Z.InferredUsage(x))
// Output:
// (p1|p2){3,}
}
func ExampleInferredUsage_commands() {
x := &Z.Cmd{
Commands: []*Z.Cmd{
&Z.Cmd{Name: "foo", Aliases: []string{"f"}},
&Z.Cmd{Name: "bar"},
},
}
fmt.Println(Z.InferredUsage(x))
// Output:
// ((f|foo)|bar)
}
func ExampleInferredUsage_commands_and_Params() {
x := &Z.Cmd{
Params: []string{"p1", "p2"},
Commands: []*Z.Cmd{
&Z.Cmd{Name: "foo", Aliases: []string{"f"}},
&Z.Cmd{Name: "bar"},
},
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
}
fmt.Println(Z.InferredUsage(x))
// Output:
// ((p1|p2)?|((f|foo)|bar))
}
func ExampleInferredUsage_error_No_Call_or_Command() {
x := &Z.Cmd{
Params: []string{"p1", "p2"},
}
fmt.Println(Z.InferredUsage(x))
// Output:
// {ERROR: neither Call nor Commands defined}
}
func ExampleInferredUsage_error_Params_without_Call() {
x := &Z.Cmd{
Params: []string{"p1", "p2"},
Commands: []*Z.Cmd{
&Z.Cmd{Name: "foo", Aliases: []string{"f"}},
&Z.Cmd{Name: "bar"},
},
}
fmt.Println(Z.InferredUsage(x))
// Output:
// {ERROR: Params without Call: p1, p2}
}
/*
func ExampleInferredUsage() {
// call method, params, aliases, commands, and hidden commands
// TODO
// [(p1|p2)|(h|help)]
// Call with optional params and also Commands
x := &Z.Cmd{
Params: []string{"p1", "p2"},
Commands: []*Z.Cmd{&Z.Cmd{Name: "foo"}, &Z.Cmd{Name: "bar"}},
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
}
fmt.Println(Z.InferredUsage(x))
// no Call, Command required
x = &Z.Cmd{
Commands: []*Z.Cmd{&Z.Cmd{Name: "foo"}, &Z.Cmd{Name: "bar"}},
}
fmt.Println(Z.InferredUsage(x))
// no Call with unused params (ERROR)
x = &Z.Cmd{
Params: []string{"p1", "p2"},
}
fmt.Println(Z.InferredUsage(x))
// Call with optional Params, but no commands
x = &Z.Cmd{
Params: []string{"p1", "p2"},
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
}
fmt.Println(Z.InferredUsage(x))
// Call with optional Commands, but no params
x = &Z.Cmd{
Commands: []*Z.Cmd{&Z.Cmd{Name: "foo"}, &Z.Cmd{Name: "bar"}},
Call: func(_ *Z.Cmd, _ ...string) error { return nil },
}
fmt.Println(Z.InferredUsage(x))
// no Call, Commands, or Params (ERROR)
x = &Z.Cmd{}
fmt.Println(Z.InferredUsage(x))
// Output:
// [p1|p2|foo|bar]
// (foo|bar)
// {ERROR: Params without Call: p1, p2}
// [p1|p2]
// [foo|bar]
// {ERROR: neither Call nor Commands defined}
}
*/

@ -14,38 +14,87 @@ import (
"github.com/rwxrob/fn/each"
"github.com/rwxrob/fn/maps"
"github.com/rwxrob/structs/qstack"
"github.com/rwxrob/to"
)
type Cmd struct {
Name string `json:"name,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Summary string `json:"summary,omitempty"`
Usage string `json:"usage,omitempty"`
Version string `json:"version,omitempty"`
Updated int `json:"updated,omitempty"` // isosec
Copyright string `json:"copyright,omitempty"`
License string `json:"license,omitempty"`
Description string `json:"description,omitempty"`
Site string `json:"site,omitempty"`
Source string `json:"source,omitempty"`
Issues string `json:"issues,omitempty"`
Other map[string]string `json:"issues,omitempty"`
Commands []*Cmd `json:"commands,omitempty"`
Params []string `json:"params,omitempty"`
Hidden []string `json:"hide,omitempty"`
Completer comp.Completer `json:"-"`
Conf conf.Configurer `json:"-"`
// Cache cache.Cacher `json:"-"`
Name string `json:"name,omitempty"`
Aliases []string `json:"aliases,omitempty"`
Summary string `json:"summary,omitempty"`
Usage string `json:"usage,omitempty"`
Version string `json:"version,omitempty"`
Copyright string `json:"copyright,omitempty"`
License string `json:"license,omitempty"`
Description string `json:"description,omitempty"`
Site string `json:"site,omitempty"`
Source string `json:"source,omitempty"`
Issues string `json:"issues,omitempty"`
Commands []*Cmd `json:"commands,omitempty"`
Params []string `json:"params,omitempty"`
Hidden []string `json:"hidden,omitempty"`
Other map[string]string `json:"other,omitempty"`
Completer comp.Completer `json:"-"`
Conf conf.Configurer `json:"-"`
UsageFunc func(x *Cmd) string `json:"-"`
Root *Cmd `json:"-"`
Caller *Cmd `json:"-"`
Call Method `json:"-"`
MinArgs int `json:"-"`
MinArgs int `json:"-"` // minimum number of arguments required
MinParm int `json:"-"` // minimum number of params required
_aliases map[string]*Cmd
}
// Names returns the Name and any Aliases grouped such that the Name is
// always last.
func (x *Cmd) Names() []string {
var names []string
names = append(names, x.Aliases...)
names = append(names, x.Name)
return names
}
// UsageNames returns single name, joined Names with bar (|) and wrapped
// in parentheses, or empty string if no names.
func (x *Cmd) UsageNames() string { return to.UsageGroup(x.Names()) }
// Title returns a dynamic field of Name and Summary combined (if
// exists).
func (x *Cmd) Title() string {
switch {
case len(x.Summary) > 0 && len(x.Version) > 0:
return x.Name + " (" + x.Version + ")" + " - " + x.Summary
case len(x.Summary) > 0:
return x.Name + " - " + x.Summary
default:
return x.Name
}
}
// Legal returns a single line with the combined values of the
// Name, Version, Copyright, and License. If Version is empty or nil an
// empty string is returned instead. Legal() is used by the
// version builtin command to aggregate all the version information into
// a single output.
func (x *Cmd) Legal() string {
switch {
case len(x.Copyright) > 0 && len(x.License) > 0 && len(x.Version) > 0:
return x.Name + " (" + x.Version + ") " +
x.Copyright + "\nLicense " + x.License
case len(x.Copyright) > 0 && len(x.License) > 0:
return x.Name + " " + x.Copyright + "\nLicense " + x.License
case len(x.Copyright) > 0 && len(x.Version) > 0:
return x.Name + " (" + x.Version + ") " + x.Copyright
case len(x.Copyright) > 0:
return x.Name + "\n" + x.Copyright
default:
return ""
}
}
func (x *Cmd) cacheAliases() {
x._aliases = map[string]*Cmd{}
if x.Commands == nil {
@ -136,9 +185,17 @@ func (x *Cmd) Run() {
Exit()
}
// UsageError returns an error with a single-line usage string.
// UsageError returns an error with a single-line usage string. The word
// "usage" can be changed by assigning Z.UsageText to something else.
// The commands own UsageFunc will be used if defined. If undefined, the
// Z.DefaultUsageFunc will be used instead (which can also be assigned
// to something else if needed).
func (x *Cmd) UsageError() error {
return fmt.Errorf("usage: %v %v", x.Name, x.Usage)
usage := x.UsageFunc
if usage == nil {
usage = DefaultUsageFunc
}
return fmt.Errorf("%v: %v %v", UsageText, x.Name, usage(x))
}
// Unimplemented returns an error with a single-line usage string.

@ -7,8 +7,8 @@ import (
"fmt"
"os"
Z "github.com/rwxrob/bonzai"
"github.com/rwxrob/bonzai/inc/help"
"github.com/rwxrob/bonzai/help"
Z "github.com/rwxrob/bonzai/z"
)
func ExampleCmd_Seek() {
@ -146,3 +146,65 @@ func ExampleCmd_Branch() {
// Output:
// some.thing.deep
}
func ExampleCmd_Names() {
x := &Z.Cmd{
Name: `foo`,
Aliases: []string{"f", "FOO"},
}
fmt.Println(x.Names())
//Output:
// [f FOO foo]
}
func ExampleCmd_UsageNames() {
x := &Z.Cmd{
Name: `foo`,
Aliases: []string{"f", "FOO"},
}
fmt.Println(x.UsageNames())
//Output:
// (f|FOO|foo)
}
func ExampleCmd_UsageError_commands_with_Aliases() {
x := &Z.Cmd{
Name: `cmd`,
Commands: []*Z.Cmd{
&Z.Cmd{Name: "foo", Aliases: []string{"f"}},
&Z.Cmd{Name: "bar"},
},
}
fmt.Println(x.UsageError())
// Output:
// usage: cmd ((f|foo)|bar)
}
func ExampleCmd_UsageError_params_but_No_Call() {
x := &Z.Cmd{
Name: `cmd`,
Params: []string{"p1", "p2"},
Commands: []*Z.Cmd{
&Z.Cmd{Name: "foo", Aliases: []string{"f"}},
&Z.Cmd{Name: "bar"},
},
}
fmt.Println(x.UsageError())
// Output:
// usage: cmd {ERROR: Params without Call: p1, p2}
}
func ExampleCmd_UsageError_no_Call_nor_Commands() {
x := &Z.Cmd{
Name: `cmd`,
}
fmt.Println(x.UsageError())
// Output:
// usage: cmd {ERROR: neither Call nor Commands defined}
}

@ -5,7 +5,7 @@ import (
"net/http"
ht "net/http/httptest"
Z "github.com/rwxrob/bonzai"
Z "github.com/rwxrob/bonzai/z"
)
func ExampleCompareUpdated() {
Loading…
Cancel
Save