Weighted person names, valid personal ids and date() in both locales #21
+35
-8
@@ -472,17 +472,28 @@ 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.
|
// Alike in no field; layoutDay differs from layoutProbe in its date fields only,
|
||||||
|
// layoutClock in its clock fields only.
|
||||||
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)
|
||||||
layoutDay = time.Date(2010, 11, 12, 4, 5, 6, 0, time.UTC)
|
layoutDay = time.Date(2010, 11, 12, 4, 5, 6, 0, time.UTC)
|
||||||
|
layoutClock = time.Date(2001, 2, 3, 13, 14, 15, 0, time.UTC)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// quotedLayout reports whether an arg carries the single quotes a layout is written in.
|
||||||
|
func quotedLayout(a string) bool {
|
||||||
|
return len(a) >= 2 && a[0] == '\'' && a[len(a)-1] == '\''
|
||||||
|
}
|
||||||
|
|
||||||
// layoutArg is the Go layout a quoted arg holds, refused when unquoted or constant.
|
// layoutArg is the Go layout a quoted arg holds, refused when unquoted or constant.
|
||||||
func layoutArg(a string) (string, error) {
|
func layoutArg(a string) (string, error) {
|
||||||
if len(a) < 2 || a[0] != '\'' || a[len(a)-1] != '\'' {
|
if !quotedLayout(a) {
|
||||||
return "", fmt.Errorf("layout %s is not quoted; write '%s'", a, strings.Trim(a, "'"))
|
bare := strings.Trim(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 not quoted; write '%s', which a shell keeps only inside a double-quoted argument", a, bare)
|
||||||
}
|
}
|
||||||
layout := a[1 : len(a)-1]
|
layout := a[1 : len(a)-1]
|
||||||
if layoutProbe.Format(layout) == layoutProbe2.Format(layout) {
|
if layoutProbe.Format(layout) == layoutProbe2.Format(layout) {
|
||||||
@@ -501,18 +512,31 @@ func layoutOf(a string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// layoutArity checks a call ending in a layout takes n args, naming the quoted
|
// layoutArity checks a call ending in a layout takes n args, naming the quoted
|
||||||
// layout when an unquoted one split into more.
|
// layout where an unquoted one split into more.
|
||||||
func layoutArity(name string, n int, a []string) error {
|
func layoutArity(name string, n int, a []string) error {
|
||||||
if len(a) == n {
|
if len(a) == n {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
hint := ""
|
hint := ""
|
||||||
if len(a) > n {
|
if len(a) > n && !split(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 args, got %d%s", name, 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
|
||||||
|
// comma split apart.
|
||||||
|
func split(a []string) bool {
|
||||||
|
for _, arg := range a {
|
||||||
|
if quotedLayout(arg) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
@@ -525,8 +549,8 @@ func dateArgs(_ map[string]node, a []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("date(from,to,layout): to %q is not a YYYY-MM-DD date", a[1])
|
return fmt.Errorf("date(from,to,layout): to %q is not a YYYY-MM-DD date", a[1])
|
||||||
}
|
}
|
||||||
if !from.Before(to) {
|
if to.Before(from) {
|
||||||
return fmt.Errorf("date(from,to,layout): from %s is not before to %s", a[0], a[1])
|
return fmt.Errorf("date(from,to,layout): from %s is after to %s", a[0], a[1])
|
||||||
}
|
}
|
||||||
layout, err := layoutArg(a[2])
|
layout, err := layoutArg(a[2])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -535,6 +559,9 @@ func dateArgs(_ map[string]node, a []string) error {
|
|||||||
if layoutProbe.Format(layout) == layoutDay.Format(layout) {
|
if layoutProbe.Format(layout) == layoutDay.Format(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) {
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,6 +140,10 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
|
|||||||
return nil, fmt.Errorf("fejkdata: %s descends into %q, a field; only a category-level template is a record", path, tail[0])
|
return nil, fmt.Errorf("fejkdata: %s descends into %q, a field; only a category-level template is a record", path, tail[0])
|
||||||
}
|
}
|
||||||
shape := f.recordShapeOf(n)
|
shape := f.recordShapeOf(n)
|
||||||
|
if errors.Is(shape.err, errNoColumns) {
|
||||||
|
column := path[strings.LastIndex(path, ".")+1:]
|
||||||
|
return nil, fmt.Errorf("fejkdata: %s %w; render it as a column of one: '{\"format\":\"\",\"%s\":\"{/%s}\"}'", path, shape.err, column, 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)
|
||||||
}
|
}
|
||||||
@@ -227,6 +231,10 @@ func (f *Generator) FakeRecordTemplate(input string) (*Record, error) {
|
|||||||
return t.Fake(), nil
|
return t.Fake(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errNoColumns is the one record fence a path can answer, so its entry point names
|
||||||
|
// the record to write instead.
|
||||||
|
var errNoColumns = errors.New("has no fields, so no columns")
|
||||||
|
|
||||||
// recordOf is the fence both record entry points pass. The columns come back with
|
// recordOf is the fence both record entry points pass. The columns come back with
|
||||||
// the template, fixed for every draw the caller goes on to make.
|
// the template, fixed for every draw the caller goes on to make.
|
||||||
func recordOf(n node) (*template, []Column, error) {
|
func recordOf(n node) (*template, []Column, error) {
|
||||||
@@ -239,7 +247,7 @@ func recordOf(n node) (*template, []Column, error) {
|
|||||||
}
|
}
|
||||||
names := recordColumns(t)
|
names := recordColumns(t)
|
||||||
if len(names) == 0 {
|
if len(names) == 0 {
|
||||||
return nil, nil, errors.New("has no fields, so no columns")
|
return nil, nil, errNoColumns
|
||||||
}
|
}
|
||||||
if !t.record {
|
if !t.record {
|
||||||
return nil, nil, fmt.Errorf("carries repeat %d, which composes its format into one string; a record projects columns instead — drop the repeat and render the record again for more rows", t.repeat)
|
return nil, nil, fmt.Errorf("carries repeat %d, which composes its format into one string; a record projects columns instead — drop the repeat and render the record again for more rows", t.repeat)
|
||||||
|
|||||||
@@ -14,6 +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
|
||||||
file string
|
file string
|
||||||
format *template // fields are the column nodes
|
format *template // fields are the column nodes
|
||||||
columns []string
|
columns []string
|
||||||
@@ -350,6 +351,7 @@ 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
|
||||||
}
|
}
|
||||||
@@ -508,7 +510,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.category)
|
return 0, fmt.Errorf("%s has no key or name column to select a row by", t.spelledPath())
|
||||||
}
|
}
|
||||||
if r, ok := t.byKey[sel]; ok {
|
if r, ok := t.byKey[sel]; ok {
|
||||||
return r, nil
|
return r, nil
|
||||||
@@ -521,7 +523,7 @@ 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.category, sel)
|
return 0, fmt.Errorf("no row of %s has key or name %q", t.spelledPath(), sel)
|
||||||
}
|
}
|
||||||
keys := make([]string, len(rows))
|
keys := make([]string, len(rows))
|
||||||
for i, r := range rows {
|
for i, r := range rows {
|
||||||
@@ -531,14 +533,24 @@ func (t *table) find(sel string, d *draws) (int, error) {
|
|||||||
keys[i] = t.cell(r, t.key)
|
keys[i] = t.cell(r, t.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
listed := strings.Join(keys, ", ")
|
||||||
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 %v", sel, len(rows), t.category, t.parentT.category, keys)
|
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)
|
||||||
}
|
}
|
||||||
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.category)
|
||||||
}
|
}
|
||||||
return 0, fmt.Errorf("%q names %d rows of %s; select one by key, one of %v%s", sel, len(rows), t.category, keys, inside)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
@@ -546,7 +558,7 @@ func (t *table) find(sel string, d *draws) (int, error) {
|
|||||||
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.category + "[" + t.cell(r, t.key) + "]"
|
return t.spelledPath() + "[" + 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) + "]"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -125,7 +125,7 @@ func checkFunc(body string, fields map[string]node) error {
|
|||||||
return fmt.Errorf("token {%s}: unknown function %q", body, name)
|
return fmt.Errorf("token {%s}: unknown function %q", body, name)
|
||||||
}
|
}
|
||||||
if b.arity >= 0 && len(args) != b.arity {
|
if b.arity >= 0 && len(args) != b.arity {
|
||||||
return fmt.Errorf("token {%s}: %s takes %d args, got %d", body, name, b.arity, len(args))
|
return fmt.Errorf("token {%s}: %s takes %d argument%s, got %d", body, name, b.arity, plural(b.arity), len(args))
|
||||||
}
|
}
|
||||||
if b.check != nil {
|
if b.check != nil {
|
||||||
if err := b.check(fields, args); err != nil {
|
if err := b.check(fields, args); err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user