Make great documentation goal 1.2, and plan the README's reorganisation and the nested-template gaps #33
+3
-2
@@ -29,7 +29,8 @@ replacement, and each removed path, column or flag.
|
|||||||
- First release: the CLI, the library and the shipped data set.
|
- First release: the CLI, the library and the shipped data set.
|
||||||
- The CLI renders exactly the template on its stdin, `echo '{/sv_SE.person} hihi' |
|
- The CLI renders exactly the template on its stdin, `echo '{/sv_SE.person} hihi' |
|
||||||
fejkdata`, and adds nothing under `--format text`: `echo`'s newline ends the render, and `--separator`
|
fejkdata`, and adds nothing under `--format text`: `echo`'s newline ends the render, and `--separator`
|
||||||
defaults to empty. Whitespace around a JSON template is dropped. A lone reference,
|
defaults to empty. Whitespace around a JSON object, array or string template is
|
||||||
|
dropped, and a bare number or boolean is a format string, printed as written. A lone reference,
|
||||||
`{/users}`, is the record `users` under `--format`. An argument other than a flag, or
|
`{/users}`, is the record `users` under `--format`. An argument other than a flag, or
|
||||||
nothing piped in, exits 2; an empty or blank stdin is a template, and renders as it is.
|
nothing piped in, exits 2; an empty or blank stdin is a template, and renders as it is.
|
||||||
- Table categories: a category JSON naming a `rows` TSV beside it, with the options
|
- Table categories: a category JSON naming a `rows` TSV beside it, with the options
|
||||||
@@ -100,7 +101,7 @@ replacement, and each removed path, column or flag.
|
|||||||
from a `sv_SE.birth-number` table under `sex`, in place of `sv_SE.ssn`, whose
|
from a `sv_SE.birth-number` table under `sex`, in place of `sv_SE.ssn`, whose
|
||||||
`ssn.mmdd`, `ssn.mmdd.m` and `ssn.mmdd.d` go with it. `en_US.ssn` now draws the
|
`ssn.mmdd`, `ssn.mmdd.m` and `ssn.mmdd.d` go with it. `en_US.ssn` now draws the
|
||||||
ranges the SSA assigns and carries the columns `area`, `group` and `serial`, so
|
ranges the SSA assigns and carries the columns `area`, `group` and `serial`, so
|
||||||
`--format csv en_US.ssn` writes a header where it used to fail; `en_US.itin` is new
|
`echo '{/en_US.ssn}' | fejkdata --format csv` writes a header where it used to fail; `en_US.itin` is new
|
||||||
and carries the same three. `person.prefix` is null where a person has no title,
|
and carries the same three. `person.prefix` is null where a person has no title,
|
||||||
where it used to be an empty string, so `--format sql` writes `NULL` and a `string`
|
where it used to be an empty string, so `--format sql` writes `NULL` and a `string`
|
||||||
struct field reading it becomes `*string`.
|
struct field reading it becomes `*string`.
|
||||||
|
|||||||
@@ -30,8 +30,7 @@ born {date(1990-01-01,2010-12-31,'2006-01-02')}
|
|||||||
EOF
|
EOF
|
||||||
```
|
```
|
||||||
|
|
||||||
fejkdata renders the template on its stdin: a format string, or a JSON object, array or
|
fejkdata renders the template on its stdin: a format string, or any JSON value. Its `{…}` tokens reach the data by reference from the root,
|
||||||
string. Its `{…}` tokens reach the data by reference from the root,
|
|
||||||
`{/sv_SE.person.last}`, so shipped and `--data-path` categories are alike available,
|
`{/sv_SE.person.last}`, so shipped and `--data-path` categories are alike available,
|
||||||
and everything outside them prints as written. A reference names a category, or a field
|
and everything outside them prints as written. A reference names a category, or a field
|
||||||
inside one: each dot segment descends one level — folders, then the category (a JSON
|
inside one: each dot segment descends one level — folders, then the category (a JSON
|
||||||
@@ -40,9 +39,9 @@ these paths; a template reads one as `{/path}`.
|
|||||||
|
|
||||||
Under `--format text`, the default, what you send is what renders, and fejkdata adds nothing. A format string keeps every
|
Under `--format text`, the default, what you send is what renders, and fejkdata adds nothing. A format string keeps every
|
||||||
byte, so `echo`, a file or a heredoc ends each render with its newline, and `printf '%s'`
|
byte, so `echo`, a file or a heredoc ends each render with its newline, and `printf '%s'`
|
||||||
sends none; `-n` joins renders with `--separator`, empty by default. A JSON template is
|
sends none; `-n` joins renders with `--separator`, empty by default. A JSON object, array
|
||||||
any JSON value, and the whitespace around it is dropped; `42` or `true` alone renders its
|
or string drops the whitespace around it, and `null` alone renders nothing; `42` or `true`
|
||||||
text, and `null` alone renders nothing. An empty or blank stdin is a template too, and
|
alone is a format string, so it prints as written, newline included. An empty or blank stdin is a template too, and
|
||||||
renders as it is. A JSON template that should end
|
renders as it is. A JSON template that should end
|
||||||
each render in a newline writes `\n` in its format, from a quoted heredoc or a file. A
|
each render in a newline writes `\n` in its format, from a quoted heredoc or a file. A
|
||||||
lone reference, one `{/…}` and nothing else, is the record it names under `--format`, one
|
lone reference, one `{/…}` and nothing else, is the record it names under `--format`, one
|
||||||
@@ -55,8 +54,8 @@ template holding one from a quoted heredoc or a file. The rest of stdin's rules
|
|||||||
| `-d`, `--data-path D` | a directory to layer over the shipped data; repeatable, the last wins a name clash |
|
| `-d`, `--data-path D` | a directory to layer over the shipped data; repeatable, the last wins a name clash |
|
||||||
| `--no-shipped-data` | load only the `--data-path` directories |
|
| `--no-shipped-data` | load only the `--data-path` directories |
|
||||||
| `-s`, `--seed N` | same seed, version and data: identical output |
|
| `-s`, `--seed N` | same seed, version and data: identical output |
|
||||||
| `-n`, `--repeat N` | render the template N times (0 to 1048576), each an independent draw, streamed; `0` prints nothing, or `[]` under `--format json` |
|
| `-n`, `--repeat N` | render the template N times (0 to 1048576), each an independent draw, streamed; `0` prints nothing, `[]` under `--format json`, or the header under `--format csv` |
|
||||||
| `--separator S` | between repeated renders (default empty); a record `--format` ignores it |
|
| `--separator S` | between repeated renders (default empty); refused with a record `--format`, which has its own separators |
|
||||||
| `--format F` | `text` (default), `json`, `ndjson`, `csv` or `sql` — a record's columns, one record per row (json frames them as an array) |
|
| `--format F` | `text` (default), `json`, `ndjson`, `csv` or `sql` — a record's columns, one record per row (json frames them as an array) |
|
||||||
| `--table T` | the INSERT target for `--format sql`, ignored under any other (default: a lone reference's last segment, such as `users` for `{/users}`, else `records`) |
|
| `--table T` | the INSERT target for `--format sql`, ignored under any other (default: a lone reference's last segment, such as `users` for `{/users}`, else `records`) |
|
||||||
| `--list` | print every path, then exit; `--repeat`, `--separator`, `--format` and `--table` do nothing here |
|
| `--list` | print every path, then exit; `--repeat`, `--separator`, `--format` and `--table` do nothing here |
|
||||||
@@ -678,8 +677,10 @@ between the two days is reachable, so a layout with a clock draws the time too,
|
|||||||
`from` may equal `to`, which is that one day. The instant is UTC, so a zone in the
|
`from` may equal `to`, which is that one day. The instant is UTC, so a zone in the
|
||||||
layout prints `UTC` or `Z`.
|
layout prints `UTC` or `Z`.
|
||||||
The quotes delimit a layout outside a selector only, so `[O'Fallon]` in an
|
The quotes delimit a layout outside a selector only, so `[O'Fallon]` in an
|
||||||
argument stays a name. A layout naming no field prints itself, `time` prints a date
|
argument stays a name. A layout naming no field prints itself, `date` with a clock-only
|
||||||
field as 1970-01-01, and `date` over one day with no clock field prints that day.
|
layout prints the drawn instant's clock, and `date` over one day with no clock field prints
|
||||||
|
that day; `time` with a layout naming a date field is refused, naming `date`, since `time`
|
||||||
|
draws no date.
|
||||||
Rejected at `New`: a bound that is no calendar date, or after the other, and an unquoted
|
Rejected at `New`: a bound that is no calendar date, or after the other, and an unquoted
|
||||||
or double-quoted layout, naming the single-quoted one. `{seq()}` spans `Fake`
|
or double-quoted layout, naming the single-quoted one. `{seq()}` spans `Fake`
|
||||||
calls and `repeat`, resets with a new generator, and is the natural primary key for
|
calls and `repeat`, resets with a new generator, and is the natural primary key for
|
||||||
|
|||||||
+54
-31
@@ -35,7 +35,7 @@ fejkdata <<'EOF'
|
|||||||
born {date(1990-01-01,2010-12-31,'2006-01-02')}
|
born {date(1990-01-01,2010-12-31,'2006-01-02')}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
A template is a format string, or a JSON object, array or string. Its {…} tokens
|
A template is a format string, or any JSON value. Its {…} tokens
|
||||||
reach the data by reference from the root, {/sv_SE.person.last}, whether the data
|
reach the data by reference from the root, {/sv_SE.person.last}, whether the data
|
||||||
is shipped or layered with --data-path. A reference names a category or a dotted
|
is shipped or layered with --data-path. A reference names a category or a dotted
|
||||||
path into one ({/sv_SE.person.last}), a table's row by key or name
|
path into one ({/sv_SE.person.last}), a table's row by key or name
|
||||||
@@ -45,8 +45,9 @@ path into one ({/sv_SE.person.last}), a table's row by key or name
|
|||||||
Under --format text, the default, what you send is what renders, and fejkdata adds
|
Under --format text, the default, what you send is what renders, and fejkdata adds
|
||||||
nothing: a format string keeps every byte, so echo's newline ends each render, and
|
nothing: a format string keeps every byte, so echo's newline ends each render, and
|
||||||
printf '%s' sends none. -n joins renders with --separator, empty by default. A JSON
|
printf '%s' sends none. -n joins renders with --separator, empty by default. A JSON
|
||||||
template is any JSON value, and the whitespace around it is dropped; 42 or true alone
|
object, array or string drops the whitespace around it, and null alone renders nothing;
|
||||||
renders its text, and null nothing. An empty or blank stdin renders as it is. A
|
42 or true alone is a format string, printed as written. An empty or blank stdin
|
||||||
|
renders as it is. A
|
||||||
quoted heredoc, <<'EOF', passes $, a backslash or a quote as written.
|
quoted heredoc, <<'EOF', passes $, a backslash or a quote as written.
|
||||||
|
|
||||||
With --format json, ndjson, csv or sql the template must be a record — a JSON
|
With --format json, ndjson, csv or sql the template must be a record — a JSON
|
||||||
@@ -61,9 +62,11 @@ one INSERT.
|
|||||||
--list list the paths the data offers, each written after {/ in a template, then exit;
|
--list list the paths the data offers, each written after {/ in a template, then exit;
|
||||||
--repeat, --separator, --format and --table do nothing here
|
--repeat, --separator, --format and --table do nothing here
|
||||||
--no-shipped-data load only the --data-path directories
|
--no-shipped-data load only the --data-path directories
|
||||||
-n, --repeat N render the template N times, 0..1048576 (default 1)
|
-n, --repeat N render the template N times, 0..1048576 (default 1); 0 prints nothing,
|
||||||
|
[] under --format json, or the header under --format csv
|
||||||
-s, --seed N same seed, version and data: identical output
|
-s, --seed N same seed, version and data: identical output
|
||||||
--separator S string between repeated renders (default empty); a record --format ignores it
|
--separator S string between repeated renders (default empty); refused with a record
|
||||||
|
--format, which has its own separators
|
||||||
--table T the INSERT target for --format sql, ignored under any other
|
--table T the INSERT target for --format sql, ignored under any other
|
||||||
(default: a lone reference's last segment, else records)
|
(default: a lone reference's last segment, else records)
|
||||||
--version print the version, then exit
|
--version print the version, then exit
|
||||||
@@ -73,19 +76,20 @@ A short flag's value attaches or follows (-n3, -n 3); short flags bundle (-hn 3)
|
|||||||
`
|
`
|
||||||
|
|
||||||
type invocation struct {
|
type invocation struct {
|
||||||
args []string
|
args []string
|
||||||
dirs []string
|
dirs []string
|
||||||
format string
|
format string
|
||||||
help bool
|
help bool
|
||||||
list bool
|
list bool
|
||||||
noShipped bool
|
noShipped bool
|
||||||
repeat int
|
repeat int
|
||||||
seed uint64
|
seed uint64
|
||||||
seeded bool
|
seeded bool
|
||||||
separator string
|
separator string
|
||||||
table string
|
separatorSet bool
|
||||||
tableSet bool
|
table string
|
||||||
version bool
|
tableSet bool
|
||||||
|
version bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type flagDef struct {
|
type flagDef struct {
|
||||||
@@ -117,7 +121,7 @@ var flagDefs = []flagDef{
|
|||||||
in.seed, in.seeded = n, true
|
in.seed, in.seeded = n, true
|
||||||
return nil
|
return nil
|
||||||
}},
|
}},
|
||||||
{"separator", "", true, func(in *invocation, v string) error { in.separator = v; return nil }},
|
{"separator", "", true, func(in *invocation, v string) error { in.separator, in.separatorSet = v, true; return nil }},
|
||||||
{"table", "", true, func(in *invocation, v string) error { in.table, in.tableSet = v, true; return nil }},
|
{"table", "", true, func(in *invocation, v string) error { in.table, in.tableSet = v, true; return nil }},
|
||||||
{"version", "", false, func(in *invocation, _ string) error { in.version = true; return nil }},
|
{"version", "", false, func(in *invocation, _ string) error { in.version = true; return nil }},
|
||||||
}
|
}
|
||||||
@@ -233,15 +237,16 @@ type recordFormat struct {
|
|||||||
open string
|
open string
|
||||||
close string
|
close string
|
||||||
sep string
|
sep string
|
||||||
|
writes string // what it writes between values, which --separator would seem to set
|
||||||
}
|
}
|
||||||
|
|
||||||
// recordFormats is every --format that writes records. json frames the records
|
// recordFormats is every --format that writes records. json frames the records
|
||||||
// as one array document; ndjson writes one object per line.
|
// as one array document; ndjson writes one object per line.
|
||||||
var recordFormats = map[string]recordFormat{
|
var recordFormats = map[string]recordFormat{
|
||||||
"csv": {header: (*fejkdata.Record).CSVHeader, line: func(r *fejkdata.Record, _ string) string { return r.CSVLine() }, sep: "\n"},
|
"csv": {header: (*fejkdata.Record).CSVHeader, line: func(r *fejkdata.Record, _ string) string { return r.CSVLine() }, sep: "\n", writes: `always separates fields with ","`},
|
||||||
"json": {line: jsonLine, open: "[", close: "]", sep: ",\n"},
|
"json": {line: jsonLine, open: "[", close: "]", sep: ",\n", writes: "writes the records as one JSON array"},
|
||||||
"ndjson": {line: jsonLine, sep: "\n"},
|
"ndjson": {line: jsonLine, sep: "\n", writes: "writes one record per line"},
|
||||||
"sql": {line: func(r *fejkdata.Record, table string) string { return r.SQLInsert(table) }, sep: "\n"},
|
"sql": {line: func(r *fejkdata.Record, table string) string { return r.SQLInsert(table) }, sep: "\n", writes: "writes one INSERT per line"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonLine(r *fejkdata.Record, _ string) string { return r.JSON() }
|
func jsonLine(r *fejkdata.Record, _ string) string { return r.JSON() }
|
||||||
@@ -268,6 +273,9 @@ func (in invocation) checkFlags() error {
|
|||||||
if in.tableSet && in.table == "" && in.format == "sql" {
|
if in.tableSet && in.table == "" && in.format == "sql" {
|
||||||
return errors.New("--table names the INSERT target, so it cannot be empty")
|
return errors.New("--table names the INSERT target, so it cannot be empty")
|
||||||
}
|
}
|
||||||
|
if f, records := recordFormats[in.format]; records && in.separatorSet && !in.list {
|
||||||
|
return fmt.Errorf("--separator joins text renders; --format %s %s", in.format, f.writes)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,16 +408,19 @@ func (in invocation) writeRecords(f *fejkdata.Generator, src input, w io.Writer)
|
|||||||
return out.Flush()
|
return out.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeNoRecord writes zero records: an empty document where the format frames one, else nothing. It
|
// writeNoRecord writes zero records: the header where the format writes one, an empty document
|
||||||
// draws one record and drops it, so a lone reference to nothing fails as it would with records.
|
// where it frames one, else nothing. It draws one record, which names the header's columns, so a
|
||||||
|
// lone reference to nothing fails as it would with records.
|
||||||
func writeNoRecord(record func() (*fejkdata.Record, error), format recordFormat, w io.Writer) error {
|
func writeNoRecord(record func() (*fejkdata.Record, error), format recordFormat, w io.Writer) error {
|
||||||
if _, err := record(); err != nil {
|
r, err := record()
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
return err
|
return err
|
||||||
|
case format.header != nil:
|
||||||
|
_, err = io.WriteString(w, format.header(r)+"\n")
|
||||||
|
case format.open != "":
|
||||||
|
_, err = io.WriteString(w, format.open+format.close+"\n")
|
||||||
}
|
}
|
||||||
if format.open == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err := io.WriteString(w, format.open+format.close+"\n")
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -595,7 +606,7 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
if err := in.write(f, src, stdout); err != nil {
|
if err := in.write(f, src, stdout); err != nil {
|
||||||
return fail(stderr, err)
|
return fail(stderr, in.noDataCause(err))
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -611,6 +622,18 @@ func readStdin(stdin io.Reader) (input, error) {
|
|||||||
return parseInput(string(raw)), nil
|
return parseInput(string(raw)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noDataCause adds the flags that left the generator without data to an error saying none is
|
||||||
|
// loaded.
|
||||||
|
func (in invocation) noDataCause(err error) error {
|
||||||
|
if !in.noShipped || len(in.dirs) > 0 || !strings.HasSuffix(err.Error(), "no data is loaded") {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if te, isInput := err.(inputError); isInput {
|
||||||
|
return inputError{fmt.Errorf("%w: --no-shipped-data and no --data-path", te.error)}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%w: --no-shipped-data and no --data-path", err)
|
||||||
|
}
|
||||||
|
|
||||||
func fail(stderr io.Writer, err error) int {
|
func fail(stderr io.Writer, err error) int {
|
||||||
var te inputError
|
var te inputError
|
||||||
if errors.As(err, &te) {
|
if errors.As(err, &te) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fejkdata
|
package fejkdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/larvit/fejkdata/internal/grammar"
|
"github.com/larvit/fejkdata/internal/grammar"
|
||||||
@@ -36,7 +37,7 @@ func (f *Generator) NewTemplate(input string) (*Template, error) {
|
|||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
defer f.mu.Unlock()
|
defer f.mu.Unlock()
|
||||||
if err := loadInline(&f.root, n, "template", false); err != nil {
|
if err := loadInline(&f.root, n, "template", false); err != nil {
|
||||||
return nil, fmt.Errorf("fejkdata: %w", err)
|
return nil, f.noDataNote(fmt.Errorf("fejkdata: %w", err))
|
||||||
}
|
}
|
||||||
return &Template{g: f, n: n}, nil
|
return &Template{g: f, n: n}, nil
|
||||||
}
|
}
|
||||||
@@ -68,13 +69,18 @@ func compileInput(input string) (node, error) {
|
|||||||
return compile(inputValue(input))
|
return compile(inputValue(input))
|
||||||
}
|
}
|
||||||
|
|
||||||
// inputValue reads an inline template as the value compile takes: the JSON value it holds, the
|
// inputValue reads an inline template as the value compile takes: the JSON object, array, string
|
||||||
// whitespace around it dropped, or the input itself as a format string when it is not JSON.
|
// or null it holds, the whitespace around it dropped, or else the input itself as a format string,
|
||||||
|
// a bare number or boolean among them.
|
||||||
func inputValue(input string) jsonvalue.Value {
|
func inputValue(input string) jsonvalue.Value {
|
||||||
raw, err := jsonvalue.Decode([]byte(input))
|
raw, err := jsonvalue.Decode([]byte(input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return jsonvalue.String(input)
|
return jsonvalue.String(input)
|
||||||
}
|
}
|
||||||
|
switch raw.Any().(type) {
|
||||||
|
case json.Number, bool:
|
||||||
|
return jsonvalue.String(input)
|
||||||
|
}
|
||||||
return raw
|
return raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -83,10 +83,19 @@ func timeArg(a []string) error {
|
|||||||
if err := layoutArity("time", 1, a); err != nil {
|
if err := layoutArity("time", 1, a); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err := layoutArg(a[0])
|
layout, err := layoutArg(a[0])
|
||||||
|
if err == nil && namesADateField(layout) {
|
||||||
|
return fmt.Errorf("time(layout): '%s' names a date field, and time draws no date; write date(from,to,layout)", layout)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// namesADateField reports a layout that prints two instants differing in their date alone apart.
|
||||||
|
func namesADateField(layout string) bool {
|
||||||
|
day, nextYear := time.Date(2001, 2, 3, 4, 5, 6, 0, time.UTC), time.Date(2010, 11, 12, 4, 5, 6, 0, time.UTC)
|
||||||
|
return day.Format(layout) != nextYear.Format(layout)
|
||||||
|
}
|
||||||
|
|
||||||
// datePrep draws a second in [from 00:00:00, to 23:59:59] UTC; the span is counted
|
// datePrep draws a second in [from 00:00:00, to 23:59:59] UTC; the span is counted
|
||||||
// in seconds, since a Duration overflows past 292 years.
|
// in seconds, since a Duration overflows past 292 years.
|
||||||
func datePrep(a []string) Call {
|
func datePrep(a []string) Call {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ func childNamed(n node, seg string) (node, error) {
|
|||||||
if child, ok := n.children[seg]; ok {
|
if child, ok := n.children[seg]; ok {
|
||||||
return child, nil
|
return child, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no entry %q", seg)
|
return nil, noEntry{seg}
|
||||||
case *template:
|
case *template:
|
||||||
if child, ok := n.fields[seg]; ok {
|
if child, ok := n.fields[seg]; ok {
|
||||||
return child, nil
|
return child, nil
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ func (f *Generator) FakeRecord(path string) (*Record, error) {
|
|||||||
f.loadShippedAt(segments)
|
f.loadShippedAt(segments)
|
||||||
_, n, tail, err := resolveCategory(f.root.children, segments)
|
_, n, tail, err := resolveCategory(f.root.children, segments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fejkdata: %s: %w", path, err)
|
return nil, f.noDataNote(fmt.Errorf("fejkdata: %s: %w", path, err))
|
||||||
}
|
}
|
||||||
var frames frameStack
|
var frames frameStack
|
||||||
env := renderEnv{frames: &frames}
|
env := renderEnv{frames: &frames}
|
||||||
|
|||||||
+17
-1
@@ -1,6 +1,7 @@
|
|||||||
package fejkdata
|
package fejkdata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ func resolveCategory(root map[string]node, segments []string) (categorySegs []st
|
|||||||
}
|
}
|
||||||
n, ok := g.children[segments[i]]
|
n, ok := g.children[segments[i]]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, nil, fmt.Errorf("no entry %q", segments[i])
|
return nil, nil, nil, noEntry{segments[i]}
|
||||||
}
|
}
|
||||||
return segments[:i+1], n, segments[i+1:], nil
|
return segments[:i+1], n, segments[i+1:], nil
|
||||||
}
|
}
|
||||||
@@ -114,3 +115,18 @@ func refTokens(toks []grammar.Token) []string {
|
|||||||
}
|
}
|
||||||
return refs
|
return refs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noEntry is a path segment naming nothing in the folder it reads.
|
||||||
|
type noEntry struct{ seg string }
|
||||||
|
|
||||||
|
func (e noEntry) Error() string { return fmt.Sprintf("no entry %q", e.seg) }
|
||||||
|
|
||||||
|
// noDataNote adds to err, where a segment names nothing and f holds no data at all, that no
|
||||||
|
// data is loaded.
|
||||||
|
func (f *Generator) noDataNote(err error) error {
|
||||||
|
var ne noEntry
|
||||||
|
if errors.As(err, &ne) && len(f.root.children) == 0 && len(f.root.unloaded) == 0 {
|
||||||
|
return fmt.Errorf("%w; no data is loaded", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (f *Generator) Fake(path string) (string, error) {
|
|||||||
f.loadShippedAt(segments)
|
f.loadShippedAt(segments)
|
||||||
n, pins, err := drawCallerPath(f.drawState, &f.root, segments)
|
n, pins, err := drawCallerPath(f.drawState, &f.root, segments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("fejkdata: %s: %w", path, err)
|
return "", f.noDataNote(fmt.Errorf("fejkdata: %s: %w", path, err))
|
||||||
}
|
}
|
||||||
if _, ok := n.(*folder); ok {
|
if _, ok := n.(*folder); ok {
|
||||||
return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path)
|
return "", fmt.Errorf("fejkdata: %s names a folder, not a value", path)
|
||||||
|
|||||||
Reference in New Issue
Block a user