Weighted person names, valid personal ids and date() in both locales #21
+24
-13
@@ -472,8 +472,9 @@ func iban(r rng, cc string) string {
|
|||||||
|
|
||||||
const dayLayout = "2006-01-02"
|
const dayLayout = "2006-01-02"
|
||||||
|
|
||||||
// Alike in no field; layoutDay differs from layoutProbe in its date fields only,
|
// The instants a layout is proved against: layoutProbe2 is alike in no field, while
|
||||||
// layoutClock in its clock fields only.
|
// layoutDay differs from layoutProbe in its date fields alone and layoutClock in its
|
||||||
|
// clock fields alone, so formatting two of them tells which kind a layout names.
|
||||||
var (
|
var (
|
||||||
layoutProbe = time.Date(2001, 2, 3, 4, 5, 6, 0, time.UTC)
|
layoutProbe = time.Date(2001, 2, 3, 4, 5, 6, 0, time.UTC)
|
||||||
layoutProbe2 = time.Date(2010, 11, 12, 13, 14, 15, 0, time.UTC)
|
layoutProbe2 = time.Date(2010, 11, 12, 13, 14, 15, 0, time.UTC)
|
||||||
@@ -481,6 +482,18 @@ var (
|
|||||||
layoutClock = time.Date(2001, 2, 3, 13, 14, 15, 0, time.UTC)
|
layoutClock = time.Date(2001, 2, 3, 13, 14, 15, 0, time.UTC)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func namesAField(layout string) bool {
|
||||||
|
return layoutProbe.Format(layout) != layoutProbe2.Format(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func namesADateField(layout string) bool {
|
||||||
|
return layoutProbe.Format(layout) != layoutDay.Format(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func namesAClockField(layout string) bool {
|
||||||
|
return layoutProbe.Format(layout) != layoutClock.Format(layout)
|
||||||
|
}
|
||||||
|
|
||||||
// quotedLayout reports whether an arg carries the single quotes a layout is written in.
|
// quotedLayout reports whether an arg carries the single quotes a layout is written in.
|
||||||
func quotedLayout(a string) bool {
|
func quotedLayout(a string) bool {
|
||||||
return len(a) >= 2 && a[0] == '\'' && a[len(a)-1] == '\''
|
return len(a) >= 2 && a[0] == '\'' && a[len(a)-1] == '\''
|
||||||
@@ -493,10 +506,10 @@ func layoutArg(a string) (string, error) {
|
|||||||
if strings.HasPrefix(a, `"`) || strings.HasSuffix(a, `"`) {
|
if strings.HasPrefix(a, `"`) || strings.HasSuffix(a, `"`) {
|
||||||
return "", fmt.Errorf("layout %s is double-quoted; write '%s'", a, bare)
|
return "", fmt.Errorf("layout %s is double-quoted; write '%s'", a, bare)
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("layout %s is not quoted; write '%s', which a shell keeps only inside a double-quoted argument", a, bare)
|
return "", fmt.Errorf("layout %s is not quoted; write '%s'", a, bare)
|
||||||
}
|
}
|
||||||
layout := a[1 : len(a)-1]
|
layout := a[1 : len(a)-1]
|
||||||
if layoutProbe.Format(layout) == layoutProbe2.Format(layout) {
|
if !namesAField(layout) {
|
||||||
return "", fmt.Errorf("layout '%s' names no field, so it is the constant %q; write it as text", layout, layout)
|
return "", fmt.Errorf("layout '%s' names no field, so it is the constant %q; write it as text", layout, layout)
|
||||||
}
|
}
|
||||||
return layout, nil
|
return layout, nil
|
||||||
@@ -518,15 +531,15 @@ func layoutArity(name string, n int, a []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
hint := ""
|
hint := ""
|
||||||
if len(a) > n && !split(a[n-1:]) {
|
if len(a) > n && !holdsQuotedLayout(a[n-1:]) {
|
||||||
hint = fmt.Sprintf("; a layout holding a comma is quoted: '%s'", strings.Join(a[n-1:], ", "))
|
hint = fmt.Sprintf("; a layout holding a comma is quoted: '%s'", strings.Join(a[n-1:], ", "))
|
||||||
}
|
}
|
||||||
return fmt.Errorf("%s takes %d argument%s, got %d%s", name, n, plural(n), len(a), hint)
|
return fmt.Errorf("%s takes %d argument%s, got %d%s", name, n, plural(n), len(a), hint)
|
||||||
}
|
}
|
||||||
|
|
||||||
// split reports whether the surplus args already hold a quoted layout, which no
|
// holdsQuotedLayout reports whether the surplus args already carry a quoted layout,
|
||||||
// comma split apart.
|
// which no comma split apart.
|
||||||
func split(a []string) bool {
|
func holdsQuotedLayout(a []string) bool {
|
||||||
for _, arg := range a {
|
for _, arg := range a {
|
||||||
if quotedLayout(arg) {
|
if quotedLayout(arg) {
|
||||||
return true
|
return true
|
||||||
@@ -535,8 +548,6 @@ func split(a []string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func plural(n int) string { return map[bool]string{true: "s"}[n != 1] }
|
|
||||||
|
|
||||||
func dateArgs(_ map[string]node, a []string) error {
|
func dateArgs(_ map[string]node, a []string) error {
|
||||||
if err := layoutArity("date", 3, a); err != nil {
|
if err := layoutArity("date", 3, a); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -556,10 +567,10 @@ func dateArgs(_ map[string]node, a []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if layoutProbe.Format(layout) == layoutDay.Format(layout) {
|
if !namesADateField(layout) {
|
||||||
return fmt.Errorf("date(from,to,layout): '%s' names no date field; write time('%s')", layout, layout)
|
return fmt.Errorf("date(from,to,layout): '%s' names no date field; write time('%s')", layout, layout)
|
||||||
}
|
}
|
||||||
if from.Equal(to) && layoutProbe.Format(layout) == layoutClock.Format(layout) {
|
if from.Equal(to) && !namesAClockField(layout) {
|
||||||
return fmt.Errorf("date(%s,%s,'%s') is the constant %q; write it as text", a[0], a[1], layout, from.Format(layout))
|
return fmt.Errorf("date(%s,%s,'%s') is the constant %q; write it as text", a[0], a[1], layout, from.Format(layout))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -573,7 +584,7 @@ func timeArg(_ map[string]node, a []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if layoutProbe.Format(layout) != layoutDay.Format(layout) {
|
if namesADateField(layout) {
|
||||||
return fmt.Errorf("time(layout): '%s' names a date field; write date(from,to,layout)", layout)
|
return fmt.Errorf("time(layout): '%s' names a date field; write date(from,to,layout)", layout)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ const usage = `Usage: fejkdata [flags] <path|template>
|
|||||||
<template> a format string or JSON value to render inline, e.g.
|
<template> a format string or JSON value to render inline, e.g.
|
||||||
'name: {/sv_SE.person.last}' or '{"format":"{x}","x":["bosse","lina"]}'
|
'name: {/sv_SE.person.last}' or '{"format":"{x}","x":["bosse","lina"]}'
|
||||||
|
|
||||||
|
A layout inside a template is single-quoted, so quote the whole argument with " to
|
||||||
|
keep it: "{date(1990-01-01,2010-12-31,'2006-01-02')}".
|
||||||
|
|
||||||
An argument containing a { token, or a JSON object, array or string, is a
|
An argument containing a { token, or a JSON object, array or string, is a
|
||||||
template; any other argument is a path (a path never contains a brace or a quote,
|
template; any other argument is a path (a path never contains a brace or a quote,
|
||||||
and a bracket only as a [selector] after a table's name). Templates reach the
|
and a bracket only as a [selector] after a table's name). Templates reach the
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ func loadData(sources []dataSource) (map[string]node, error) {
|
|||||||
if len(root) == 0 {
|
if len(root) == 0 {
|
||||||
return nil, fmt.Errorf("no .json data found")
|
return nil, fmt.Errorf("no .json data found")
|
||||||
}
|
}
|
||||||
|
setTablePaths(root)
|
||||||
if err := linkTables(root); err != nil {
|
if err := linkTables(root); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ func (d *draws) rowOf(t *table) int {
|
|||||||
// pinRow pins row r of t where it agrees with the rows pinned before it.
|
// pinRow pins row r of t where it agrees with the rows pinned before it.
|
||||||
func (d *draws) pinRow(t *table, r int) error {
|
func (d *draws) pinRow(t *table, r int) error {
|
||||||
if pr, ok := d.pinned(t); ok && pr != r {
|
if pr, ok := d.pinned(t); ok && pr != r {
|
||||||
return fmt.Errorf("%s and %s are two rows of %s", t.selectorSpelling(pr), t.selectorSpelling(r), t.category)
|
return fmt.Errorf("%s and %s are two rows of %s", t.selectorSpelling(pr), t.selectorSpelling(r), t.path)
|
||||||
}
|
}
|
||||||
for a := t.parentT; a != nil; a = a.parentT {
|
for a := t.parentT; a != nil; a = a.parentT {
|
||||||
if pa, ok := d.pinned(a); ok && !t.under(r, a, pa) {
|
if pa, ok := d.pinned(a); ok && !t.under(r, a, pa) {
|
||||||
|
|||||||
@@ -141,8 +141,8 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
|
|||||||
}
|
}
|
||||||
shape := f.recordShapeOf(n)
|
shape := f.recordShapeOf(n)
|
||||||
if errors.Is(shape.err, errNoColumns) {
|
if errors.Is(shape.err, errNoColumns) {
|
||||||
column := path[strings.LastIndex(path, ".")+1:]
|
ns := names(segments)
|
||||||
return nil, fmt.Errorf("fejkdata: %s %w; render it as a column of one: '{\"format\":\"\",\"%s\":\"{/%s}\"}'", path, shape.err, column, path)
|
return nil, fmt.Errorf(`fejkdata: %s %w; render it as a column of one: {"format":"","%s":"{/%s}"}`, path, shape.err, ns[len(ns)-1], path)
|
||||||
}
|
}
|
||||||
if shape.err != nil {
|
if shape.err != nil {
|
||||||
return nil, fmt.Errorf("fejkdata: %s %w", path, shape.err)
|
return nil, fmt.Errorf("fejkdata: %s %w", path, shape.err)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
// cell of the row a render pinned; a cell carrying tokens compiles to a string node.
|
// cell of the row a render pinned; a cell carrying tokens compiles to a string node.
|
||||||
type table struct {
|
type table struct {
|
||||||
category string
|
category string
|
||||||
path string // the category's path from the data root, set when the tables are linked
|
path string // the category's path from the data root, which a selector is written at
|
||||||
file string
|
file string
|
||||||
format *template // fields are the column nodes
|
format *template // fields are the column nodes
|
||||||
columns []string
|
columns []string
|
||||||
@@ -340,6 +340,23 @@ func (t *table) compileFormat(format string) error {
|
|||||||
// linkTables binds every table's parent to the table beside it, and proves the
|
// linkTables binds every table's parent to the table beside it, and proves the
|
||||||
// links: a parent has a key, every link cell is one, every parent row is linked
|
// links: a parent has a key, every link cell is one, every parent row is linked
|
||||||
// to, no chain of parents closes, and no child is named like a parent's column.
|
// to, no chain of parents closes, and no child is named like a parent's column.
|
||||||
|
// setTablePaths gives every table the path a selector on it is written at, before a
|
||||||
|
// link or a draw can name one.
|
||||||
|
func setTablePaths(root map[string]node) {
|
||||||
|
var walk func(dir string, children map[string]node)
|
||||||
|
walk = func(dir string, children map[string]node) {
|
||||||
|
for name, child := range children {
|
||||||
|
switch n := child.(type) {
|
||||||
|
case *folder:
|
||||||
|
walk(join(dir, name), n.children)
|
||||||
|
case *table:
|
||||||
|
n.path = join(dir, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
walk("", root)
|
||||||
|
}
|
||||||
|
|
||||||
func linkTables(root map[string]node) error {
|
func linkTables(root map[string]node) error {
|
||||||
var walk func(dir string, children map[string]node) error
|
var walk func(dir string, children map[string]node) error
|
||||||
walk = func(dir string, children map[string]node) error {
|
walk = func(dir string, children map[string]node) error {
|
||||||
@@ -351,7 +368,6 @@ func linkTables(root map[string]node) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case *table:
|
case *table:
|
||||||
n.path = path
|
|
||||||
if n.parent < 0 {
|
if n.parent < 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -510,7 +526,7 @@ func (t *table) under(r int, a *table, pr int) bool {
|
|||||||
// naming several rows resolves inside the ancestors pinned in d.
|
// naming several rows resolves inside the ancestors pinned in d.
|
||||||
func (t *table) find(sel string, d *draws) (int, error) {
|
func (t *table) find(sel string, d *draws) (int, error) {
|
||||||
if t.key < 0 && t.name < 0 {
|
if t.key < 0 && t.name < 0 {
|
||||||
return 0, fmt.Errorf("%s has no key or name column to select a row by", t.spelledPath())
|
return 0, fmt.Errorf("%s has no key or name column to select a row by", t.path)
|
||||||
}
|
}
|
||||||
if r, ok := t.byKey[sel]; ok {
|
if r, ok := t.byKey[sel]; ok {
|
||||||
return r, nil
|
return r, nil
|
||||||
@@ -523,34 +539,31 @@ func (t *table) find(sel string, d *draws) (int, error) {
|
|||||||
case 1:
|
case 1:
|
||||||
return rows[0], nil
|
return rows[0], nil
|
||||||
case 0:
|
case 0:
|
||||||
return 0, fmt.Errorf("no row of %s has key or name %q", t.spelledPath(), sel)
|
return 0, fmt.Errorf("no row of %s has key or name %q", t.path, sel)
|
||||||
}
|
}
|
||||||
keys := make([]string, len(rows))
|
return 0, t.ambiguous(sel, rows)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ambiguous names each row a selector could have meant: a keyed table by its keys,
|
||||||
|
// and one told apart only by its parent by the path that selects it.
|
||||||
|
func (t *table) ambiguous(sel string, rows []int) error {
|
||||||
|
spellings := make([]string, len(rows))
|
||||||
for i, r := range rows {
|
for i, r := range rows {
|
||||||
if t.key < 0 {
|
if t.key < 0 {
|
||||||
keys[i] = t.selectorSpelling(r)
|
spellings[i] = t.selectorSpelling(r)
|
||||||
} else {
|
} else {
|
||||||
keys[i] = t.cell(r, t.key)
|
spellings[i] = t.cell(r, t.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
listed := strings.Join(keys, ", ")
|
listed := strings.Join(spellings, ", ")
|
||||||
if t.key < 0 {
|
if t.key < 0 {
|
||||||
return 0, fmt.Errorf("%q names %d rows of %s; select it inside its %s, one of %s", sel, len(rows), t.spelledPath(), t.parentT.spelledPath(), listed)
|
return fmt.Errorf("%q names %d rows of %s; select it inside its %s, one of %s", sel, len(rows), t.path, t.parentT.path, listed)
|
||||||
}
|
}
|
||||||
inside := ""
|
inside := ""
|
||||||
if t.parentT != nil {
|
if t.parentT != nil {
|
||||||
inside = fmt.Sprintf(", or select it inside its %s", t.parentT.category)
|
inside = fmt.Sprintf(", or select it inside its %s", t.parentT.path)
|
||||||
}
|
}
|
||||||
return 0, fmt.Errorf("%q names %d rows of %s; select one by key, one of %s%s", sel, len(rows), t.spelledPath(), listed, inside)
|
return fmt.Errorf("%q names %d rows of %s; select one by key, one of %s%s", sel, len(rows), t.path, listed, inside)
|
||||||
}
|
|
||||||
|
|
||||||
// spelledPath is the path a selector on t is written at, the category's own name
|
|
||||||
// until the tables are linked.
|
|
||||||
func (t *table) spelledPath() string {
|
|
||||||
if t.path == "" {
|
|
||||||
return t.category
|
|
||||||
}
|
|
||||||
return t.path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectorSpelling is how a path writes a selected row, for messages: by key, or
|
// selectorSpelling is how a path writes a selected row, for messages: by key, or
|
||||||
@@ -558,7 +571,7 @@ func (t *table) spelledPath() string {
|
|||||||
func (t *table) selectorSpelling(r int) string {
|
func (t *table) selectorSpelling(r int) string {
|
||||||
switch {
|
switch {
|
||||||
case t.key >= 0:
|
case t.key >= 0:
|
||||||
return t.spelledPath() + "[" + t.cell(r, t.key) + "]"
|
return t.path + "[" + t.cell(r, t.key) + "]"
|
||||||
case t.name >= 0:
|
case t.name >= 0:
|
||||||
return t.parentT.selectorSpelling(t.parentRow(r)) + "." + t.category + "[" + t.cell(r, t.name) + "]"
|
return t.parentT.selectorSpelling(t.parentRow(r)) + "." + t.category + "[" + t.cell(r, t.name) + "]"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,6 +112,13 @@ func splitArgs(s string) []string {
|
|||||||
return append(args, strings.TrimSpace(s[start:]))
|
return append(args, strings.TrimSpace(s[start:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func plural(n int) string {
|
||||||
|
if n == 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "s"
|
||||||
|
}
|
||||||
|
|
||||||
// checkFunc validates a function token at compile time: well-formed, naming a
|
// checkFunc validates a function token at compile time: well-formed, naming a
|
||||||
// known builtin, with the arg count that builtin takes and args its check accepts.
|
// known builtin, with the arg count that builtin takes and args its check accepts.
|
||||||
// fields is passed through for the one builtin (calc) that validates against them.
|
// fields is passed through for the one builtin (calc) that validates against them.
|
||||||
|
|||||||
Reference in New Issue
Block a user