Compare commits
2 Commits
047237f361
...
7abd3e2d95
| Author | SHA1 | Date | |
|---|---|---|---|
| 7abd3e2d95 | |||
| df52566a6a |
+5
-4
@@ -21,7 +21,8 @@ replacement, and each removed path, column or flag.
|
|||||||
- `New` refuses a root choice of templates sharing one format and one set of string
|
- `New` refuses a root choice of templates sharing one format and one set of string
|
||||||
fields, naming the rows TSV to write instead.
|
fields, naming the rows TSV to write instead.
|
||||||
- `misc.country`, `misc.currency`, `misc.language`, `misc.httpstatus` and
|
- `misc.country`, `misc.currency`, `misc.language`, `misc.httpstatus` and
|
||||||
`misc.mimetype` are tables. `misc.country` is the full ISO 3166 register with the
|
`misc.mimetype` are tables. `misc.country` is every ISO 3166 country that has a
|
||||||
columns `calling-code`, `capital`, `currency`, `flag`, `languages`, `numeric` and
|
capital, a currency and a TLD, with the columns `calling-code`, `capital`,
|
||||||
`tld` added; `misc.currency` the current ISO 4217 list with `decimals` and
|
`currency`, `flag`, `languages`, `numeric` and `tld` added; `misc.currency` the
|
||||||
`numeric` added, and its symbols from CLDR. `DATA-LICENSES.md` lists each source.
|
current ISO 4217 currencies with a minor unit, with `decimals` and `numeric`
|
||||||
|
added, and its symbols from CLDR. `DATA-LICENSES.md` lists each source.
|
||||||
|
|||||||
@@ -12,7 +12,3 @@ Every shipped dataset, its source, its licence and the attribution it asks for.
|
|||||||
| `misc/mimetype.tsv` | curated (IANA media types are facts) | — | — | — |
|
| `misc/mimetype.tsv` | curated (IANA media types are facts) | — | — | — |
|
||||||
|
|
||||||
Every other category is hand-written JSON under [`data/`](data), MIT like the code.
|
Every other category is hand-written JSON under [`data/`](data), MIT like the code.
|
||||||
|
|
||||||
```sh
|
|
||||||
docker compose run --rm --user "$(id -u):$(id -g)" data-import data-import/country.py
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ services:
|
|||||||
<<: *go
|
<<: *go
|
||||||
command: sh
|
command: sh
|
||||||
|
|
||||||
# Rebuilds a shipped TSV from its source: docker compose run --rm data-import data-import/country.py
|
|
||||||
data-import:
|
data-import:
|
||||||
image: python:3.14.7-slim
|
image: python:3.14.7-slim
|
||||||
working_dir: /app
|
working_dir: /app
|
||||||
|
|||||||
@@ -144,12 +144,34 @@ func (c *drawCheck) checkDraws(path string, n node) error {
|
|||||||
for _, e := range renderEdges(t) {
|
for _, e := range renderEdges(t) {
|
||||||
w.edge(t, e, drawAt{group: t.drawGroupKey, route: drawRoute{e.reached(), e.label}})
|
w.edge(t, e, drawAt{group: t.drawGroupKey, route: drawRoute{e.reached(), e.label}})
|
||||||
}
|
}
|
||||||
|
if err := w.checkOwnFamily(t); err != nil {
|
||||||
|
return fmt.Errorf("%s: %w", path, err)
|
||||||
|
}
|
||||||
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)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checkOwnFamily refuses a table's format or cell that reads, however many templates
|
||||||
|
// away, a table of its own family: a row rendered whole draws its row without
|
||||||
|
// pinning it, so the family would draw apart from the row being rendered.
|
||||||
|
func (w *drawWalk) checkOwnFamily(t *template) error {
|
||||||
|
own := t.table
|
||||||
|
if own == nil {
|
||||||
|
own = t.cellOf
|
||||||
|
}
|
||||||
|
if own == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, r := range w.reads {
|
||||||
|
if r.tr != nil && r.tr.head.family() == own.family() {
|
||||||
|
return fmt.Errorf("%s reads %s, a table of its own family, which a row of %s rendered whole would draw apart from; read the family from a template beside it, or add the value as a column", r.at.route.spelled(r.a.name), r.tr.head.category, own.category)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// checkRecordDraws fences the columns of a record — a template compiled at the top without a
|
// checkRecordDraws fences the columns of a record — a template compiled at the top without a
|
||||||
// repeat — so a load proves the record view of it as well as the string view.
|
// repeat — so a load proves the record view of it as well as the string view.
|
||||||
func (c *drawCheck) checkRecordDraws(path string, n node) error {
|
func (c *drawCheck) checkRecordDraws(path string, n node) error {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ type template struct {
|
|||||||
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
|
||||||
table *table // the table whose format this is, whose columns are the fields
|
table *table // the table whose format this is, whose columns are the fields
|
||||||
|
cellOf *table // the table whose cell this is
|
||||||
drawGroup string // the draw group it draws in, as written; "" keeps its caller's
|
drawGroup string // the draw group it draws in, as written; "" keeps its caller's
|
||||||
drawGroupKey string // its draw 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,8 +141,7 @@ type pathWalk struct {
|
|||||||
// walkPath descends tail from n and returns the node it ends at: a folder or
|
// walkPath descends tail from n and returns the node it ends at: a folder or
|
||||||
// template by its next segment, a choice by w.choice, which consumes no segment, a
|
// template by its next segment, a choice by w.choice, which consumes no segment, a
|
||||||
// table by walkTable. A missing segment is an error, so no walk reaches past what
|
// table by walkTable. A missing segment is an error, so no walk reaches past what
|
||||||
// the data holds. A table-shaped dispatch, one case per node kind, kept whole on
|
// the data holds.
|
||||||
// purpose.
|
|
||||||
func walkPath(n node, tail []string, w pathWalk) (node, error) {
|
func walkPath(n node, tail []string, w pathWalk) (node, error) {
|
||||||
if len(tail) == 0 {
|
if len(tail) == 0 {
|
||||||
return n, w.atLeaf(n)
|
return n, w.atLeaf(n)
|
||||||
@@ -216,33 +215,57 @@ func walkChoice(c *choice, tail []string, w pathWalk) (node, error) {
|
|||||||
// table was reached from another; a table reached whole, with no selector, is left
|
// table was reached from another; a table reached whole, with no selector, is left
|
||||||
// to a render's own draw.
|
// to a render's own draw.
|
||||||
func walkTable(t *table, tail []string, w pathWalk, descended bool) (node, error) {
|
func walkTable(t *table, tail []string, w pathWalk, descended bool) (node, error) {
|
||||||
sel := ""
|
sel, tail, err := t.selector(tail)
|
||||||
if len(tail) > 0 && isSelector(tail[0]) {
|
if err != nil {
|
||||||
sel, tail = selectorOf(tail[0]), tail[1:]
|
return nil, err
|
||||||
if len(tail) > 0 && isSelector(tail[0]) {
|
|
||||||
return nil, fmt.Errorf("%s[%s] is selected twice; one selector names its row", t.category, sel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// A selector further down pins this table by ancestry, so the walk draws only
|
column, child, err := t.step(tail)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Resolved before any draw, so a path that fails moves no seeded stream. A
|
||||||
|
// selector further down pins this table by ancestry, so the walk draws only
|
||||||
// where none follows; drawing first could pick a row the selector is not inside.
|
// where none follows; drawing first could pick a row the selector is not inside.
|
||||||
if w.pins != nil {
|
if w.pins != nil {
|
||||||
if err := readRow(w.pins, t, sel, (descended || len(tail) > 0) && !hasSelector(tail)); err != nil {
|
if err := readRow(w.pins, t, sel, (descended || len(tail) > 0) && !hasSelector(tail)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(tail) == 0 {
|
switch {
|
||||||
if sel == "" && !descended {
|
case len(tail) == 0 && sel == "" && !descended:
|
||||||
return walkPath(t, nil, w)
|
return walkPath(t, nil, w)
|
||||||
}
|
case len(tail) == 0:
|
||||||
return walkPath(t.whole, nil, w)
|
return walkPath(t.whole, nil, w)
|
||||||
}
|
case child != nil:
|
||||||
if i, ok := t.col[tail[0]]; ok {
|
|
||||||
return walkPath(t.fields[t.columns[i]], tail[1:], w)
|
|
||||||
}
|
|
||||||
if child := t.descendant(tail[0]); child != nil {
|
|
||||||
return walkTable(child, tail[1:], w, true)
|
return walkTable(child, tail[1:], w, true)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no column or linked table %q in %s", tail[0], t.category)
|
return walkPath(column, tail[1:], w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// selector splits the selector a tail starts with from the rest of it.
|
||||||
|
func (t *table) selector(tail []string) (sel string, rest []string, err error) {
|
||||||
|
if len(tail) == 0 || !isSelector(tail[0]) {
|
||||||
|
return "", tail, nil
|
||||||
|
}
|
||||||
|
sel, rest = selectorOf(tail[0]), tail[1:]
|
||||||
|
if len(rest) > 0 && isSelector(rest[0]) {
|
||||||
|
return "", nil, fmt.Errorf("%s[%s] is selected twice; one selector names its row", t.category, sel)
|
||||||
|
}
|
||||||
|
return sel, rest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// step is what a tail's first segment names in t: a column, or a table linked to it.
|
||||||
|
func (t *table) step(tail []string) (column node, child *table, err error) {
|
||||||
|
if len(tail) == 0 {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
if i, ok := t.col[tail[0]]; ok {
|
||||||
|
return t.fields[t.columns[i]], nil, nil
|
||||||
|
}
|
||||||
|
if child = t.descendant(tail[0]); child == nil {
|
||||||
|
return nil, nil, fmt.Errorf("no column or linked table %q in %s", tail[0], t.category)
|
||||||
|
}
|
||||||
|
return nil, child, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readRow pins the row a path reads of t: the one its selector names, or, where the
|
// readRow pins the row a path reads of t: the one its selector names, or, where the
|
||||||
|
|||||||
@@ -109,9 +109,6 @@ func linkTemplateRefs(folder []string, path, category string, t *template, root
|
|||||||
if category != "" && key == "/"+category {
|
if category != "" && key == "/"+category {
|
||||||
return fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", path, name)
|
return fmt.Errorf("%s: reference {%s}: names the category it sits in; read a sibling field as a path, or move the shared value into its own category and reference that", path, name)
|
||||||
}
|
}
|
||||||
if err := checkNotOwnFamily(root, category, target); err != nil {
|
|
||||||
return fmt.Errorf("%s: reference {%s}: %w", path, name, err)
|
|
||||||
}
|
|
||||||
if err := checkPath(target, tail, key); err != nil {
|
if err := checkPath(target, tail, key); err != nil {
|
||||||
return fmt.Errorf("%s: reference {%s}: %w", path, name, err)
|
return fmt.Errorf("%s: reference {%s}: %w", path, name, err)
|
||||||
}
|
}
|
||||||
@@ -125,22 +122,6 @@ func linkTemplateRefs(folder []string, path, category string, t *template, root
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNotOwnFamily refuses a reference from a table's format or cell into a table of
|
|
||||||
// its own family: a table rendered whole draws its row without pinning it, so the
|
|
||||||
// family would draw apart from the row being rendered.
|
|
||||||
func checkNotOwnFamily(root map[string]node, category string, target node) error {
|
|
||||||
if category == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, own, _, err := resolveCategory(root, strings.Split(category, "."))
|
|
||||||
from, isTable := own.(*table)
|
|
||||||
into, targetIsTable := target.(*table)
|
|
||||||
if err != nil || !isTable || !targetIsTable || from.family() != into.family() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("a cell or format of %s reads %s, a table of its own family, which a row of %s rendered whole would draw apart from; read the family from a template beside it, or add the value as a column", from.category, into.category, from.category)
|
|
||||||
}
|
|
||||||
|
|
||||||
// columnRead is a record's column read by a format of that one reference alone, which is the
|
// columnRead is a record's column read by a format of that one reference alone, which is the
|
||||||
// column: it takes the column's datatype and null.
|
// column: it takes the column's datatype and null.
|
||||||
type columnRead struct {
|
type columnRead struct {
|
||||||
|
|||||||
@@ -171,25 +171,40 @@ func (t *table) parseRows(data string) error {
|
|||||||
}
|
}
|
||||||
m := len(t.columns)
|
m := len(t.columns)
|
||||||
t.cells = make([]string, 0, m*(strings.Count(rest, "\n")+1))
|
t.cells = make([]string, 0, m*(strings.Count(rest, "\n")+1))
|
||||||
for line, n := 2, 0; rest != ""; line++ {
|
for line := 2; ; line++ {
|
||||||
var row string
|
row, more, found := strings.Cut(rest, "\n")
|
||||||
row, rest, _ = strings.Cut(rest, "\n")
|
|
||||||
row = strings.TrimSuffix(row, "\r")
|
row = strings.TrimSuffix(row, "\r")
|
||||||
for n = 0; n < m; n++ {
|
if err := t.appendRow(row, line); err != nil {
|
||||||
cell, more, tab := strings.Cut(row, "\t")
|
return err
|
||||||
if !tab && n < m-1 || tab && n == m-1 {
|
|
||||||
return fmt.Errorf("line %d has %s cells than the %d columns", line, map[bool]string{true: "more", false: "fewer"}[tab], m)
|
|
||||||
}
|
|
||||||
t.cells = append(t.cells, cell)
|
|
||||||
row = more
|
|
||||||
}
|
}
|
||||||
|
if !found {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rest = more
|
||||||
}
|
}
|
||||||
if t.rows() == 1 {
|
if t.rows() < 2 {
|
||||||
return fmt.Errorf("has one row, which is a template; write it as one")
|
return fmt.Errorf("has one row, which is a template; write it as one")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendRow splits one line into as many cells as the header has columns.
|
||||||
|
func (t *table) appendRow(row string, line int) error {
|
||||||
|
if row == "" {
|
||||||
|
return fmt.Errorf("line %d is empty; every line below the header is a row", line)
|
||||||
|
}
|
||||||
|
m := len(t.columns)
|
||||||
|
for n := 0; n < m; n++ {
|
||||||
|
cell, next, tab := strings.Cut(row, "\t")
|
||||||
|
if !tab && n < m-1 || tab && n == m-1 {
|
||||||
|
return fmt.Errorf("line %d has %s cells than the %d columns", line, map[bool]string{true: "more", false: "fewer"}[tab], m)
|
||||||
|
}
|
||||||
|
t.cells = append(t.cells, cell)
|
||||||
|
row = next
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// bindOptions resolves each option to its column and proves what it claims of the
|
// bindOptions resolves each option to its column and proves what it claims of the
|
||||||
// cells: a key is unique, a weight a positive number.
|
// cells: a key is unique, a weight a positive number.
|
||||||
func (t *table) bindOptions(o tableOptionValues) error {
|
func (t *table) bindOptions(o tableOptionValues) error {
|
||||||
@@ -280,6 +295,7 @@ func (t *table) checkCells() error {
|
|||||||
t.tokens = map[int]*template{}
|
t.tokens = map[int]*template{}
|
||||||
}
|
}
|
||||||
t.tokens[i] = n.(*template)
|
t.tokens[i] = n.(*template)
|
||||||
|
t.tokens[i].cellOf = t
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -395,7 +411,8 @@ func (t *table) indexed() *tableIndex {
|
|||||||
for k, rows := range t.index.children {
|
for k, rows := range t.index.children {
|
||||||
cum, total := make([]float64, len(rows)), 0.0
|
cum, total := make([]float64, len(rows)), 0.0
|
||||||
for i, r := range rows {
|
for i, r := range rows {
|
||||||
total += t.cum[r] - t.cumBefore(r)
|
w, _ := strconv.ParseFloat(t.cell(r, t.weight), 64) // sumWeights proved it
|
||||||
|
total += w
|
||||||
cum[i] = total
|
cum[i] = total
|
||||||
}
|
}
|
||||||
t.index.childCum[k] = cum
|
t.index.childCum[k] = cum
|
||||||
@@ -406,13 +423,6 @@ func (t *table) indexed() *tableIndex {
|
|||||||
return &t.index
|
return &t.index
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *table) cumBefore(r int) float64 {
|
|
||||||
if r == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return t.cum[r-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw picks a row over the whole table. The session is concrete rather than the rng
|
// draw picks a row over the whole table. The session is concrete rather than the rng
|
||||||
// interface so that a walk holding the draws allocates nothing.
|
// interface so that a walk holding the draws allocates nothing.
|
||||||
func (t *table) draw(s *session) int {
|
func (t *table) draw(s *session) int {
|
||||||
@@ -424,7 +434,7 @@ func (t *table) draw(s *session) int {
|
|||||||
|
|
||||||
func pickCum(s *session, cum []float64) int {
|
func pickCum(s *session, cum []float64) int {
|
||||||
x := s.Float64() * cum[len(cum)-1]
|
x := s.Float64() * cum[len(cum)-1]
|
||||||
return sort.Search(len(cum), func(i int) bool { return cum[i] > x })
|
return min(sort.Search(len(cum), func(i int) bool { return cum[i] > x }), len(cum)-1) // x can round up to the total
|
||||||
}
|
}
|
||||||
|
|
||||||
// drawUnder picks a row among those linked to parent row pr.
|
// drawUnder picks a row among those linked to parent row pr.
|
||||||
|
|||||||
+60
-46
@@ -377,52 +377,55 @@ func TestTableFences(t *testing.T) {
|
|||||||
files map[string]string
|
files map[string]string
|
||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
"rows names a missing file": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`}, "t.tsv"},
|
"rows names a missing file": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`}, "t.tsv"},
|
||||||
"a TSV nothing names": {with(base, map[string]string{"stray.tsv": "a\nx\n"}), "stray.tsv"},
|
"a TSV nothing names": {with(base, map[string]string{"stray.tsv": "a\nx\n"}), "stray.tsv"},
|
||||||
"rows outside its folder": {with(base, map[string]string{"t.json": `{"format":"{code}","rows":"../region.tsv"}`}), "beside"},
|
"rows outside its folder": {with(base, map[string]string{"t.json": `{"format":"{code}","rows":"../region.tsv"}`}), "beside"},
|
||||||
"rows not a tsv": {with(base, map[string]string{"t.json": `{"format":"{code}","rows":"region.txt"}`, "region.txt": "code\n1\n"}), ".tsv"},
|
"rows not a tsv": {with(base, map[string]string{"t.json": `{"format":"{code}","rows":"region.txt"}`, "region.txt": "code\n1\n"}), ".tsv"},
|
||||||
"key names no column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"b"}`, "t.tsv": "a\nx\ny\n"}, `"b"`},
|
"key names no column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"b"}`, "t.tsv": "a\nx\ny\n"}, `"b"`},
|
||||||
"name names no column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","name":"b"}`, "t.tsv": "a\nx\ny\n"}, `"b"`},
|
"name names no column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","name":"b"}`, "t.tsv": "a\nx\ny\n"}, `"b"`},
|
||||||
"weight names no column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","weight":"b"}`, "t.tsv": "a\nx\ny\n"}, `"b"`},
|
"weight names no column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","weight":"b"}`, "t.tsv": "a\nx\ny\n"}, `"b"`},
|
||||||
"parent names no column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","parent":"b"}`, "t.tsv": "a\nx\ny\n"}, `"b"`},
|
"parent names no column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","parent":"b"}`, "t.tsv": "a\nx\ny\n"}, `"b"`},
|
||||||
"key equals name": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a","name":"a"}`, "t.tsv": "a\nx\ny\n"}, "drop"},
|
"key equals name": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a","name":"a"}`, "t.tsv": "a\nx\ny\n"}, "drop"},
|
||||||
"duplicate key": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a"}`, "t.tsv": "a\nx\nx\n"}, `"x"`},
|
"duplicate key": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a"}`, "t.tsv": "a\nx\nx\n"}, `"x"`},
|
||||||
"empty key": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a"}`, "t.tsv": "a\tb\n\ty\nx\tz\n"}, "empty"},
|
"empty key": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a"}`, "t.tsv": "a\tb\n\ty\nx\tz\n"}, "empty"},
|
||||||
"a bracket in a key": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a"}`, "t.tsv": "a\nx[1]\ny\n"}, `"["`},
|
"a bracket in a key": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a"}`, "t.tsv": "a\nx[1]\ny\n"}, `"["`},
|
||||||
"a brace in a name": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a","name":"n"}`, "t.tsv": "a\tn\nx\tx{1}\ny\ty\n"}, `"{"`},
|
"a brace in a name": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a","name":"n"}`, "t.tsv": "a\tn\nx\tx{1}\ny\ty\n"}, `"{"`},
|
||||||
"name without a key": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","name":"a"}`, "t.tsv": "a\nx\nx\n"}, "key"},
|
"name without a key": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","name":"a"}`, "t.tsv": "a\nx\nx\n"}, "key"},
|
||||||
"a name that is another row's key": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a","name":"n"}`, "t.tsv": "a\tn\nx\ty\ny\tz\n"}, `"y"`},
|
"a name that is another row's key": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a","name":"n"}`, "t.tsv": "a\tn\nx\ty\ny\tz\n"}, `"y"`},
|
||||||
"a cell reading its family": {with(geo(), map[string]string{"locality.tsv": "code\tname\tmunicipality\tnote\nL1\tStockholm\t0180\t{/municipality.code}\nL2\tSolna\t0184\t-\nL3\tMalmö\t1280\t-\nL4\tLund\t1281\t-\nL5\tGöteborg\t1480\t-\n"}), "family"},
|
"a cell reading its family": {with(geo(), map[string]string{"locality.tsv": "code\tname\tmunicipality\tnote\nL1\tStockholm\t0180\t{/municipality.code}\nL2\tSolna\t0184\t-\nL3\tMalmö\t1280\t-\nL4\tLund\t1281\t-\nL5\tGöteborg\t1480\t-\n"}), "family"},
|
||||||
"a format reading its family": {with(geo(), map[string]string{"locality.json": `{"format":"{name} {/region.name}","rows":"locality.tsv","key":"code","name":"name","parent":"municipality"}`}), "family"},
|
"a format reading its family": {with(geo(), map[string]string{"locality.json": `{"format":"{name} {/region.name}","rows":"locality.tsv","key":"code","name":"name","parent":"municipality"}`}), "family"},
|
||||||
"a descendant named like an ancestor's column": {with(geo(), map[string]string{"region.tsv": "code\tname\tpopulation\tlocality\n01\tStockholms län\t2400000\tx\n12\tSkåne län\t1400000\ty\n14\tVästra Götalands län\t1750000\tz\n"}), `"locality"`},
|
"a descendant named like an ancestor's column": {with(geo(), map[string]string{"region.tsv": "code\tname\tpopulation\tlocality\n01\tStockholms län\t2400000\tx\n12\tSkåne län\t1400000\ty\n14\tVästra Götalands län\t1750000\tz\n"}), `"locality"`},
|
||||||
"weight not a number": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","weight":"w"}`, "t.tsv": "a\tw\nx\tmany\ny\t2\n"}, `"many"`},
|
"weight not a number": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","weight":"w"}`, "t.tsv": "a\tw\nx\tmany\ny\t2\n"}, `"many"`},
|
||||||
"weight zero": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","weight":"w"}`, "t.tsv": "a\tw\nx\t0\ny\t2\n"}, "0"},
|
"weight zero": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","weight":"w"}`, "t.tsv": "a\tw\nx\t0\ny\t2\n"}, "0"},
|
||||||
"weight negative": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","weight":"w"}`, "t.tsv": "a\tw\nx\t-1\ny\t2\n"}, "-1"},
|
"weight negative": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","weight":"w"}`, "t.tsv": "a\tw\nx\t-1\ny\t2\n"}, "-1"},
|
||||||
"reserved column name": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\tb.c\nx\ty\n"}, `"b.c"`},
|
"reserved column name": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\tb.c\nx\ty\n"}, `"b.c"`},
|
||||||
"duplicate column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\ta\nx\ty\n"}, `"a"`},
|
"duplicate column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\ta\nx\ty\n"}, `"a"`},
|
||||||
"empty column name": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\t\nx\ty\n"}, "empty"},
|
"empty column name": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\t\nx\ty\n"}, "empty"},
|
||||||
"short row": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\tb\nx\ty\nz\n"}, "line 3"},
|
"short row": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\tb\nx\ty\nz\n"}, "line 3"},
|
||||||
"no rows": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\n"}, "no rows"},
|
"no rows": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\n"}, "no rows"},
|
||||||
"one row": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\nx\n"}, "one row"},
|
"a blank line after the rows": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\tb\nx\t1\ny\t2\n\n"}, "line 4"},
|
||||||
"empty file": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": ""}, "header"},
|
"a blank line in one column": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\nx\n\ny\n"}, "line 3"},
|
||||||
"parent is not a table": {map[string]string{"p.json": `"x"`, "t.json": `{"format":"{a}","rows":"t.tsv","parent":"p"}`, "t.tsv": "a\tp\nx\tx\ny\tx\n"}, "not a table"},
|
"a table reaching its family through a template": {with(geo(), map[string]string{"addr.json": `"{/locality.name}"`, "region.tsv": "code\tname\tpopulation\ttimezone\tnote\n01\tStockholms län\t2400000\tEurope/Stockholm\t{/addr}\n12\tSkåne län\t1400000\tEurope/Stockholm\t-\n14\tVästra Götalands län\t1750000\tEurope/Stockholm\t-\n"}), "family"},
|
||||||
"parent does not exist": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","parent":"p"}`, "t.tsv": "a\tp\nx\tx\ny\tx\n"}, `"p"`},
|
"one row": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\nx\n"}, "one row"},
|
||||||
"parent in another folder": {map[string]string{"g/p.json": `{"format":"{k}","rows":"p.tsv","key":"k"}`, "g/p.tsv": "k\nx\ny\n", "t.json": `{"format":"{a}","rows":"t.tsv","parent":"p"}`, "t.tsv": "a\tp\nx\tx\ny\ty\n"}, `"p"`},
|
"empty file": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": ""}, "header"},
|
||||||
"parent has no key": {map[string]string{"p.json": `{"format":"{k}","rows":"p.tsv"}`, "p.tsv": "k\nx\ny\n", "t.json": `{"format":"{a}","rows":"t.tsv","parent":"p"}`, "t.tsv": "a\tp\nx\tx\ny\ty\n"}, "key"},
|
"parent is not a table": {map[string]string{"p.json": `"x"`, "t.json": `{"format":"{a}","rows":"t.tsv","parent":"p"}`, "t.tsv": "a\tp\nx\tx\ny\tx\n"}, "not a table"},
|
||||||
"dangling link": {with(base, map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","parent":"region"}`, "t.tsv": "a\tregion\nx\t01\ny\t99\n"}), `"99"`},
|
"parent does not exist": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","parent":"p"}`, "t.tsv": "a\tp\nx\tx\ny\tx\n"}, `"p"`},
|
||||||
"childless parent row": {with(base, map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","parent":"region"}`, "t.tsv": "a\tregion\nx\t01\ny\t01\n"}), `"12"`},
|
"parent in another folder": {map[string]string{"g/p.json": `{"format":"{k}","rows":"p.tsv","key":"k"}`, "g/p.tsv": "k\nx\ny\n", "t.json": `{"format":"{a}","rows":"t.tsv","parent":"p"}`, "t.tsv": "a\tp\nx\tx\ny\ty\n"}, `"p"`},
|
||||||
"parent cycle": {map[string]string{"a.json": `{"format":"{k}","rows":"a.tsv","key":"k","parent":"b"}`, "a.tsv": "k\tb\nx\tx\ny\ty\n", "b.json": `{"format":"{k}","rows":"b.tsv","key":"k","parent":"a"}`, "b.tsv": "k\ta\nx\tx\ny\ty\n"}, "cycle"},
|
"parent has no key": {map[string]string{"p.json": `{"format":"{k}","rows":"p.tsv"}`, "p.tsv": "k\nx\ny\n", "t.json": `{"format":"{a}","rows":"t.tsv","parent":"p"}`, "t.tsv": "a\tp\nx\tx\ny\ty\n"}, "key"},
|
||||||
"child named like a column": {with(base, map[string]string{"name.json": `{"format":"{a}","rows":"name.tsv","parent":"region"}`, "name.tsv": "a\tregion\nx\t01\ny\t12\n"}), `"name"`},
|
"dangling link": {with(base, map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","parent":"region"}`, "t.tsv": "a\tregion\nx\t01\ny\t99\n"}), `"99"`},
|
||||||
"rows nested in a field": {map[string]string{"t.json": `{"format":"{x}","x":{"format":"{a}","rows":"x.tsv"}}`, "x.tsv": "a\nx\ny\n"}, "category"},
|
"childless parent row": {with(base, map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","parent":"region"}`, "t.tsv": "a\tregion\nx\t01\ny\t01\n"}), `"12"`},
|
||||||
"rows in a choice item": {map[string]string{"t.json": `[{"format":"{a}","rows":"t.tsv"},"y"]`, "t.tsv": "a\nx\ny\n"}, "category"},
|
"parent cycle": {map[string]string{"a.json": `{"format":"{k}","rows":"a.tsv","key":"k","parent":"b"}`, "a.tsv": "k\tb\nx\tx\ny\ty\n", "b.json": `{"format":"{k}","rows":"b.tsv","key":"k","parent":"a"}`, "b.tsv": "k\ta\nx\tx\ny\ty\n"}, "cycle"},
|
||||||
"unknown table option": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","fields":"a"}`, "t.tsv": "a\nx\ny\n"}, "a table takes"},
|
"child named like a column": {with(base, map[string]string{"name.json": `{"format":"{a}","rows":"name.tsv","parent":"region"}`, "name.tsv": "a\tregion\nx\t01\ny\t12\n"}), `"name"`},
|
||||||
"repeat on a table": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","repeat":2}`, "t.tsv": "a\nx\ny\n"}, "a table takes"},
|
"rows nested in a field": {map[string]string{"t.json": `{"format":"{x}","x":{"format":"{a}","rows":"x.tsv"}}`, "x.tsv": "a\nx\ny\n"}, "category"},
|
||||||
"drawGroup on a table": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","drawGroup":"g"}`, "t.tsv": "a\nx\ny\n"}, "a table takes"},
|
"rows in a choice item": {map[string]string{"t.json": `[{"format":"{a}","rows":"t.tsv"},"y"]`, "t.tsv": "a\nx\ny\n"}, "category"},
|
||||||
"option not a string": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":1}`, "t.tsv": "a\nx\ny\n"}, "string"},
|
"unknown table option": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","fields":"a"}`, "t.tsv": "a\nx\ny\n"}, "a table takes"},
|
||||||
"format names no column": {map[string]string{"t.json": `{"format":"{b}","rows":"t.tsv"}`, "t.tsv": "a\nx\ny\n"}, `no column "b"`},
|
"repeat on a table": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","repeat":2}`, "t.tsv": "a\nx\ny\n"}, "a table takes"},
|
||||||
"format reads into a column": {map[string]string{"t.json": `{"format":"{a.x}","rows":"t.tsv"}`, "t.tsv": "a\nx\ny\n"}, `"a"`},
|
"drawGroup on a table": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","drawGroup":"g"}`, "t.tsv": "a\nx\ny\n"}, "a table takes"},
|
||||||
"a reference into a column": {with(base, map[string]string{"t.json": `"{/region.name.x}"`}), "column"},
|
"option not a string": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":1}`, "t.tsv": "a\nx\ny\n"}, "string"},
|
||||||
"a category referencing itself": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\n{/t.a}\ny\n"}, "names the category it sits in"},
|
"format names no column": {map[string]string{"t.json": `{"format":"{b}","rows":"t.tsv"}`, "t.tsv": "a\nx\ny\n"}, `no column "b"`},
|
||||||
|
"format reads into a column": {map[string]string{"t.json": `{"format":"{a.x}","rows":"t.tsv"}`, "t.tsv": "a\nx\ny\n"}, `"a"`},
|
||||||
|
"a reference into a column": {with(base, map[string]string{"t.json": `"{/region.name.x}"`}), "column"},
|
||||||
|
"a category referencing itself": {map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv"}`, "t.tsv": "a\n{/t.a}\ny\n"}, "names the category it sits in"},
|
||||||
}
|
}
|
||||||
for name, c := range rejected {
|
for name, c := range rejected {
|
||||||
_, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, c.files)))
|
_, err := New(WithoutShippedData(), WithDataPath(writeFiles(t, c.files)))
|
||||||
@@ -445,6 +448,17 @@ func TestTableFences(t *testing.T) {
|
|||||||
if v := fake(t, bom, "t[x].b"); v != "1" {
|
if v := fake(t, bom, "t[x].b"); v != "1" {
|
||||||
t.Fatalf("t[x].b under a BOM header = %q, want the mark stripped", v)
|
t.Fatalf("t[x].b under a BOM header = %q, want the mark stripped", v)
|
||||||
}
|
}
|
||||||
|
crlf := newGenerator(t, writeFiles(t, map[string]string{"t.json": `{"format":"{a}","rows":"t.tsv","key":"a"}`, "t.tsv": "a\tb\r\nx\t1\r\ny\t2\r\n"}), WithSeed(1))
|
||||||
|
if v := fake(t, crlf, "t[y].b"); v != "2" {
|
||||||
|
t.Fatalf("t[y].b in a CRLF file = %q, want the carriage return stripped", v)
|
||||||
|
}
|
||||||
|
a, b := newGenerator(t, writeFiles(t, geo()), WithSeed(1)), newGenerator(t, writeFiles(t, geo()), WithSeed(1))
|
||||||
|
if _, err := a.Fake("region.nope"); err == nil {
|
||||||
|
t.Fatal("Fake(region.nope) = nil error")
|
||||||
|
}
|
||||||
|
if x, y := fake(t, a, "region.name"), fake(t, b, "region.name"); x != y {
|
||||||
|
t.Fatalf("a failed Fake shifted the seeded stream: %q != %q", x, y)
|
||||||
|
}
|
||||||
empty := newGenerator(t, writeFiles(t, map[string]string{"t.json": `{"format":"","rows":"t.tsv","key":"a"}`, "t.tsv": "a\tb\nx\t1\ny\t2\n"}), WithSeed(1))
|
empty := newGenerator(t, writeFiles(t, map[string]string{"t.json": `{"format":"","rows":"t.tsv","key":"a"}`, "t.tsv": "a\tb\nx\t1\ny\t2\n"}), WithSeed(1))
|
||||||
if v := fake(t, empty, "t"); v != "" {
|
if v := fake(t, empty, "t"); v != "" {
|
||||||
t.Fatalf("a record-only table renders %q, want \"\"", v)
|
t.Fatalf("a record-only table renders %q, want \"\"", v)
|
||||||
|
|||||||
Reference in New Issue
Block a user