Cut the comments the README already records, and record the cycle-fence and exit-code rules there
Tests / vet + fmt + tests (pull_request) Successful in 58s
Tests / vet + fmt + tests (pull_request) Successful in 58s
This commit is contained in:
@@ -386,6 +386,15 @@ tokens add cost in proportion to the output.
|
|||||||
instead of a leading-position special case. The JSON string is what makes the
|
instead of a leading-position special case. The JSON string is what makes the
|
||||||
library's own advice reachable: the error for an object holding only a format
|
library's own advice reachable: the error for an object holding only a format
|
||||||
names `"…"`, and that spelling has to work where it is printed.
|
names `"…"`, and that spelling has to work where it is printed.
|
||||||
|
- **An inline template skips the cycle fence, and only that one.** `New` proves the
|
||||||
|
loaded tree acyclic, an inline node is a finite tree of its own, and nothing in
|
||||||
|
the tree can reference it, so no render of it reaches itself. Every other fence
|
||||||
|
runs over both, from one `checkScope`.
|
||||||
|
- **An inline template that does not compile is misuse (exit 2), including a
|
||||||
|
reference that resolves to nothing** — the whole argument is the spelling under
|
||||||
|
test, and `NewTemplate` compiles, links and validates as one step. An unknown
|
||||||
|
*path* stays a runtime error (exit 1): there the argument is well-formed and only
|
||||||
|
the data is absent.
|
||||||
- **A padded JSON argument is rejected, not trimmed.** Padding is the one place the
|
- **A padded JSON argument is rejected, not trimmed.** Padding is the one place the
|
||||||
two readings disagree — a format string renders it, JSON drops it — so the
|
two readings disagree — a format string renders it, JSON drops it — so the
|
||||||
spelling that renders is named rather than silently chosen.
|
spelling that renders is named rather than silently chosen.
|
||||||
|
|||||||
@@ -270,11 +270,8 @@ const (
|
|||||||
argTemplate
|
argTemplate
|
||||||
)
|
)
|
||||||
|
|
||||||
// classify reads what a positional argument names by its shape: a format string
|
// classify reads what a positional argument names by its shape: a { token, or a
|
||||||
// carrying a { token, or a JSON object, array or string, is an inline template;
|
// JSON object, array or string, is an inline template; anything else is a path.
|
||||||
// anything else is a path. A name may hold neither a brace nor a bracket nor a
|
|
||||||
// quote, so no path collides with any of those spellings, and the JSON gate is
|
|
||||||
// valid-JSON so a copied bracket names nothing rather than swallowing an argument.
|
|
||||||
func classify(arg string) (argKind, error) {
|
func classify(arg string) (argKind, error) {
|
||||||
if strings.ContainsRune(arg, '{') || (isJSONStart(strings.TrimSpace(arg)) && json.Valid([]byte(arg))) {
|
if strings.ContainsRune(arg, '{') || (isJSONStart(strings.TrimSpace(arg)) && json.Valid([]byte(arg))) {
|
||||||
return argTemplate, nil
|
return argTemplate, nil
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func walkNodes(root map[string]node, fn func(path string, n node) error) error {
|
|||||||
|
|
||||||
// eachNode visits n and every node contained within it once, passing the dot path
|
// eachNode visits n and every node contained within it once, passing the dot path
|
||||||
// that reaches each. It never crosses a reference edge — a bound {/path} field is
|
// that reaches each. It never crosses a reference edge — a bound {/path} field is
|
||||||
// skipped, as in walkNodes — so a single inline node is walked on its own.
|
// skipped — so a single inline node is walked on its own.
|
||||||
func eachNode(n node, path string, fn func(path string, n node) error) error {
|
func eachNode(n node, path string, fn func(path string, n node) error) error {
|
||||||
seen := map[node]bool{}
|
seen := map[node]bool{}
|
||||||
var visit func(string, node) error
|
var visit func(string, node) error
|
||||||
|
|||||||
@@ -27,10 +27,6 @@ func (t *Template) Fake() string {
|
|||||||
// binds its references against the loaded tree, so repeated renders pay the
|
// binds its references against the loaded tree, so repeated renders pay the
|
||||||
// compile and validation once. It shares [New]'s guarantees: a bad template errors
|
// compile and validation once. It shares [New]'s guarantees: a bad template errors
|
||||||
// here, and rendering cannot fail.
|
// here, and rendering cannot fail.
|
||||||
//
|
|
||||||
// checkNoCycles is the one fence loadData runs that an inline node does not need:
|
|
||||||
// the loaded tree is proven acyclic at [New], the node is a finite tree, and no
|
|
||||||
// tree node can reference it, so nothing it renders can reach itself.
|
|
||||||
func (f *Generator) NewTemplate(input string) (*Template, error) {
|
func (f *Generator) NewTemplate(input string) (*Template, error) {
|
||||||
n, err := compileInput(input)
|
n, err := compileInput(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -58,12 +54,7 @@ func (f *Generator) FakeTemplate(input string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compileInput compiles an inline template: a JSON value, or a bare format string
|
// compileInput compiles an inline template: a JSON value, or a bare format string
|
||||||
// when the input is not JSON. A JSON string literal and a bare string compile
|
// when the input is not JSON.
|
||||||
// alike (both are a template with no fields); the other JSON scalars — a number,
|
|
||||||
// bool or null — are no template, so they are rejected here, as a data file that
|
|
||||||
// was one would be at load. Padding is where the two readings would disagree — a
|
|
||||||
// format string renders it, JSON drops it — so a padded JSON value is rejected
|
|
||||||
// naming the one that renders.
|
|
||||||
func compileInput(input string) (node, error) {
|
func compileInput(input string) (node, error) {
|
||||||
var raw any
|
var raw any
|
||||||
if err := json.Unmarshal([]byte(input), &raw); err != nil {
|
if err := json.Unmarshal([]byte(input), &raw); err != nil {
|
||||||
@@ -76,8 +67,7 @@ func compileInput(input string) (node, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// linkNodeRefs binds the references in an inline node's templates against the
|
// linkNodeRefs binds the references in an inline node's templates against the
|
||||||
// loaded tree. An inline template sits in no folder, so . and .. name nothing and
|
// loaded tree.
|
||||||
// are rejected for the root spelling they would otherwise silently mean.
|
|
||||||
func linkNodeRefs(scope nodeScope, root map[string]node) error {
|
func linkNodeRefs(scope nodeScope, root map[string]node) error {
|
||||||
return scope(func(path string, m node) error {
|
return scope(func(path string, m node) error {
|
||||||
t, ok := m.(*template)
|
t, ok := m.(*template)
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ func repeatOf(m map[string]any) (int, error) {
|
|||||||
if r == 1 {
|
if r == 1 {
|
||||||
return 0, fmt.Errorf("repeat 1 is the default, so it has no effect; drop it")
|
return 0, fmt.Errorf("repeat 1 is the default, so it has no effect; drop it")
|
||||||
}
|
}
|
||||||
if r > MaxRepeat { // caps the renders one repeat asks for; checkRepeatReach bounds what nested ones multiply to
|
if r > MaxRepeat { // caps the renders one repeat asks for; repeatCheck bounds what nested ones multiply to
|
||||||
return 0, fmt.Errorf("repeat %v exceeds the maximum %d", rv, MaxRepeat)
|
return 0, fmt.Errorf("repeat %v exceeds the maximum %d", rv, MaxRepeat)
|
||||||
}
|
}
|
||||||
return int(r), nil
|
return int(r), nil
|
||||||
|
|||||||
Reference in New Issue
Block a user