Hold a path's last level too, and reject an unfinished path
This commit is contained in:
@@ -191,17 +191,14 @@ func resolve(s *session, arms []arm, t *template, bound *draws) string {
|
|||||||
}
|
}
|
||||||
// Walk the tail, holding the draw at every level the path passes through, so
|
// Walk the tail, holding the draw at every level the path passes through, so
|
||||||
// two paths sharing a prefix share every choice along it, not just the head.
|
// two paths sharing a prefix share every choice along it, not just the head.
|
||||||
|
// The last level is held too: another token may name it in full.
|
||||||
for i, seg := range a.tail {
|
for i, seg := range a.tail {
|
||||||
if i < len(a.steps) {
|
|
||||||
held, drew := bound.variant[a.steps[i]]
|
held, drew := bound.variant[a.steps[i]]
|
||||||
if !drew {
|
if !drew {
|
||||||
held = drawn(s, child(n, seg))
|
held = drawn(s, child(n, seg))
|
||||||
bound.variant[a.steps[i]] = held
|
bound.variant[a.steps[i]] = held
|
||||||
}
|
}
|
||||||
n = held
|
n = held
|
||||||
continue
|
|
||||||
}
|
|
||||||
n = child(n, seg)
|
|
||||||
}
|
}
|
||||||
v := render(s, n)
|
v := render(s, n)
|
||||||
bound.value[a.name] = v
|
bound.value[a.name] = v
|
||||||
|
|||||||
+27
-6
@@ -143,6 +143,9 @@ func checkTokens(format string, fields map[string]node) error {
|
|||||||
continue // a root reference; its target is checked at New (see linkRefs)
|
continue // a root reference; its target is checked at New (see linkRefs)
|
||||||
}
|
}
|
||||||
a := splitArm(name)
|
a := splitArm(name)
|
||||||
|
if err := checkSegments(a); err != nil {
|
||||||
|
return fmt.Errorf("token {%s}: %w", t.body, err)
|
||||||
|
}
|
||||||
head, ok := fields[a.key]
|
head, ok := fields[a.key]
|
||||||
if !ok {
|
if !ok {
|
||||||
if isOption(a.key) {
|
if isOption(a.key) {
|
||||||
@@ -180,9 +183,9 @@ type arm struct {
|
|||||||
name string // as written, and the key a bound draw's value is held under
|
name string // as written, and the key a bound draw's value is held under
|
||||||
key string
|
key string
|
||||||
tail []string
|
tail []string
|
||||||
// steps holds the key for each segment the walk passes *through* — the path
|
// steps holds the key for each segment the walk descends to, so every level a
|
||||||
// prefixes below the head and above the leaf. The head is bound under key and
|
// path passes through is held — including its last, which another path may
|
||||||
// the leaf's value under name, so a one-segment tail needs none of these.
|
// name in full ({p.addr} beside {p.addr.city}).
|
||||||
steps []string
|
steps []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,13 +200,31 @@ func splitArm(name string) arm {
|
|||||||
return arm{name: name, key: name}
|
return arm{name: name, key: name}
|
||||||
}
|
}
|
||||||
segs := strings.Split(tail, ".")
|
segs := strings.Split(tail, ".")
|
||||||
var steps []string
|
steps := make([]string, len(segs))
|
||||||
for i := 0; i < len(segs)-1; i++ { // every prefix except the leaf's own
|
for i := range segs {
|
||||||
steps = append(steps, head+"."+strings.Join(segs[:i+1], "."))
|
steps[i] = head + "." + strings.Join(segs[:i+1], ".")
|
||||||
}
|
}
|
||||||
return arm{name: name, key: head, tail: segs, steps: steps}
|
return arm{name: name, key: head, tail: segs, steps: steps}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkSegments rejects an unfinished path: "{a.}", "{.b}" and "{a..b}" each have
|
||||||
|
// a segment naming nothing. A field really named "" would otherwise make them
|
||||||
|
// resolve, so a typo would read as a path that worked.
|
||||||
|
func checkSegments(a arm) error {
|
||||||
|
if len(a.tail) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if a.key == "" {
|
||||||
|
return fmt.Errorf("path has an empty segment")
|
||||||
|
}
|
||||||
|
for _, seg := range a.tail {
|
||||||
|
if seg == "" {
|
||||||
|
return fmt.Errorf("path has an empty segment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// splitArms splits a token body's '|' alternatives.
|
// splitArms splits a token body's '|' alternatives.
|
||||||
func splitArms(body string) []arm {
|
func splitArms(body string) []arm {
|
||||||
parts := strings.Split(body, "|")
|
parts := strings.Split(body, "|")
|
||||||
|
|||||||
Reference in New Issue
Block a user