From 09f18520447edb6c2b626d2aaad31622a41fd24e Mon Sep 17 00:00:00 2001 From: "user.email" Date: Fri, 3 Apr 2026 08:03:01 -0700 Subject: [PATCH] Support `#` as comment syntax and `\#` escaping --- CLAUDE.md | 4 +++- README.md | 15 ++++++++++++--- src/parse.rs | 15 +++++++++++++-- src/update.rs | 8 ++++---- tests/comments.shout | 10 ++++++++++ vim/README.md | 3 ++- vim/ftplugin/shout.lua | 2 +- vim/syntax/shout.vim | 13 ++++++++----- web/index.html | 7 ++++--- 9 files changed, 57 insertions(+), 20 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 1ba9314..3c12a22 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,9 @@ Transcript-based shell integration test runner. Rust. - `...` inline = matches any characters on that line - `[N]` on last line of expected output = assert exit code N - `[*]` = assert any non-zero exit code; default expects 0 -- `$#` comment line = not executed, no output expected (e.g. `$# start the server`) +- `#` at start of line = comment (not executed, no output expected) +- `$#` also works as comment (legacy syntax) +- `\#` in expected output = literal line starting with `#` - `#` after a command = comment (stripped); `#` in expected output is literal - `@env KEY=VALUE` before first command = set environment variable - `@teardown ` before first command = run command after all test commands diff --git a/README.md b/README.md index 86778ec..b16f919 100644 --- a/README.md +++ b/README.md @@ -30,16 +30,25 @@ ls: missing: No such file or directory `[1]` after the expected output matches the exit code. -`$#` is a comment line — not executed, no output expected: +`#` at the start of a line is a comment — not executed, no output expected: ``` -$# start the server +# start the server $ my-server & -$# now test it +# now test it $ curl localhost:8080 OK ``` +If expected output starts with `#`, escape it with `\#`: + +``` +$ echo "#hashtag" +\#hashtag +``` + +`#` elsewhere in expected output is literal (not a comment). + ## Usage ``` diff --git a/src/parse.rs b/src/parse.rs index 84d14c0..47df474 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -68,9 +68,10 @@ fn strip_comment(line: &str) -> &str { line } -/// A line like `$# ...` or `$ # ...` — a comment, not a real command. +/// A line like `$# ...`, `$ # ...`, or `# ...` — a comment, not a real command. pub fn is_comment_line(line: &str) -> bool { - line.starts_with("$#") + line.starts_with('#') + || line.starts_with("$#") || (line.starts_with("$ ") && strip_comment(&line[2..]).is_empty()) } @@ -269,6 +270,16 @@ pub fn parse(path: &str, content: &str) -> Result { } else if line.starts_with("\\$ ") && current.is_some() { // Escaped dollar-space: literal expected output starting with "$ " current.as_mut().unwrap().expected.push(line[1..].to_string()); + } else if line.starts_with("\\#") && current.is_some() { + // Escaped hash: literal expected output starting with "#" + current.as_mut().unwrap().expected.push(line[1..].to_string()); + } else if line.starts_with('#') { + // Comment line + if let Some(ref mut cmd) = current { + finalize_command(cmd); + commands.push(cmd.clone()); + current = None; + } } else if line.starts_with("$ ") { seen_command = true; if let Some(ref mut cmd) = current { diff --git a/src/update.rs b/src/update.rs index 169f65f..1641d76 100644 --- a/src/update.rs +++ b/src/update.rs @@ -2,8 +2,8 @@ use crate::matching::match_output; use crate::parse::{ShoutFile, is_comment_line, trim_trailing_empty}; use crate::run::CommandResult; -fn escape_dollar(line: &str) -> String { - if line.starts_with("$ ") { +fn escape_output(line: &str) -> String { + if line.starts_with("$ ") || line.starts_with('#') { format!("\\{line}") } else { line.to_string() @@ -44,7 +44,7 @@ pub fn rewrite_file( // Skip past old expected output lines in the original let mut j = i + 1; while j < lines.len() - && !is_comment_line(lines[j]) + && !(is_comment_line(lines[j]) && !lines[j].starts_with("\\#")) && !(lines[j].starts_with("$ ") && !lines[j].starts_with("\\$ ")) { j += 1; @@ -88,7 +88,7 @@ pub fn rewrite_file( } else { // Replace with actual output for al in &result.actual { - output.push(escape_dollar(al)); + output.push(escape_output(al)); } // Re-add exit code marker if it existed if let Some(marker) = old_exit_marker { diff --git a/tests/comments.shout b/tests/comments.shout index 54fe5eb..69a98e3 100644 --- a/tests/comments.shout +++ b/tests/comments.shout @@ -3,3 +3,13 @@ hello $ echo "keep # this" keep # this + +# this is a comment +$ echo "after comment" +after comment + +$ echo "#hashtag" +\#hashtag + +$ echo "https://google.com/#autoload" +https://google.com/#autoload diff --git a/vim/README.md b/vim/README.md index 1c1f52e..5ee2438 100644 --- a/vim/README.md +++ b/vim/README.md @@ -34,11 +34,12 @@ ln -s /path/to/shout/vim ~/.local/share/nvim/site/pack/shout/start/shout | Pattern | Highlight | |---|---| | `$ command` | Statement (prompt as Special) | -| `$# comment` | Comment | +| `# comment` | Comment | | `@env KEY=VALUE` | PreProc / Identifier / String | | `@setup`, `@teardown` | PreProc | | Expected output | String | | `...` (wildcard) | WarningMsg | | `[N]`, `[*]` (exit code) | Constant | | `\$ ...` (escaped dollar) | SpecialChar + String | +| `\# ...` (escaped hash) | SpecialChar + String | | `# inline comment` | Comment | diff --git a/vim/ftplugin/shout.lua b/vim/ftplugin/shout.lua index f0e9d4f..bb5a581 100644 --- a/vim/ftplugin/shout.lua +++ b/vim/ftplugin/shout.lua @@ -1,5 +1,5 @@ -- Buffer-local settings for .shout files -vim.bo.commentstring = "$# %s" +vim.bo.commentstring = "# %s" vim.bo.shiftwidth = 0 vim.bo.tabstop = 2 vim.bo.expandtab = true diff --git a/vim/syntax/shout.vim b/vim/syntax/shout.vim index 0197df9..85e6a36 100644 --- a/vim/syntax/shout.vim +++ b/vim/syntax/shout.vim @@ -16,7 +16,8 @@ syn match shoutEnvValue /.*$/ contained syn match shoutSetupDirective /^@setup\s\+.*$/ contains=shoutDirectiveKey syn match shoutTeardownDirective /^@teardown\s\+.*$/ contains=shoutDirectiveKey -" Comment commands: $# ... or $ # ... +" Comment lines: # ..., $# ..., or $ # ... +syn match shoutCommentCommand /^#.*$/ syn match shoutCommentCommand /^\$#.*$/ syn match shoutCommentCommand /^\$\s\+#.*$/ @@ -25,9 +26,11 @@ syn match shoutPrompt /^\$\s/ contained syn match shoutCommand /^\$\s.\+/ contains=shoutPrompt,shoutInlineComment syn match shoutInlineComment /\s\+#[^"']*$/ contained -" Escaped dollar in expected output -syn match shoutEscapedDollar /^\\\$/ contained -syn match shoutEscapedLine /^\\\$.*$/ contains=shoutEscapedDollar +" Escaped dollar/hash in expected output +syn match shoutEscapedChar /^\\\$/ contained +syn match shoutEscapedChar /^\\#/ contained +syn match shoutEscapedLine /^\\\$.*$/ contains=shoutEscapedChar +syn match shoutEscapedLine /^\\#.*$/ contains=shoutEscapedChar " Wildcards syn match shoutWildcardLine /^\.\.\.$/ @@ -55,7 +58,7 @@ hi def link shoutPrompt Special hi def link shoutCommand Statement hi def link shoutInlineComment Comment -hi def link shoutEscapedDollar SpecialChar +hi def link shoutEscapedChar SpecialChar hi def link shoutEscapedLine String hi def link shoutWildcardLine WarningMsg diff --git a/web/index.html b/web/index.html index b3cb60c..3902e0b 100644 --- a/web/index.html +++ b/web/index.html @@ -196,12 +196,13 @@ Homebrew 5...

... matches anything — inline or across lines.

[1] asserts the exit code.

-

$# starts a comment line — not executed, no output expected.

-
$# start the server
+  

# at the start of a line is a comment — not executed, no output expected.

+
# start the server
 $ my-server &
-$# now test it
+# now test it
 $ curl localhost:8080
 OK
+

If expected output starts with #, escape it with \#. # elsewhere in output is literal.