From d52de218c85af0866ba322f90c040bb292a9b144 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 10 Oct 2026 18:39:20 +0200 Subject: [PATCH] Quote none of the line in the unclosed-quote refusal: a refusal is kept on the bus as the call's answer (hq issue 294) --- cmd/mesh-controller/seatverbs.go | 19 +++++++------------ cmd/mesh-controller/seatverbs_test.go | 7 +++++-- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/cmd/mesh-controller/seatverbs.go b/cmd/mesh-controller/seatverbs.go index 7116e6da..81838973 100644 --- a/cmd/mesh-controller/seatverbs.go +++ b/cmd/mesh-controller/seatverbs.go @@ -1405,7 +1405,7 @@ func splitCommandLine(line string) ([]string, error) { } } if quote != 0 { - return nil, unclosedQuote(runes, quote, opened) + return nil, unclosedQuote(quote, opened) } if inWord { words = append(words, cur.String()) @@ -1415,18 +1415,13 @@ func splitCommandLine(line string) ([]string, error) { // unclosedQuote is the refusal of a line whose quote is never closed. Most often an apostrophe inside // a single-quoted value ended that quote early and a later quote was left open, so the refusal says -// where the open quote is, shows the line from there, and says how a quote is written inside a quoted -// value — the shell's own two ways, which this splitter already reads (novox/hq issue 294). -func unclosedQuote(runes []rune, quote rune, opened int) error { - const shown = 40 - from := runes[opened:] - excerpt := string(from) - if len(from) > shown { - excerpt = string(from[:shown]) + "…" - } - return fmt.Errorf("command has an unclosed %c quote, opened at character %d: %s — an apostrophe "+ +// where the open quote is and how a quote is written inside a quoted value — the shell's own two +// ways, which this splitter already reads (novox/hq issue 294). It quotes none of the line: a refusal +// is kept on the bus as the call's answer, and the line may carry a setting's value. +func unclosedQuote(quote rune, opened int) error { + return fmt.Errorf("command has an unclosed %c quote, opened at character %d — an apostrophe "+ "inside a single-quoted value ends it; write a ' inside single quotes as '\\'' (it'\\''s), or use "+ - "double quotes and write \\\" for a \" and \\\\ for a \\ inside them", quote, opened+1, excerpt) + "double quotes and write \\\" for a \" and \\\\ for a \\ inside them", quote, opened+1) } // seatAnnouncement is what the controller says it serves on the bus (novox/hq ADR 0197): the diff --git a/cmd/mesh-controller/seatverbs_test.go b/cmd/mesh-controller/seatverbs_test.go index dd1c638e..98df004c 100644 --- a/cmd/mesh-controller/seatverbs_test.go +++ b/cmd/mesh-controller/seatverbs_test.go @@ -417,7 +417,7 @@ func TestNoVerbSetsTheOperatorsKeyOrRevealsASecret(t *testing.T) { } // A value with a quote in it can be said on a `command` line, as in a shell, and a line whose quote -// is left open is refused naming where it opened and how a quote is written (novox/hq issue 294: an +// is left open is refused naming where it opened and how a quote is written, quoting none of it (novox/hq issue 294: an // apostrophe inside a single-quoted JSON value cut the line, and the refusal named no cause). func TestAQuotedValueCanHoldAQuote(t *testing.T) { for _, c := range []struct{ line, want string }{ @@ -436,11 +436,14 @@ func TestAQuotedValueCanHoldAQuote(t *testing.T) { if err == nil { t.Fatal("an apostrophe that leaves a quote open was accepted") } - for _, want := range []string{"character 57", `"}' --node g14`, `'\''`, `\"`} { + for _, want := range []string{"character 57", `'\''`, `\"`} { if !strings.Contains(err.Error(), want) { t.Errorf("the refusal does not say %q: %v", want, err) } } + if strings.Contains(err.Error(), "laptop") || strings.Contains(err.Error(), "g14") { + t.Errorf("the refusal quotes the line, which may carry a setting's value: %v", err) + } } // Every value reads back as it was when written the way the refusal says: single-quoted with '\”