Support # as comment syntax and \# escaping

This commit is contained in:
user.email 2026-04-03 08:03:01 -07:00
parent c9dba93f7f
commit 09f1852044
9 changed files with 57 additions and 20 deletions

View File

@ -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 <command>` before first command = run command after all test commands

View File

@ -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
```

View File

@ -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<ShoutFile, ParseError> {
} 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 {

View File

@ -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 {

View File

@ -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

View File

@ -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 |

View File

@ -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

View File

@ -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

View File

@ -196,12 +196,13 @@
<span class="output">Homebrew 5</span><span class="wildcard">...</span></code></pre>
<p><code class="wildcard">...</code> matches anything &mdash; inline or across lines.</p>
<p><code class="exit-code">[1]</code> asserts the exit code.</p>
<p><code class="dim">$#</code> starts a comment line &mdash; not executed, no output expected.</p>
<pre><code><span class="prompt">$</span><span class="comment"># start the server</span>
<p><code class="dim">#</code> at the start of a line is a comment &mdash; not executed, no output expected.</p>
<pre><code><span class="comment"># start the server</span>
<span class="prompt">$</span> <span class="cmd">my-server &amp;</span>
<span class="prompt">$</span><span class="comment"># now test it</span>
<span class="comment"># now test it</span>
<span class="prompt">$</span> <span class="cmd">curl localhost:8080</span>
<span class="output">OK</span></code></pre>
<p>If expected output starts with <code>#</code>, escape it with <code>\#</code>. <code>#</code> elsewhere in output is literal.</p>
</section>
<section>