Fix the help example, read padding past the shape gate, catch a stray closing brace, and stop the fieldless hint naming a spelling that fails

This commit is contained in:
2026-09-03 20:58:01 +02:00
parent d8048df169
commit 0cdbdb7bf4
2 changed files with 21 additions and 6 deletions
+5 -5
View File
@@ -30,8 +30,8 @@ const usage = `Usage: fejkdata [flags] <path|template>
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, a bracket 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 — 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 {/sv_SE.person.last} — whether the data is shipped or layered with --data-path. An
bracket that is not valid JSON names no template and no path. 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) -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 -h, --help print this help, then exit
@@ -208,7 +208,7 @@ func (in invocation) check() (argKind, error) {
return argPath, nil return argPath, nil
} }
if len(in.paths) != 1 { 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]) 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 // 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. // valid-JSON so a copied bracket names nothing rather than swallowing an argument.
func classify(arg string) (argKind, error) { 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 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, 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 return argPath, nil
+16 -1
View File
@@ -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) return fmt.Errorf("%q is an option and can never be a field", a.key)
} }
if len(fields) == 0 { 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) return fmt.Errorf("no field %q", a.key)
} }
@@ -175,6 +179,17 @@ func checkArm(name string, fields map[string]node) error {
return nil 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 // tokenOperands lists the fields one {token} body reads as operands, empty for a
// field token or a builtin that reads none. // field token or a builtin that reads none.
func tokenOperands(body string) []string { func tokenOperands(body string) []string {