diff --git a/README.md b/README.md index 97c111a..04f333c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/bonzai/main.go b/bonzai/main.go index 2626069..9a7d871 100644 --- a/bonzai/main.go +++ b/bonzai/main.go @@ -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" ) diff --git a/bonzai_test.go b/bonzai_test.go deleted file mode 100644 index 36eacc3..0000000 --- a/bonzai_test.go +++ /dev/null @@ -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", "", "other&"} - fmt.Println(Z.EscAll(list)) - // Output: - // [so\!me \ other\&] -} diff --git a/go.mod b/go.mod index 51b8f15..136b868 100644 --- a/go.mod +++ b/go.mod @@ -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 -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 296d6bf..0000000 --- a/go.sum +++ /dev/null @@ -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= diff --git a/help/_help.go b/help/_help.go deleted file mode 100644 index 9facb87..0000000 --- a/help/_help.go +++ /dev/null @@ -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 - - }, -} -*/ diff --git a/help/help.go b/help/help.go index 92fd61f..5bd0521 100644 --- a/help/help.go +++ b/help/help.go @@ -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 { - - // - 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*** +// (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 , 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 +func Emphasize(buf string) string { + + // italic = `` + // bold = `` + // bolditalic = `` + // 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) } diff --git a/help/help_test.go b/help/help_test.go index 030892c..2b07fe4 100644 --- a/help/help_test.go +++ b/help/help_test.go @@ -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("")) - 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("")) + + // 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 } diff --git a/old/is/compound.go b/old/is/compound.go deleted file mode 100644 index 3553a5a..0000000 --- a/old/is/compound.go +++ /dev/null @@ -1,6 +0,0 @@ -package z - -// keep only compound expressions here - -var WS = I{' ', '\n', '\t', '\r'} -var Digit = R{0, 9} diff --git a/old/is/is.go b/old/is/is.go deleted file mode 100644 index de1875e..0000000 --- a/old/is/is.go +++ /dev/null @@ -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 -} diff --git a/old/mark/README.md b/old/mark/README.md deleted file mode 100644 index e8bb18a..0000000 --- a/old/mark/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Bonzai Mark (Simple Subset of CommonMark) - - diff --git a/old/mark/_mark.go b/old/mark/_mark.go deleted file mode 100644 index 71460cd..0000000 --- a/old/mark/_mark.go +++ /dev/null @@ -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 -// (), 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 -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 -} diff --git a/old/mark/_mark_test.go b/old/mark/_mark_test.go deleted file mode 100644 index 346af93..0000000 --- a/old/mark/_mark_test.go +++ /dev/null @@ -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***", ""} - 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 : - - 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 : - - 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 :\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() - } -} diff --git a/old/mark/ast/ast.go b/old/mark/ast/ast.go deleted file mode 100644 index bd41296..0000000 --- a/old/mark/ast/ast.go +++ /dev/null @@ -1 +0,0 @@ -package ast diff --git a/old/mark/grammar.pegn b/old/mark/grammar.pegn deleted file mode 100644 index 61a5565..0000000 --- a/old/mark/grammar.pegn +++ /dev/null @@ -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 )+ EndBlock -Bulleted <-- (!EndLine '*' SP )+ -Numbered <-- (!EndLine '1.' SP )+ -Verbatim <-- (!EndLine SP{4} 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 - diff --git a/old/mark/mark.go b/old/mark/mark.go deleted file mode 100644 index 8ff268d..0000000 --- a/old/mark/mark.go +++ /dev/null @@ -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}}) } diff --git a/old/mark/mark_test.go b/old/mark/mark_test.go deleted file mode 100644 index a4a76f9..0000000 --- a/old/mark/mark_test.go +++ /dev/null @@ -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: - // - // {"T":1,"N":[{"T":10,"V":"some thing here"}]} -} - -func ExampleBracketed() { - s := scan.New("") - s.TraceX() - s.X(mark.Bracketed) - s.Print() - s.Tree.Root.Print() - // Output: - // - // {"T":1,"N":[{"T":10,"V":"some thing here"}]} -} diff --git a/old/pegn.go b/old/pegn.go deleted file mode 100644 index d79de09..0000000 --- a/old/pegn.go +++ /dev/null @@ -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() - } -} diff --git a/old/pegn_test.go b/old/pegn_test.go deleted file mode 100644 index 09ed2b0..0000000 --- a/old/pegn_test.go +++ /dev/null @@ -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) - // -} - -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) -} diff --git a/old/scan.go b/old/scan.go deleted file mode 100644 index 431d1fa..0000000 --- a/old/scan.go +++ /dev/null @@ -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() - } - } -} diff --git a/old/scan_test.go b/old/scan_test.go deleted file mode 100644 index be4d5b8..0000000 --- a/old/scan_test.go +++ /dev/null @@ -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) - // -} - -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 - 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() -} diff --git a/old/trees.go b/old/trees.go deleted file mode 100644 index b080215..0000000 --- a/old/trees.go +++ /dev/null @@ -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 - } -} diff --git a/bonzai.go b/z/bonzai.go similarity index 81% rename from bonzai.go rename to z/bonzai.go index a37f102..2e235b3 100644 --- a/bonzai.go +++ b/z/bonzai.go @@ -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" diff --git a/z/bonzai_test.go b/z/bonzai_test.go new file mode 100644 index 0000000..47fede1 --- /dev/null +++ b/z/bonzai_test.go @@ -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", "", "other&"} + fmt.Println(Z.EscAll(list)) + // Output: + // [so\!me \ 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} + +} +*/ diff --git a/cmd.go b/z/cmd.go similarity index 68% rename from cmd.go rename to z/cmd.go index e707bc4..0996d2c 100644 --- a/cmd.go +++ b/z/cmd.go @@ -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. diff --git a/cmd_test.go b/z/cmd_test.go similarity index 70% rename from cmd_test.go rename to z/cmd_test.go index fc329e0..c5d570c 100644 --- a/cmd_test.go +++ b/z/cmd_test.go @@ -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} + +} diff --git a/exec.go b/z/exec.go similarity index 100% rename from exec.go rename to z/exec.go diff --git a/exec_test.go b/z/exec_test.go similarity index 100% rename from exec_test.go rename to z/exec_test.go diff --git a/testdata/in b/z/testdata/in similarity index 100% rename from testdata/in rename to z/testdata/in diff --git a/update.go b/z/update.go similarity index 100% rename from update.go rename to z/update.go diff --git a/update_test.go b/z/update_test.go similarity index 98% rename from update_test.go rename to z/update_test.go index 25ab6b5..29fa3df 100644 --- a/update_test.go +++ b/z/update_test.go @@ -5,7 +5,7 @@ import ( "net/http" ht "net/http/httptest" - Z "github.com/rwxrob/bonzai" + Z "github.com/rwxrob/bonzai/z" ) func ExampleCompareUpdated() {