Name the draw option drawGroup, refuse one whose reads all sit behind a nested draw group, and record why the two fences stay apart
Tests / vet + fmt + tests (pull_request) Successful in 1m7s

This commit is contained in:
2026-09-16 08:55:55 +02:00
parent 53a87da9b7
commit 68d2461704
7 changed files with 111 additions and 78 deletions
+28 -21
View File
@@ -127,8 +127,8 @@ renders nothing by `Fake`, and is compiled only so the tree's fences still run.
The columns are the point, and their facts stay together: a record is one render, so The columns are the point, and their facts stay together: a record is one render, so
columns that read a path into one category — `{/currency.code}` and columns that read a path into one category — `{/currency.code}` and
`{/currency.symbol}` — read one draw of it ([References](#references)), and a `{/currency.symbol}` — read one draw of it ([References](#references)), and a
[group](#group) draws a column apart. That one draw is also why the columns of one [draw group](#draw-group) draws a column apart. That one draw is also why the columns of one
record may not overlap — `{/cat.a}`, or a bare `{/cat}` rendering it, beside record may not overlap — `{/cat.a}`, or a bare `{/cat}`, beside
`{/cat.a.b}` is refused, naming the fields to write instead, as `{/cat.a.b}` is refused, naming the fields to write instead, as
[One draw, one spelling](#one-draw-one-spelling) refuses that pair inside a single [One draw, one spelling](#one-draw-one-spelling) refuses that pair inside a single
format. A column may not reference the record it belongs to by any spelling: `{/users.first}` format. A column may not reference the record it belongs to by any spelling: `{/users.first}`
@@ -341,7 +341,7 @@ different datatypes.
### Options and fields ### Options and fields
`format`, `weight`, `repeat`, `separator`, `datatype` and `group` are the only options; `format`, `weight`, `repeat`, `separator`, `datatype` and `drawGroup` are the only options;
**any other key is a field** (see [Decisions](#decisions)). An object that does nothing a **any other key is a field** (see [Decisions](#decisions)). An object that does nothing a
string can't — only a `format` — is rejected naming the string, as is a one-item string can't — only a `format` — is rejected naming the string, as is a one-item
choice naming its item. choice naming its item.
@@ -443,7 +443,7 @@ Renders e.g. `Hej, Pat Smith!`. A reference path into a category is held like a
record — rather than one format: `{.person.femalefirst} {.person.last}` name one record — rather than one format: `{.person.femalefirst} {.person.last}` name one
person, as do the same two references in sibling fields or a nested template, and person, as do the same two references in sibling fields or a nested template, and
`{lowercase(.person.femalefirst)}` reads that same draw. Each `repeat` iteration is `{lowercase(.person.femalefirst)}` reads that same draw. Each `repeat` iteration is
a render of its own, in no group, so it draws anew, and a [group](#group) holds a a render of its own, in no group, so it draws anew, and a [draw group](#draw-group) holds a
draw apart. A bare reference names no field and makes its own picks each time — draw apart. A bare reference names no field and makes its own picks each time —
`{/misc.uuid} {/misc.uuid}` is two draws — while the reference paths inside what it `{/misc.uuid} {/misc.uuid}` is two draws — while the reference paths inside what it
renders still read the render's draws. Rejected at `New`: a path that is renders still read the render's draws. Rejected at `New`: a path that is
@@ -451,27 +451,28 @@ unknown, names a folder, has no folder above, or reads a field not every variant
of a choice carries, and a reference that leads back to its own value, directly, of a choice carries, and a reference that leads back to its own value, directly,
mutually or through a chain. mutually or through a chain.
### Group ### Draw group
A template may carry `group` to hold its reference draws apart: every reference path A template may carry `drawGroup` to hold its reference draws apart: every reference path
it renders, however deep short of a `repeat`, reads the draw of that group, and the it renders, however deep short of a `repeat` or a nested `drawGroup`, reads the draw of
templates of one category naming one group read one draw. A group name is local to that group, and the templates of one category naming one group in one render read one
its category, so a category another one references never joins its groups by name; draw. A name is local to its category, so a category another one references never joins
the unnamed group spans them all. its groups by name; the unnamed group spans them all.
```json ```json
{ "format": "{payer} pays {payee}; signed {signature}", { "format": "{payer} pays {payee}; signed {signature}",
"payer": { "format": "{/sv_SE.person.femalefirst} {/sv_SE.person.last}", "group": "payer" }, "payer": { "format": "{/sv_SE.person.femalefirst} {/sv_SE.person.last}", "drawGroup": "payer" },
"payee": "{/sv_SE.person.femalefirst} {/sv_SE.person.last}", "payee": "{/sv_SE.person.femalefirst} {/sv_SE.person.last}",
"signature": { "format": "{/sv_SE.person.last}", "group": "payer" } } "signature": { "format": "{/sv_SE.person.last}", "drawGroup": "payer" } }
``` ```
Renders e.g. `Sara Eriksson pays Ebba Lind; signed Eriksson`: the signature reads the Renders e.g. `Sara Eriksson pays Ebba Lind; signed Eriksson`: the signature reads the
payer's draw, while the payee is drawn apart. Rejected at load, each naming nothing: a payer's draw, while the payee is drawn apart. Rejected at load, each naming nothing: a
`group` of `""` (the default); one naming the group its template already draws in; one `drawGroup` of `""` (the default); one naming the draw group its template already draws
on a template that renders no reference path short of a `repeat`, on a `repeat` itself — in; one on a template that renders no reference path short of a `repeat` or a nested
each iteration renders in no group — or on an inline template's root, which nothing `drawGroup`, on a `repeat` itself — each iteration renders in no draw group — or on an
references. So is a path reading into a level that carries a `group`. inline template's root, which nothing references. So is a path reading into a level that
carries a `drawGroup`.
### Correlated fields ### Correlated fields
@@ -505,9 +506,9 @@ The sub-fields stay addressable — `Fake("address.place.locality")` renders, an
A name any token reads as a path (`{p.first}`) or as an operand (`{calc(net * 2)}`, A name any token reads as a path (`{p.first}`) or as an operand (`{calc(net * 2)}`,
`{uppercase(w)}`) is drawn **once per expansion**, a reference path (`{/cat.p.first}`) `{uppercase(w)}`) is drawn **once per expansion**, a reference path (`{/cat.p.first}`)
**once per render** in its [group](#group), and every other route to either — a bare **once per render** in its [draw group](#draw-group), and every other route to either — a bare
`{p}`, a second bare `{w}`, `{/cat.net}`, a nested template rendering `{/cat.p.last}` `{p}`, a second bare `{w}`, `{/cat.net}`, a nested template rendering `{/cat.p.last}`
beside `{p.first}`, a bare `{/cat}` rendering what `{/cat.p.first}` reads, at any beside `{p.first}`, a bare `{/cat}` beside `{/cat.p.first}`, at any
depth — is a load error naming the spelling to use. A name nothing reads that way is depth — is a load error naming the spelling to use. A name nothing reads that way is
drawn each time: `{word} {word}` differs. An expansion is one render of one format, so drawn each time: `{word} {word}` differs. An expansion is one render of one format, so
each nested template draws its own names again; a render is one `Fake` or one record, each nested template draws its own names again; a render is one `Fake` or one record,
@@ -552,7 +553,7 @@ tokens add cost in proportion to the output.
## Decisions ## Decisions
- **Options and fields share one namespace.** `format`, `weight`, `repeat`, - **Options and fields share one namespace.** `format`, `weight`, `repeat`,
`separator`, `datatype` and `group` are reserved; every other key is a field. Nesting fields under a `separator`, `datatype` and `drawGroup` are reserved; every other key is a field. Nesting fields under a
key, or prefixing options, would tax every template to guard against a key, or prefixing options, would tax every template to guard against a
misspelt option. misspelt option.
- **`{a|b}` stays beside nested choices.** `[[…], […]]` picks the same way, but - **`{a|b}` stays beside nested choices.** `[[…], […]]` picks the same way, but
@@ -673,16 +674,22 @@ tokens add cost in proportion to the output.
value's facts agree across its fields, nested templates and columns alike — value's facts agree across its fields, nested templates and columns alike —
`{/currency.code}` in one field and `{/currency.symbol}` in another name one `{/currency.code}` in one field and `{/currency.symbol}` in another name one
currency, whichever view renders them. A `repeat` iteration is a render of its currency, whichever view renders them. A `repeat` iteration is a render of its
own, since repeating asks for another entity, and a [group](#group) names further own, since repeating asks for another entity, and a [draw group](#draw-group) names further
entities within one render, so a payer and a payee are two groups over one entities within one render, so a payer and a payee are two groups over one
`person` rather than two copies of it. Only references share: a sibling field is `person` rather than two copies of it. Only references share: a sibling field is
local to its own expansion, so a `first` column does not silently bind to a local to its own expansion, so a `first` column does not silently bind to a
`first` in the column next to it. `first` in the column next to it.
- **A group name is local to its category.** A category's groups are its own - **A draw group name is local to its category.** A category's groups are its own
entities, so a caller naming a group the same way never joins them by accident, entities, so a caller naming a group the same way never joins them by accident,
and renaming a group inside one file changes no render elsewhere. The unnamed and renaming a group inside one file changes no render elsewhere. The unnamed
group still spans categories, since facts that belong together across categories group still spans categories, since facts that belong together across categories
must agree. must agree.
- **The expansion hold and the render's draws are two fences.** One proves a sibling
path or an operand is reached only by its readers within an expansion, the other
does the same for reference paths across a render and its draw groups. They pin
different things — an operand pins the value its own render produced and stops at a
reference, a path pins every level it passes through — so one walk would carry both
rules and both scopes anyway, and tell them apart at every step.
- **A record's column set is fixed before the first draw.** Only a category-level - **A record's column set is fixed before the first draw.** Only a category-level
template is a record: a path descending into a field, or naming a folder or a template is a record: a path descending into a field, or naming a folder or a
choice, errors. A tail may pass through a choice whose variants carry different choice, errors. A tail may pass through a choice whose variants carry different
+66 -40
View File
@@ -6,13 +6,13 @@ import (
"strings" "strings"
) )
// drawSet is one render's reference draws: the unnamed group's, and each named group's. // drawSet is one render's reference draws: the unnamed draw group's, and each named one's.
type drawSet struct { type drawSet struct {
unnamed draws unnamed draws
named map[string]*draws named map[string]*draws
} }
// drawScope is where a render reads its reference paths: a draw set, in the group of the // drawScope is where a render reads its reference paths: a draw set, in the draw group of the
// template rendering. // template rendering.
type drawScope struct { type drawScope struct {
set *drawSet set *drawSet
@@ -31,15 +31,15 @@ func renderOnce(s *session, n node) string {
return render(s, n, drawScope{set: &set}) return render(s, n, drawScope{set: &set})
} }
// in is the scope t renders in: its group where it names one, else its caller's. // in is the scope t renders in: its draw group where it names one, else its caller's.
func (sc drawScope) in(t *template) drawScope { func (sc drawScope) in(t *template) drawScope {
if t.groupKey != "" { if t.drawGroupKey != "" {
sc.group = t.groupKey sc.group = t.drawGroupKey
} }
return sc return sc
} }
// draws is the set's draws for sc's group. // draws is the set's draws for sc's draw group.
func (sc drawScope) draws() *draws { func (sc drawScope) draws() *draws {
if sc.group == "" { if sc.group == "" {
return &sc.set.unnamed return &sc.set.unnamed
@@ -55,36 +55,36 @@ func (sc drawScope) draws() *draws {
return d return d
} }
// groupOf reads a template's "group" (default ""), which a repeat cannot carry: each iteration // drawGroupOf reads a template's "drawGroup" (default ""), which a repeat cannot carry: each
// renders in no group. // iteration renders in no draw group.
func groupOf(m map[string]any, repeat int) (string, error) { func drawGroupOf(m map[string]any, repeat int) (string, error) {
v, ok := m["group"] v, ok := m["drawGroup"]
if !ok { if !ok {
return "", nil return "", nil
} }
name, ok := v.(string) name, ok := v.(string)
switch { switch {
case !ok: case !ok:
return "", fmt.Errorf("group must be a string, got %T", v) return "", fmt.Errorf("drawGroup must be a string, got %T", v)
case name == "": case name == "":
return "", fmt.Errorf(`group "" is the default, so it has no effect; drop it`) return "", fmt.Errorf(`drawGroup "" is the default, so it has no effect; drop it`)
case repeat > 1: case repeat > 1:
return "", fmt.Errorf("group %q on a repeat names nothing, since each iteration is a render of its own; drop it", name) return "", fmt.Errorf("drawGroup %q on a repeat names nothing, since each iteration is a render of its own; drop it", name)
} }
return name, nil return name, nil
} }
// keyGroup keys t's group by the category t sits in, "" for an inline template, so a group name is // keyDrawGroup keys t's draw group by the category t sits in, "" for an inline template, so a name
// local to its category. // is local to its category.
func (t *template) keyGroup(category string) { func (t *template) keyDrawGroup(category string) {
if t.group != "" { if t.drawGroup != "" {
t.groupKey = category + "/" + t.group t.drawGroupKey = category + "/" + t.drawGroup
} }
} }
// checkNestedGroup refuses a template beneath one drawing in group that names group again, short of // checkNestedDrawGroup refuses a template beneath one drawing in group that names group again,
// a repeat or another group. // short of a repeat or another draw group.
func checkNestedGroup(fields map[string]node, group string) error { func checkNestedDrawGroup(fields map[string]node, group string) error {
if group == "" { if group == "" {
return nil return nil
} }
@@ -92,9 +92,9 @@ func checkNestedGroup(fields map[string]node, group string) error {
walk = func(path string, n node) error { walk = func(path string, n node) error {
t, isTemplate := n.(*template) t, isTemplate := n.(*template)
switch { switch {
case isTemplate && t.group == group: case isTemplate && t.drawGroup == group:
return fmt.Errorf("%q names group %q, the group this template draws in already; drop it", path, group) return fmt.Errorf("%q names drawGroup %q, the draw group this template draws in already; drop it", path, group)
case isTemplate && (t.group != "" || t.repeat > 1): case isTemplate && (t.drawGroup != "" || t.repeat > 1):
return nil return nil
} }
for _, c := range contained(n) { for _, c := range contained(n) {
@@ -115,14 +115,15 @@ func checkNestedGroup(fields map[string]node, group string) error {
// drawCheck fences each template of a scope as a render of its own, remembering which nodes read a // drawCheck fences each template of a scope as a render of its own, remembering which nodes read a
// reference path. // reference path.
type drawCheck struct { type drawCheck struct {
reads map[node]bool reads map[node]bool
splits map[node]bool
} }
// checkGroup refuses a group that splits nothing: one whose render reads no reference path short of // checkDrawGroup refuses a draw group that splits nothing: one whose render reads every reference
// a repeat. // path inside a repeat or a nested draw group, which draw apart from it whatever it names.
func (c *drawCheck) checkGroup(path string, n node) error { func (c *drawCheck) checkDrawGroup(path string, n node) error {
if t, ok := n.(*template); ok && t.group != "" && !c.readsPath(t) { if t, ok := n.(*template); ok && t.drawGroup != "" && !c.splitsDraws(t) {
return fmt.Errorf("%s: group %q splits nothing, since nothing it renders reads a reference path; drop it", path, t.group) return fmt.Errorf("%s: drawGroup %q splits nothing, since nothing it renders reads a reference path outside a repeat or a nested drawGroup; drop it", path, t.drawGroup)
} }
return nil return nil
} }
@@ -134,7 +135,7 @@ func (c *drawCheck) checkDraws(path string, n node) error {
} }
w := newDrawWalk(nil) w := newDrawWalk(nil)
for _, e := range renderEdges(t) { for _, e := range renderEdges(t) {
w.edge(t, e, drawAt{group: t.groupKey, route: drawRoute{e.reached(), e.label}}) w.edge(t, e, drawAt{group: t.drawGroupKey, route: drawRoute{e.reached(), e.label}})
} }
if err := w.check(); err != nil { if err := w.check(); err != nil {
return fmt.Errorf("%s: %w", path, err) return fmt.Errorf("%s: %w", path, err)
@@ -142,8 +143,8 @@ func (c *drawCheck) checkDraws(path string, n node) error {
return nil return nil
} }
// readsPath reports whether rendering n reads a reference path, short of a repeat, which renders over // readsPath reports whether rendering n reads a reference path, short of a repeat, which renders
// draws of its own. // over draws of its own.
func (c *drawCheck) readsPath(n node) bool { func (c *drawCheck) readsPath(n node) bool {
if r, done := c.reads[n]; done { if r, done := c.reads[n]; done {
return r return r
@@ -162,11 +163,36 @@ func (c *drawCheck) readsPath(n node) bool {
return r return r
} }
// splitsDraws reports whether rendering n reads a reference path that n's own draw group answers
// for: one outside a repeat and outside a nested draw group, which hold their own draws.
func (c *drawCheck) splitsDraws(n node) bool {
if r, done := c.splits[n]; done {
return r
}
r := false
for _, e := range renderEdges(n) {
if a, _, isRef := refRead(n, e.label); (isRef && len(a.tail) > 0) || (!repeats(e.to) && !grouped(e.to) && c.splitsDraws(e.to)) {
r = true
break
}
}
if c.splits == nil {
c.splits = map[node]bool{}
}
c.splits[n] = r
return r
}
func repeats(n node) bool { func repeats(n node) bool {
t, isTemplate := n.(*template) t, isTemplate := n.(*template)
return isTemplate && t.repeat > 1 return isTemplate && t.repeat > 1
} }
func grouped(n node) bool {
t, isTemplate := n.(*template)
return isTemplate && t.drawGroup != ""
}
// refRead is the reference an edge of n reads, and the node it is bound to; false when the edge // refRead is the reference an edge of n reads, and the node it is bound to; false when the edge
// reads none. // reads none.
func refRead(n node, label string) (arm, node, bool) { func refRead(n node, label string) (arm, node, bool) {
@@ -182,13 +208,13 @@ func refRead(n node, label string) (arm, node, bool) {
func checkColumnDraws(t *template, columns []string) error { func checkColumnDraws(t *template, columns []string) error {
w := newDrawWalk(t) w := newDrawWalk(t)
for _, name := range columns { for _, name := range columns {
w.walk(t.fields[name], drawAt{group: t.groupKey, route: drawRoute{spelling: fmt.Sprintf("column %q", name)}}) w.walk(t.fields[name], drawAt{group: t.drawGroupKey, route: drawRoute{spelling: fmt.Sprintf("column %q", name)}})
} }
return w.check() return w.check()
} }
// drawWalk gathers what one render reads by reference and what it draws afresh, each by group, for // drawWalk gathers what one render reads by reference and what it draws afresh, each by draw group,
// check to compare. record is set for a record's columns, which may not read the record back. // for check to compare. record is set for a record's columns, which may not read the record back.
type drawWalk struct { type drawWalk struct {
record *template record *template
reads []pathRead reads []pathRead
@@ -198,7 +224,7 @@ type drawWalk struct {
err error err error
} }
// drawAt is where a walk stands: the group it draws in, how the render's root reached it, the // drawAt is where a walk stands: the draw group it draws in, how the render's root reached it, the
// reference it last crossed, and whether it renders inside a reference path's draw. // reference it last crossed, and whether it renders inside a reference path's draw.
type drawAt struct { type drawAt struct {
group string group string
@@ -251,8 +277,8 @@ func (w *drawWalk) walk(n node, at drawAt) {
if repeats(n) { if repeats(n) {
return return
} }
if t, isTemplate := n.(*template); isTemplate && t.groupKey != "" { if t, isTemplate := n.(*template); isTemplate && t.drawGroupKey != "" {
at.group = t.groupKey at.group = t.drawGroupKey
} }
for _, e := range renderEdges(n) { for _, e := range renderEdges(n) {
w.edge(n, e, at) w.edge(n, e, at)
@@ -331,7 +357,7 @@ func (w *drawWalk) checkFreshDraws() error {
} }
func overlapError(route drawRoute, ref string, into pathRead) error { func overlapError(route drawRoute, ref string, into pathRead) error {
return fmt.Errorf("%s renders a level that %s reads a path into; name the fields you want instead, or draw them apart with a group", route.spelled(ref), into.at.route.spelled(into.a.name)) return fmt.Errorf("%s renders a level that %s reads a path into; name the fields you want instead, or draw them apart with a drawGroup", route.spelled(ref), into.at.route.spelled(into.a.name))
} }
// spelled names the route, and the reference it reaches a draw by where its root edge is not that // spelled names the route, and the reference it reaches a draw by where its root edge is not that
+1 -1
View File
@@ -197,7 +197,7 @@ func checkRenders(s nodeScope) error {
return err return err
} }
fence := &drawCheck{} fence := &drawCheck{}
if err := s(fence.checkGroup); err != nil { if err := s(fence.checkDrawGroup); err != nil {
return err return err
} }
if err := s(fence.checkDraws); err != nil { if err := s(fence.checkDraws); err != nil {
+3 -3
View File
@@ -136,8 +136,8 @@ func inputValue(input string) (any, error) {
// bindInline links an inline node's references against root and runs check over it, naming its // bindInline links an inline node's references against root and runs check over it, naming its
// nodes from label. // nodes from label.
func bindInline(n node, label string, root map[string]node, check func(nodeScope) error) error { func bindInline(n node, label string, root map[string]node, check func(nodeScope) error) error {
if t, isTemplate := n.(*template); isTemplate && t.group != "" { if t, isTemplate := n.(*template); isTemplate && t.drawGroup != "" {
return fmt.Errorf("%s: group %q names nothing, since nothing can reference an inline template; drop it", label, t.group) return fmt.Errorf("%s: drawGroup %q names nothing, since nothing can reference an inline template; drop it", label, t.drawGroup)
} }
scope := inlineScope(n, label) scope := inlineScope(n, label)
if err := linkNodeRefs(scope, root); err != nil { if err := linkNodeRefs(scope, root); err != nil {
@@ -154,7 +154,7 @@ func linkNodeRefs(scope nodeScope, root map[string]node) error {
if !ok { if !ok {
return nil return nil
} }
t.keyGroup("") t.keyDrawGroup("")
for _, name := range refTokens(t.format) { for _, name := range refTokens(t.format) {
sigil, rest, err := refShape(name) sigil, rest, err := refShape(name)
if err != nil { if err != nil {
+10 -10
View File
@@ -58,12 +58,12 @@ type template struct {
bound map[string]string bound map[string]string
// held is every name drawn once per expansion: the bound levels above, plus the // held is every name drawn once per expansion: the bound levels above, plus the
// siblings a {calc()} reads. nil when the format holds nothing (see expand). // siblings a {calc()} reads. nil when the format holds nothing (see expand).
held map[string]bool held map[string]bool
fromString bool // written as a JSON string rather than an object fromString bool // written as a JSON string rather than an object
readsColumn *columnRead // set when the format is one reference alone reading a record's column readsColumn *columnRead // set when the format is one reference alone reading a record's column
record bool // compiled at the top without a repeat, so its fields are record columns record bool // compiled at the top without a repeat, so its fields are record columns
group string // the group it draws in, as written; "" keeps its caller's drawGroup string // the draw group it draws in, as written; "" keeps its caller's
groupKey string // group keyed by its category once linked, what a render reads its reference paths under drawGroupKey string // its draw group keyed by its category once linked: what a render reads its reference paths under
} }
func (*template) isNode() {} func (*template) isNode() {}
@@ -249,10 +249,10 @@ func compileTemplate(m map[string]any, pos position) (node, error) {
if err := checkTokens(o.format, fields); err != nil { if err := checkTokens(o.format, fields); err != nil {
return nil, err return nil, err
} }
if err := checkNestedGroup(fields, o.group); err != nil { if err := checkNestedDrawGroup(fields, o.group); err != nil {
return nil, err return nil, err
} }
t := &template{format: o.format, fields: fields, repeat: o.repeat, separator: o.separator, datatype: o.datatype, group: o.group, record: fieldPos == inColumn} t := &template{format: o.format, fields: fields, repeat: o.repeat, separator: o.separator, datatype: o.datatype, drawGroup: o.group, record: fieldPos == inColumn}
if err := t.compileFormat(); err != nil { if err := t.compileFormat(); err != nil {
return nil, err return nil, err
} }
@@ -284,7 +284,7 @@ func readOptions(m map[string]any, pos position) (templateOptions, error) {
if o.datatype, err = datatypeOf(m, pos); err != nil { if o.datatype, err = datatypeOf(m, pos); err != nil {
return o, err return o, err
} }
if o.group, err = groupOf(m, repeat); err != nil { if o.group, err = drawGroupOf(m, repeat); err != nil {
return o, err return o, err
} }
if sv, ok := m["separator"]; ok { if sv, ok := m["separator"]; ok {
@@ -419,7 +419,7 @@ func checkPathNames(path string) error {
// field. These names can never be fields. // field. These names can never be fields.
func isOption(name string) bool { func isOption(name string) bool {
switch name { switch name {
case "datatype", "format", "group", "repeat", "separator", "weight": case "datatype", "drawGroup", "format", "repeat", "separator", "weight":
return true return true
} }
return false return false
+2 -2
View File
@@ -105,8 +105,8 @@ func checkPath(n node, tail []string, level string) error {
switch { switch {
case t.repeat > 1: case t.repeat > 1:
return fmt.Errorf("the level %q carries a repeat, which a path reading one draw of it cannot apply", name) return fmt.Errorf("the level %q carries a repeat, which a path reading one draw of it cannot apply", name)
case t.group != "": case t.drawGroup != "":
return fmt.Errorf("the level %q carries a group, which a path reading into it cannot apply", name) return fmt.Errorf("the level %q carries a drawGroup, which a path reading into it cannot apply", name)
} }
return nil return nil
}, },
+1 -1
View File
@@ -72,7 +72,7 @@ func refSegments(name string, folder []string) ([]string, error) {
// error, never a random render-time one. // error, never a random render-time one.
func linkRefs(root map[string]node) error { func linkRefs(root map[string]node) error {
return eachTemplate(root, func(folder []string, path string, t *template) error { return eachTemplate(root, func(folder []string, path string, t *template) error {
t.keyGroup(strings.Join(strings.Split(path, ".")[:len(folder)+1], ".")) t.keyDrawGroup(strings.Join(strings.Split(path, ".")[:len(folder)+1], "."))
return linkTemplateRefs(folder, path, t, root) return linkTemplateRefs(folder, path, t, root)
}) })
} }