Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
@@ -588,8 +588,8 @@ It steps up from a selected row, a drawn row or a [name](#names) bound to a row,
|
||||
per `..`: `{l..municipality..region.name}`. `.country` after a row still reads the link
|
||||
column's cell, `NO`.
|
||||
|
||||
After a `..`, `.` steps down again, drawing afresh inside the row stepped up to, apart from
|
||||
the row the path stepped up from. Paths stepping down from one `..` read one draw, as one
|
||||
After a `..`, `.` steps down again, drawing afresh inside the row stepped up to,
|
||||
independent of the row the path stepped up from, which it may draw again. Paths stepping down from one `..` read one draw, as one
|
||||
reference path does: `{/city..country.city.name}` and `{/city..country.city.population}`
|
||||
describe one city, within a render and [draw group](#draw-group), or within a name's pick.
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ import (
|
||||
const usage = `Usage: fejkdata [flags] <path|template>
|
||||
|
||||
<path> a category, or a dotted path into one (person, person.last);
|
||||
a table's row by key or name: 'misc.territory[SE]', 'misc.territory[Sweden].tld'
|
||||
a table's row by key or name: 'misc.territory[SE]', 'misc.territory[Sweden].tld';
|
||||
.. steps up to the row a row links to: 'geo.SE.locality..municipality.name'
|
||||
<template> a format string or JSON value to render inline, e.g.
|
||||
'name: {/sv_SE.person.last}' or '{"format":"{x}","x":["bosse","lina"]}'
|
||||
|
||||
|
||||
+2
-2
@@ -232,8 +232,8 @@ func drawsApart(x, y pathRead) error {
|
||||
if s.t != x.tr.headTable {
|
||||
tail = append([]string{x.tr.headTable.segment}, tail...)
|
||||
}
|
||||
return fmt.Errorf("%s draws %s, which %s selects a row of; write {%s.%s}, or draw them apart with a drawGroup",
|
||||
x.route.spelled(x.a.spelling), drawn.segment, y.route.spelled(y.a.spelling), s.spelling, joinSegments(tail))
|
||||
return fmt.Errorf("%s draws %s, which %s selects a row of; write {%s}, or draw them apart with a drawGroup",
|
||||
x.route.spelled(x.a.spelling), drawn.segment, y.route.spelled(y.a.spelling), joinSegments(append([]string{s.spelling}, tail...)))
|
||||
}
|
||||
return fmt.Errorf("%s draws %s, which %s selects a row of; select that row in both, or draw them apart with a drawGroup",
|
||||
x.route.spelled(x.a.spelling), drawn.segment, y.route.spelled(y.a.spelling))
|
||||
|
||||
@@ -137,11 +137,11 @@ func hasSelector(segs []string) bool {
|
||||
|
||||
func selectorOf(seg string) string { return seg[1 : len(seg)-1] }
|
||||
|
||||
// nameSegments is the segments of a path that are names, its selectors left out.
|
||||
// nameSegments is the segments of a path that are names, its selectors and ".." left out.
|
||||
func nameSegments(segs []string) []string {
|
||||
out := segs[:0:0]
|
||||
for _, s := range segs {
|
||||
if !isSelector(s) {
|
||||
if !isSelector(s) && s != ".." {
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,7 @@ func stepInto(n node, seg string) (node, error) {
|
||||
if c, ok := n.(*tableColumn); ok {
|
||||
return nil, fmt.Errorf(`".." steps up from a table's row, and %q is a column; put the ".." right after the row`, c.t.header[c.i])
|
||||
}
|
||||
return nil, fmt.Errorf(`".." steps up from a table's row to the row it links to, and what it follows is not a table; step up only from a table`)
|
||||
return nil, fmt.Errorf(`".." steps up from a table's row to the row it links to, and what it follows is not a table; a path names every other level from the data root down`)
|
||||
}
|
||||
switch n := n.(type) {
|
||||
case *folder:
|
||||
@@ -170,6 +170,9 @@ func stepInto(n node, seg string) (node, error) {
|
||||
return child, nil
|
||||
}
|
||||
case *tableColumn:
|
||||
if n.i == n.t.parentIndex {
|
||||
return nil, fmt.Errorf("no field %q: %q is the link column, holding its parent's key; to read the row it links to, step up with ..%s.%s", seg, n.t.header[n.i], n.t.header[n.i], seg)
|
||||
}
|
||||
return nil, fmt.Errorf("no field %q: %q is a column, and a cell holds no fields", seg, n.t.header[n.i])
|
||||
case *nullItem:
|
||||
default:
|
||||
@@ -230,14 +233,29 @@ func (t *table) stepUp(rest []string) error {
|
||||
switch {
|
||||
case t.parentT == nil:
|
||||
return fmt.Errorf(`%s has no parent table for ".." to step up to`, t.segment)
|
||||
case len(rest) > 0 && rest[0] == "":
|
||||
return fmt.Errorf("path has an empty segment")
|
||||
case len(rest) == 0 || rest[0] != t.parentT.segment:
|
||||
return fmt.Errorf(`".." steps up from %s to its parent table, so name that next: ..%s`, t.segment, t.parentT.segment)
|
||||
return fmt.Errorf(`".." steps up from %s to its parent table, so name that next: %s`, t.segment, t.climbTo(rest))
|
||||
case hasSelector(rest):
|
||||
return fmt.Errorf(`a path selects its rows before a "..", since a row selected after it could lie outside the row it steps up to; select from the table instead, its path from the data root: %s`, joinSegments(append([]string{t.parentT.path}, rest[1:]...)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// climbTo spells the ".." steps from t up to the ancestor rest names first, or the one step to
|
||||
// t's parent where no ancestor is named.
|
||||
func (t *table) climbTo(rest []string) string {
|
||||
var b strings.Builder
|
||||
for a := t.parentT; a != nil; a = a.parentT {
|
||||
b.WriteString(".." + a.segment)
|
||||
if len(rest) > 0 && a.segment == rest[0] {
|
||||
return b.String()
|
||||
}
|
||||
}
|
||||
return ".." + t.parentT.segment
|
||||
}
|
||||
|
||||
// pathStep is one step of a compiled path, taken at the node the steps before it
|
||||
// reached, a choice there resolved first; at indexes the tail where it is taken, and
|
||||
// only a table's step sits past the tail's end. A step holds no node, so a
|
||||
|
||||
@@ -310,6 +310,17 @@ func TestTableStepsUpToTheRowItLinksTo(t *testing.T) {
|
||||
if !afresh["L4"] || !afresh["L7"] || !afresh["record L4"] || !afresh["record L7"] {
|
||||
t.Fatalf("locality[L4]..municipality.locality.code drew only %v in 200 renders, want a locality of Lund drawn afresh", afresh)
|
||||
}
|
||||
var tagged struct {
|
||||
M string `fake:"locality[L4]..municipality.name"`
|
||||
}
|
||||
if err := f.FakeStruct(&tagged); err != nil || tagged.M != "Lund" {
|
||||
t.Errorf("a struct tag stepping up = %q, %v; want Lund", tagged.M, err)
|
||||
}
|
||||
if _, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, with(geo(), map[string]string{
|
||||
"x.json": `"{/locality..municipality.code}|{/locality[L1].name}"`,
|
||||
})))); err == nil || !strings.Contains(err.Error(), "{/locality[L1]..municipality.code}") {
|
||||
t.Errorf("New = %v, want the fence to name {/locality[L1]..municipality.code}", err)
|
||||
}
|
||||
for _, p := range f.List() {
|
||||
if strings.Contains(p, "..") {
|
||||
t.Fatalf("List() advertises %q; it lists direct descents only", p)
|
||||
@@ -323,6 +334,9 @@ func TestTableStepUpRefusals(t *testing.T) {
|
||||
"region[12]..municipality.name": "no parent",
|
||||
"locality[L4]..": "..municipality",
|
||||
"locality[L4]..municipality[0180]": "municipality[0180]",
|
||||
"locality[L4]..region": "..municipality..region",
|
||||
"locality[L4]...municipality": "empty segment",
|
||||
"locality[L4].municipality.name": "..municipality.name",
|
||||
"locality[L4].name..municipality": "column",
|
||||
"x..y": "not a table",
|
||||
} {
|
||||
@@ -338,6 +352,9 @@ func TestTableStepUpRefusals(t *testing.T) {
|
||||
t.Errorf("{/%s}: New = %v, want an error mentioning %s", path, err, want)
|
||||
}
|
||||
}
|
||||
if _, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, map[string]string{"s.json": `{"format":"{a..b}","a":{"format":"{b}","b":"x"}}`}))); err == nil || !strings.Contains(err.Error(), "not a table") {
|
||||
t.Errorf("{a..b} on a field: New = %v, want an error mentioning not a table", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLinkedTablesDrawConsistently(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user