diff --git a/README.md b/README.md index a4cc68b..fb4a067 100644 --- a/README.md +++ b/README.md @@ -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 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. +- **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 two readings disagree — a format string renders it, JSON drops it — so the spelling that renders is named rather than silently chosen. diff --git a/cmd/fejkdata/main.go b/cmd/fejkdata/main.go index a9f207a..d25d7a7 100644 --- a/cmd/fejkdata/main.go +++ b/cmd/fejkdata/main.go @@ -270,11 +270,8 @@ const ( argTemplate ) -// classify reads what a positional argument names by its shape: a format string -// carrying a { token, or a JSON object, array or string, is an inline template; -// 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. +// classify reads what a positional argument names by its shape: a { token, or a +// JSON object, array or string, is an inline template; anything else is a path. func classify(arg string) (argKind, error) { if strings.ContainsRune(arg, '{') || (isJSONStart(strings.TrimSpace(arg)) && json.Valid([]byte(arg))) { return argTemplate, nil diff --git a/graph.go b/graph.go index d3a8258..cf5cd5b 100644 --- a/graph.go +++ b/graph.go @@ -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 // 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 { seen := map[node]bool{} var visit func(string, node) error diff --git a/inline.go b/inline.go index eca8e6a..f68ca29 100644 --- a/inline.go +++ b/inline.go @@ -27,10 +27,6 @@ func (t *Template) Fake() string { // 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 // 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) { n, err := compileInput(input) 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 -// when the input is not JSON. A JSON string literal and a bare string compile -// 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. +// when the input is not JSON. func compileInput(input string) (node, error) { var raw any 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 -// loaded tree. An inline template sits in no folder, so . and .. name nothing and -// are rejected for the root spelling they would otherwise silently mean. +// loaded tree. func linkNodeRefs(scope nodeScope, root map[string]node) error { return scope(func(path string, m node) error { t, ok := m.(*template) diff --git a/node.go b/node.go index 9d1520d..384a2c6 100644 --- a/node.go +++ b/node.go @@ -302,7 +302,7 @@ func repeatOf(m map[string]any) (int, error) { if r == 1 { 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 int(r), nil