Compare commits
4 Commits
c9dba93f7f
...
c62c1d3161
| Author | SHA1 | Date | |
|---|---|---|---|
| c62c1d3161 | |||
| 6fe643bf19 | |||
| 691cebbfee | |||
| 09f1852044 |
|
|
@ -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
|
||||
|
|
|
|||
15
README.md
15
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
|
||||
|
||||
```
|
||||
|
|
|
|||
15
src/parse.rs
15
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<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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
295
web/index.html
295
web/index.html
|
|
@ -11,13 +11,16 @@
|
|||
:root {
|
||||
--bg: #fff;
|
||||
--fg: #444;
|
||||
--bright: #1a1a1a;
|
||||
--bright: #000;
|
||||
--green: #1a7f37;
|
||||
--red: #cf222e;
|
||||
--dim: #888;
|
||||
--accent: #1a7f37;
|
||||
--code-bg: #f5f5f5;
|
||||
--border: #ddd;
|
||||
--yellow: #9a7200;
|
||||
--install-bg: #f0faf2;
|
||||
--install-border: #c5e4cc;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
|
@ -31,83 +34,176 @@
|
|||
--accent: #4ec966;
|
||||
--code-bg: #111;
|
||||
--border: #222;
|
||||
--yellow: #e5b567;
|
||||
--install-bg: #0d1a10;
|
||||
--install-border: #1a3d20;
|
||||
}
|
||||
}
|
||||
|
||||
html { font-size: 16px; }
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', 'JetBrains Mono', 'Menlo', 'Consolas', monospace;
|
||||
line-height: 1.6;
|
||||
padding: 0 1.5rem;
|
||||
max-width: 680px;
|
||||
padding: 0 24px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* ---- Hero ---- */
|
||||
header {
|
||||
padding: 6rem 0 2.5rem;
|
||||
padding: 64px 0 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
font-size: 64px;
|
||||
font-weight: 800;
|
||||
color: var(--bright);
|
||||
letter-spacing: -0.03em;
|
||||
letter-spacing: -0.04em;
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
h1 span {
|
||||
h1 .dollar {
|
||||
color: var(--accent);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
font-size: 1.1rem;
|
||||
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
color: var(--dim);
|
||||
margin-top: 0.5rem;
|
||||
margin-top: 8px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ---- Install ---- */
|
||||
.install {
|
||||
margin-top: 2rem;
|
||||
display: inline-block;
|
||||
margin-top: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: var(--install-bg);
|
||||
border: 1px solid var(--install-border);
|
||||
padding: 11px 11px 11px 19px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: var(--bright);
|
||||
}
|
||||
|
||||
.install code {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.install .prompt { color: var(--dim); }
|
||||
.install .cmd { color: var(--bright); }
|
||||
|
||||
.copy-btn {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 7px 14px;
|
||||
border-radius: 5px;
|
||||
font-family: inherit;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: opacity 0.15s;
|
||||
flex-shrink: 0;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.copy-btn { color: #0a0a0a; }
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* ---- Section boxes ---- */
|
||||
.section-box {
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9rem;
|
||||
color: var(--bright);
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: border-color 0.15s;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
padding-top: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.install:hover {
|
||||
border-color: var(--accent);
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.4fr;
|
||||
gap: 32px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.install .hint {
|
||||
color: var(--dim);
|
||||
font-size: 0.75rem;
|
||||
margin-left: 1rem;
|
||||
.two-col .explain h2 {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 0 0 2em 0;
|
||||
.two-col .explain p {
|
||||
margin-bottom: 14px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.two-col .explain .feature {
|
||||
margin-bottom: 11px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.two-col .explain .feature code {
|
||||
padding: 2px 0px;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.section-box pre {
|
||||
background: var(--bg);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.section-box pre + pre {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.section-box p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.two-col {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
.section-box {
|
||||
padding: 19px;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 0.85rem;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--dim);
|
||||
color: var(--fg);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
|
||||
section {
|
||||
padding: 0 0 32px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 0.95rem;
|
||||
margin-bottom: 16px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.bright { color: var(--bright); }
|
||||
|
|
@ -118,19 +214,19 @@
|
|||
pre {
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 1.2rem 1.4rem;
|
||||
border-radius: 6px;
|
||||
padding: 19px 22px;
|
||||
overflow-x: auto;
|
||||
font-size: 0.85rem;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 1.5rem;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
pre code {
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
.prompt { color: var(--dim); }
|
||||
.prompt { color: var(--yellow); }
|
||||
.cmd { color: var(--bright); }
|
||||
.output { color: var(--fg); }
|
||||
.comment { color: var(--dim); font-style: italic; }
|
||||
|
|
@ -138,12 +234,11 @@
|
|||
.exit-code { color: var(--red); }
|
||||
.pass { color: var(--green); }
|
||||
|
||||
|
||||
footer {
|
||||
padding: 3rem 0;
|
||||
padding: 48px 0;
|
||||
border-top: 1px solid var(--border);
|
||||
color: var(--dim);
|
||||
font-size: 0.8rem;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
|
|
@ -165,26 +260,37 @@
|
|||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
header { padding: 3rem 0 2rem; }
|
||||
h1 { font-size: 2rem; }
|
||||
section { padding: 2rem 0; }
|
||||
header { padding: 40px 0 0; }
|
||||
h1 { font-size: 44px; }
|
||||
.subtitle { font-size: 16px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1><span>$</span> shout</h1>
|
||||
<p class="tagline">shell output tester</p>
|
||||
<div class="install" onclick="navigator.clipboard.writeText('curl -fsSL https://because.sh/shout | sh')">
|
||||
<span class="prompt">$</span> <span class="cmd">curl -fsSL https://because.sh/shout | sh</span>
|
||||
<span class="hint">click to copy</span>
|
||||
<h1><span class="dollar">$</span>shout</h1>
|
||||
<p class="subtitle">shell output tester</p>
|
||||
<div class="install">
|
||||
<code><span class="prompt">$</span> <span class="cmd">curl -fsSL https://because.sh/shout | sh</span></code>
|
||||
<button class="copy-btn" onclick="copyInstall(this)">Copy</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<h2>Write a test</h2>
|
||||
<p>A <code>.shout</code> file is just a shell session. Commands start with <code class="bright">$</code>, everything else is expected output.</p>
|
||||
<div style="padding: 40px 0 0;">
|
||||
<div class="section-box">
|
||||
<div class="two-col">
|
||||
<div class="explain">
|
||||
<h2>✓ Write a test</h2>
|
||||
<p>Each <code class="bright">.shout</code> file is a session.</p>
|
||||
<p class="feature">Commands start with <code class="prompt">$</code>.</p>
|
||||
<p class="feature">Everything else is expected output.</p>
|
||||
<p class="feature"><code class="wildcard">...</code> matches anything — inline or across lines.</p>
|
||||
<p class="feature"><code class="exit-code">[1]</code> asserts the exit code. Default expects 0.</p>
|
||||
<p class="feature">Lines starting with <code class="dim">#</code> are comments.</p>
|
||||
<p class="feature">(<code class="dim">\#</code> matches output starting with <code class="dim">#</code>.)</p>
|
||||
</div>
|
||||
<div class="example">
|
||||
<pre><code><span class="prompt">$</span> <span class="cmd">echo hello</span>
|
||||
<span class="output">hello</span>
|
||||
|
||||
|
|
@ -193,20 +299,26 @@
|
|||
<span class="exit-code">[1]</span>
|
||||
|
||||
<span class="prompt">$</span> <span class="cmd">brew --version</span>
|
||||
<span class="output">Homebrew 5</span><span class="wildcard">...</span></code></pre>
|
||||
<p><code class="wildcard">...</code> matches anything — 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 — not executed, no output expected.</p>
|
||||
<pre><code><span class="prompt">$</span><span class="comment"># start the server</span>
|
||||
<span class="output">Homebrew 5</span><span class="wildcard">...</span>
|
||||
|
||||
<span class="comment"># start the server</span>
|
||||
<span class="prompt">$</span> <span class="cmd">my-server &</span>
|
||||
<span class="prompt">$</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>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2>Setup & teardown</h2>
|
||||
<p>Use <code class="bright">@setup</code> to share commands across test files. Use <code class="bright">@teardown</code> to clean up after tests — it runs regardless of pass/fail.</p>
|
||||
<div class="section-box">
|
||||
<div class="two-col">
|
||||
<div class="explain">
|
||||
<h2>✓ Setup & teardown</h2>
|
||||
<p>Use <code class="bright">@setup</code> to share commands across test files.</p>
|
||||
<p>Use <code class="bright">@teardown</code> to clean up after tests — it runs regardless of pass/fail.</p>
|
||||
<p><code>@teardown</code> can appear in both <code>.shout</code> files and setup files. Teardown failures produce warnings but don't affect test results.</p>
|
||||
</div>
|
||||
<div class="example">
|
||||
<pre><code><span class="comment"># setup.shout</span>
|
||||
<span class="output">export DB_URL=sqlite:data/test.db</span>
|
||||
<span class="bright">@teardown</span> <span class="cmd">rm -f "$SHOUT_PROJECT_DIR/data/test.db"</span></code></pre>
|
||||
|
|
@ -215,32 +327,51 @@
|
|||
|
||||
<span class="prompt">$</span> <span class="cmd">create-db && run-tests</span>
|
||||
<span class="wildcard">...</span></code></pre>
|
||||
<p><code>@teardown</code> can appear in both <code>.shout</code> files and setup files. Teardown failures produce warnings but don't affect test results.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2>Macros</h2>
|
||||
<div class="section-box">
|
||||
<div class="two-col">
|
||||
<div class="explain">
|
||||
<h2>✓ Macros</h2>
|
||||
<p>Use <code class="bright">@def</code> to define reusable command macros.</p>
|
||||
<p>If a command matches a macro name exactly, the body is substituted. Use <code>\</code> for multi-line bodies. Macros from setup files are inherited; user-file macros override them.</p>
|
||||
</div>
|
||||
<div class="example">
|
||||
<pre><code><span class="bright">@def</span> <span class="cmd">greet echo "hello world"</span>
|
||||
|
||||
<span class="prompt">$</span> <span class="cmd">greet</span>
|
||||
<span class="output">hello world</span></code></pre>
|
||||
<p>If a command matches a macro name exactly, the body is substituted. Use <code>\</code> for multi-line bodies — the body can start on the same line or on the next continuation line. Macros from setup files are inherited; user-file macros override them.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2>Run it</h2>
|
||||
<div class="section-box">
|
||||
<div class="two-col">
|
||||
<div class="explain">
|
||||
<h2>✓ Run it</h2>
|
||||
<p>Each file gets a fresh temp directory and its own <code>/bin/sh</code> session. State carries between commands within a file.</p>
|
||||
</div>
|
||||
<div class="example">
|
||||
<pre><code><span class="prompt">$</span> <span class="cmd">shout test</span>
|
||||
<span class="pass">...............
|
||||
15 passed</span> <span class="dim">in 23ms</span></code></pre>
|
||||
<p>Each file gets a fresh temp directory and its own <code>/bin/sh</code> session. State carries between commands within a file.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2>Update expectations</h2>
|
||||
<pre><code><span class="prompt">$</span> <span class="cmd">shout test --update</span></code></pre>
|
||||
<div class="section-box">
|
||||
<div class="two-col">
|
||||
<div class="explain">
|
||||
<h2>✓ Update expectations</h2>
|
||||
<p>Rewrites your <code>.shout</code> files with the actual output. No more copy-pasting from the terminal.</p>
|
||||
</section>
|
||||
</div>
|
||||
<div class="example">
|
||||
<pre><code><span class="prompt">$</span> <span class="cmd">shout test --update</span></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2>Usage</h2>
|
||||
|
|
@ -281,5 +412,13 @@ Options:
|
|||
<a href="https://github.com/because/shout">GitHub</a> · <a href="https://www.npmjs.com/package/@because/shout">npm</a>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
function copyInstall(btn) {
|
||||
navigator.clipboard.writeText('curl -fsSL https://because.sh/shout | sh');
|
||||
btn.textContent = 'Copied!';
|
||||
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user