diff --git a/README.md b/README.md index 6c5f681..01b21b5 100644 --- a/README.md +++ b/README.md @@ -137,8 +137,8 @@ it as nothing. A record is one render, so its columns read one pick of each [name](#names) its category binds: `"code": "{/currency as c}{c.code}"` beside `"symbol": "{c.symbol}"` is one currency, while `{/currency.code}` and `{/currency.symbol}` in two columns may name two. A -field a name binds draws afresh as a column of its own, so write the column through the name, -`"where": "{p}"` beside `{place as p}`, for it to agree with the columns reading `p`. +field the format binds whole, `{place as p}`, is a column holding that pick, so it agrees with +the columns reading `p`. A category never references itself: `{/users.first}` or a bare `{/users}` inside `users` describes a draw other than the fields beside it. Read a sibling as a field, and put a diff --git a/pick_test.go b/pick_test.go index b918105..096d60e 100644 --- a/pick_test.go +++ b/pick_test.go @@ -285,3 +285,25 @@ func TestASubFieldIsReachableByFake(t *testing.T) { } } } + +func TestAFieldBoundWholeIsItsPickAsAColumn(t *testing.T) { + f := newGenerator(t, writeData(t, map[string]string{ + "row": `{"format":"{place as p}{p.locality}","place":[{"format":"{locality}","locality":"Stockholm","zip":"1"},{"format":"{locality}","locality":"Tranås","zip":"5"}],"zip":"{p.zip}"}`, + }), WithSeed(1)) + zips := map[string]string{"Stockholm": "1", "Tranås": "5"} + seen := map[string]bool{} + for i := 0; i < 100; i++ { + r, err := f.FakeRecord("row") + if err != nil { + t.Fatal(err) + } + c := r.Columns() + if zips[c[0].Value] != c[1].Value { + t.Fatalf("%s: want the place column and the zip read through p one place", r.JSON()) + } + seen[c[0].Value] = true + } + if len(seen) != 2 { + t.Errorf("place drew only %v in 100 records, want both", seen) + } +} diff --git a/record.go b/record.go index f319b30..8d26e7a 100644 --- a/record.go +++ b/record.go @@ -266,8 +266,28 @@ func renderRecord(s *generatorState, t *template, columns []Column, sc renderSco } r := &Record{columns: append([]Column(nil), columns...)} for i := range r.columns { - column := renderLeaf(s, t.fields[r.columns[i].Name], sc) + field := t.fields[r.columns[i].Name] + var column readValue + if b := bindingOfWhole(t, field); b != nil { + column = readName(s, sc, arm{kind: namedRead, named: b, levels: []string{""}}) + } else { + column = renderLeaf(s, field, sc) + } r.columns[i].Value, r.columns[i].Null = column.text, column.null } return r } + +// bindingOfWhole is the name t binds to the whole of field, whose pick the field's column renders +// so the row agrees with the columns reading the name; nil where none does. +func bindingOfWhole(t *template, field node) *nameBinding { + if t.ownNameScope == nil { + return nil + } + for _, b := range t.ownNameScope.order { + if b.bindsField() && b.binder == t && len(b.tail) == 0 && b.head == field { + return b + } + } + return nil +}