diff --git a/cmd/fejkdata/main.go b/cmd/fejkdata/main.go index 8a077a8..a9f207a 100644 --- a/cmd/fejkdata/main.go +++ b/cmd/fejkdata/main.go @@ -30,8 +30,8 @@ const usage = `Usage: fejkdata [flags] 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, a bracket or a quote). Templates reach the data by reference from the root — -{/sv_SE.person.first} — whether the data is shipped or layered with --data-path. A -bracket that is not valid JSON names no template and no path. +{/sv_SE.person.last} — whether the data is shipped or layered with --data-path. An +argument carrying one of those characters but no valid JSON names neither. -d, --data-path D a data directory to layer over the shipped data (repeatable; last wins on a clash) -h, --help print this help, then exit @@ -208,7 +208,7 @@ func (in invocation) check() (argKind, error) { return argPath, nil } if len(in.paths) != 1 { - return argPath, fmt.Errorf("expected one path, got %d", len(in.paths)) + return argPath, fmt.Errorf("expected one path or template, got %d", len(in.paths)) } return classify(in.paths[0]) } @@ -276,10 +276,10 @@ const ( // quote, so no path collides with any of those spellings, and the JSON gate is // valid-JSON so a copied bracket names nothing rather than swallowing an argument. func classify(arg string) (argKind, error) { - if strings.ContainsRune(arg, '{') || (isJSONStart(arg) && json.Valid([]byte(arg))) { + if strings.ContainsRune(arg, '{') || (isJSONStart(strings.TrimSpace(arg)) && json.Valid([]byte(arg))) { return argTemplate, nil } - if i := strings.IndexAny(arg, `[]"`); i >= 0 { + if i := strings.IndexAny(arg, `[]}"`); i >= 0 { return argPath, fmt.Errorf("%q holds a %q, which no path may, and it is not valid JSON, so it names no template either", arg, arg[i:i+1]) } return argPath, nil diff --git a/template.go b/template.go index cf4dffc..0f6e764 100644 --- a/template.go +++ b/template.go @@ -165,7 +165,11 @@ func checkArm(name string, fields map[string]node) error { return fmt.Errorf("%q is an option and can never be a field", a.key) } if len(fields) == 0 { - return fmt.Errorf("no field %q; a token names a sibling field, and a bare string has none — write {/%s} to reference the data", a.key, name) + hint := "" + if hintableRef(name) { + hint = fmt.Sprintf(" — write {/%s} to reference the data", name) + } + return fmt.Errorf("no field %q; a token names a sibling field, and this template has none%s", a.key, hint) } return fmt.Errorf("no field %q", a.key) } @@ -175,6 +179,17 @@ func checkArm(name string, fields map[string]node) error { return nil } +// hintableRef reports whether {/name} is a reference the grammar accepts, so the +// hint never names a spelling that fails too. +func hintableRef(name string) bool { + for _, seg := range strings.Split(name, ".") { + if checkName(seg) != nil { + return false + } + } + return true +} + // tokenOperands lists the fields one {token} body reads as operands, empty for a // field token or a builtin that reads none. func tokenOperands(body string) []string {