Skip to content

Commit 1d9142c

Browse files
OneMuppetclaude
andcommitted
feat: tarn show <file> N|A-B — bare line spec (sed Np reflex) (v0.8.1)
`tarn show file 5` / `file 5-9` now work as `--lines`, serving the `sed -n '5p'` / `'5,9p'` reflex. Previously a second positional silently overwrote the file path (`show file 5` tried to read a file named `5`); a non-line-spec second positional now errors clearly. Same anti-silent-overwrite shape as the v0.7.1 find fix; reuses parse_range. 168 tests (added show bare-line-spec regression), clippy + fmt clean. Docs/whitepaper to v0.8.1; test count 167->168. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bbd2672 commit 1d9142c

7 files changed

Lines changed: 56 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ All notable changes to **tarn** are documented here. The format loosely follows
44
[Keep a Changelog](https://keepachangelog.com/); this project is pre-1.0, so the
55
surface may still shift.
66

7+
## [0.8.1]
8+
9+
### `show` — accept a bare line spec (the sed `Np` reflex)
10+
- `tarn show <file> N` and `tarn show <file> A-B` now work as `--lines N` / `--lines A-B` — serving the `sed -n 'Np'` / `'A,Bp'` reflex directly. Previously a second positional silently overwrote the file path (so `show file 5` tried to read a file named `5`); a non-line-spec second positional now errors clearly instead.
11+
712
## [0.8.0]
813

914
### Navigate — `locate`: find files by name

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# The crates.io package is `tarn-cli` (the name `tarn` is taken by an unrelated
33
# crate); the installed command stays `tarn` via the [[bin]] below.
44
name = "tarn-cli"
5-
version = "0.8.0"
5+
version = "0.8.1"
66
edition = "2021"
77
authors = ["David Borgenvik"]
88
description = "A tiny, zero-dependency terminal editor and structural CLI toolkit for AI agents: navigate, search, edit, and patch code with exact line numbers and meaningful exit codes."

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
![tarn](assets/banner.svg)
44

55
[![CI](https://github.com/OneMuppet/tarn/actions/workflows/ci.yml/badge.svg)](https://github.com/OneMuppet/tarn/actions/workflows/ci.yml)
6-
&nbsp;zero dependencies&nbsp;·&nbsp;167 tests
6+
&nbsp;zero dependencies&nbsp;·&nbsp;168 tests
77

88
</div>
99

@@ -131,7 +131,7 @@ returns structured data; on edits it returns a result object you can chain.
131131

132132
## Install
133133

134-
These are live as of **v0.8.0**.
134+
These are live as of **v0.8.1**.
135135

136136
**Homebrew:**
137137

@@ -140,14 +140,14 @@ brew install onemuppet/tap/tarn
140140
```
141141

142142
**Prebuilt binary (no Rust toolchain needed)** — from the
143-
[v0.8.0 release](https://github.com/OneMuppet/tarn/releases/tag/v0.8.0):
143+
[v0.8.1 release](https://github.com/OneMuppet/tarn/releases/tag/v0.8.1):
144144

145145
```sh
146146
# macOS (Apple Silicon)
147-
curl -L https://github.com/OneMuppet/tarn/releases/download/v0.8.0/tarn-v0.8.0-aarch64-apple-darwin.tar.gz | tar xz
147+
curl -L https://github.com/OneMuppet/tarn/releases/download/v0.8.1/tarn-v0.8.1-aarch64-apple-darwin.tar.gz | tar xz
148148

149149
# Linux (x86_64)
150-
curl -L https://github.com/OneMuppet/tarn/releases/download/v0.8.0/tarn-v0.8.0-x86_64-unknown-linux-gnu.tar.gz | tar xz
150+
curl -L https://github.com/OneMuppet/tarn/releases/download/v0.8.1/tarn-v0.8.1-x86_64-unknown-linux-gnu.tar.gz | tar xz
151151
```
152152

153153
Then put the extracted `tarn` on your `PATH`.
@@ -465,7 +465,7 @@ real multiplier for an agent is **repeated navigation + batched edits**:
465465
- The diff renderer trims the common prefix/suffix so a one-line change in a
466466
40k-line file diffs in ~26 ms instead of ~7 s.
467467

468-
**Quality.** 27 commands, **167 tests**, gated by adversarial review on every
468+
**Quality.** 27 commands, **168 tests**, gated by adversarial review on every
469469
feature. The unsafe NEON path is **AddressSanitizer-clean** and the SIMD counter
470470
is **differential-tested** against a scalar oracle (900+ fuzz cases); its counts
471471
also match `rg`/`grep` on the benchmark corpus. Zero crate dependencies — std

src/main.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,28 @@ fn cmd_show(args: &[String]) -> u8 {
417417
},
418418
"--head" => head = Some(next_usize_opt(args, &mut i).unwrap_or(20)),
419419
"--tail" => tail = Some(next_usize_opt(args, &mut i).unwrap_or(20)),
420-
s if !s.starts_with("--") => file = Some(s),
420+
s if !s.starts_with("--") => {
421+
if file.is_none() {
422+
file = Some(s);
423+
} else if lines.is_none() {
424+
// A bare line spec after the file (the sed `Np` / `A,Bp`
425+
// reflex): `show file 5` or `show file 5-9` ≡ `--lines`.
426+
// Don't silently overwrite the file with a second positional.
427+
match parse_range(s) {
428+
Some((a, b)) if a <= b => lines = Some((a, b)),
429+
_ => {
430+
eprintln!(
431+
"tarn: unexpected argument {s:?} — show takes <file> then an \
432+
optional line spec (N or A-B), or --lines/--around/--head/--tail"
433+
);
434+
return EXIT_USAGE;
435+
}
436+
}
437+
} else {
438+
eprintln!("tarn: unexpected extra argument {s:?}");
439+
return EXIT_USAGE;
440+
}
441+
}
421442
other => {
422443
eprintln!("tarn: unknown flag {other}");
423444
return EXIT_USAGE;
@@ -3557,6 +3578,23 @@ mod tests {
35573578
let _ = std::fs::remove_file(&f);
35583579
}
35593580

3581+
#[test]
3582+
fn show_accepts_bare_line_spec() {
3583+
// sed `Np`/`A,Bp` reflex: `show file N` / `file A-B` work as `--lines`,
3584+
// and a second positional no longer silently overwrites the file.
3585+
let f = std::env::temp_dir().join("tarn_test_show_linespec.txt");
3586+
std::fs::write(&f, "a\nb\nc\nd\n").unwrap();
3587+
let p = f.to_str().unwrap();
3588+
let s =
3589+
|v: &[&str]| -> u8 { cmd_show(&v.iter().map(|x| x.to_string()).collect::<Vec<_>>()) };
3590+
assert_eq!(s(&[p]), EXIT_OK); // file only
3591+
assert_eq!(s(&[p, "2"]), EXIT_OK); // bare line
3592+
assert_eq!(s(&[p, "2-3"]), EXIT_OK); // bare range
3593+
assert_eq!(s(&[p, "--lines", "1"]), EXIT_OK); // explicit form still works
3594+
assert_eq!(s(&[p, "notaspec"]), EXIT_USAGE); // garbage 2nd positional errors
3595+
let _ = std::fs::remove_file(&f);
3596+
}
3597+
35603598
#[test]
35613599
fn glob_to_regex_translates() {
35623600
assert_eq!(glob_to_regex("*.env"), "^[^/]*\\.env$");

whitepaper/tarn-whitepaper.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ <h1>The editor your agent<br>wishes it had<span class="dot">.</span></h1>
126126
<div class="stat"><div class="k">Dependencies</div><div class="v">0</div><div class="d">pure Rust std; mmap, threads & SIMD via core/libc — no crates</div></div>
127127
<div class="stat"><div class="k">Commands</div><div class="v">27</div><div class="d">navigate · read · edit · config · verify</div></div>
128128
<div class="stat"><div class="k">vs ripgrep</div><div class="v">1.3×</div><div class="d">faster on a single 380 MB file (~42 vs ~57 ms); parity on many files</div></div>
129-
<div class="stat"><div class="k">Tests</div><div class="v">167</div><div class="d">+ adversarial review on every feature; ASan-clean</div></div>
129+
<div class="stat"><div class="k">Tests</div><div class="v">168</div><div class="d">+ adversarial review on every feature; ASan-clean</div></div>
130130
</div>
131131

132132
<div class="eyebrow copper" style="margin-top:40px">A field guide to a search-and-edit tool that earns an agent's trust</div>
@@ -201,7 +201,7 @@ <h3>Deterministic, guarded, chainable.</h3>
201201
previews; multi-file <code>apply</code>/<code>patch</code> are atomic with rollback; <code>--json</code> on read &amp; edit
202202
commands lets one command feed the next. An agent can act without a defensive re-read.</p>
203203
</div>
204-
<div class="foot"><span>tarn · zero dependencies · 167 tests</span><span class="c">github.com/OneMuppet/tarn · 3 / 8</span></div>
204+
<div class="foot"><span>tarn · zero dependencies · 168 tests</span><span class="c">github.com/OneMuppet/tarn · 3 / 8</span></div>
205205
</section>
206206

207207
<!-- ============ 03 SPEED ============ -->
@@ -343,7 +343,7 @@ <h2>We stopped guessing. We counted.</h2>
343343
<div class="callout">
344344
<span class="e">What it isn't</span>
345345
<h3>A useful subset, named as one.</h3>
346-
<p>The regex engine is a grep-ish subset, not PCRE — no backreferences, lookaround, or <code>\b</code>. Unsupported syntax errors <i>loudly</i> rather than matching the wrong thing, and literal substring stays the default. Structural <code>outline</code>/<code>defs</code> is heuristic, not a parser — multi-line signatures and keyword-less Java/C#/C/C++ methods are now handled by brace-balanced, string/comment-aware scanning; the residual known gap is C# <code>@"..."</code> verbatim strings. Built from the data, bounded by the truth — zero crates, 167 tests, every claim cashable.</p>
346+
<p>The regex engine is a grep-ish subset, not PCRE — no backreferences, lookaround, or <code>\b</code>. Unsupported syntax errors <i>loudly</i> rather than matching the wrong thing, and literal substring stays the default. Structural <code>outline</code>/<code>defs</code> is heuristic, not a parser — multi-line signatures and keyword-less Java/C#/C/C++ methods are now handled by brace-balanced, string/comment-aware scanning; the residual known gap is C# <code>@"..."</code> verbatim strings. Built from the data, bounded by the truth — zero crates, 168 tests, every claim cashable.</p>
347347
</div>
348348
<div class="foot"><span>tarn · roadmap by measurement, not hunch</span><span class="c">github.com/OneMuppet/tarn · 7 / 8</span></div>
349349
</section>
@@ -366,7 +366,7 @@ <h2>Trust is the product.</h2>
366366
benchmark corpus. The unsafe NEON code is AddressSanitizer-clean.</p>
367367
<p><b>Edits never corrupt.</b> Line endings (LF/CRLF) and final-newline state are preserved on every
368368
edit; format-preserving config edits keep comments and key order; multi-file batches roll back as a unit.</p>
369-
<p><b>167 tests, clippy &amp; fmt clean, CI green.</b> Every benchmark in this paper is reproducible from the
369+
<p><b>168 tests, clippy &amp; fmt clean, CI green.</b> Every benchmark in this paper is reproducible from the
370370
repo. Where a number depended on the test machine, it's labelled. Nothing here is aspirational.</p>
371371
</div>
372372

whitepaper/tarn-whitepaper.pdf

582 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)