>_EXECUTIVE SUMMARY
temprs is a single-binary stack-based temporary file manager written in Rust. The CLI is built on clap; persistence runs through a null-byte delimited master record with atomic renames and flock-protected exclusive access. Every operation that takes an INDEX also accepts a tag name (@name) — resolution falls through numeric parse to tag lookup. Two binaries (tp and temprs) ship from one Cargo target. 4,767 production Rust lines + 57,945 test Rust lines + 5,644 tests (5,059 unit + 585 integration) back the surface: stack mutation (push, pop, shift, unshift, insert, remove, move, swap, duplicate, reverse, sort), inspection (head, tail, wc, size, path, info), transformation (find-and-replace, grep, concat, diff), and editor handoff. CI runs check + test + fmt + clippy + doc + release build on every push.
tp+temprs)Source distribution — 62,712 Rust lines total
Production source: src/lib.rs, src/main.rs, src/util/{consts,utils,mod}.rs, src/model/{mod,app,state/mod}.rs, and the CLI definition in src/model/opts.rs (lines 1–259). Test source: tests/integration.rs + 7 contract/edge test files + inline test modules in src/model/opts.rs, src/model/apply_permutation_tests.rs, src/model/state/tests.rs.
~SOURCE LAYOUT
Module-level breakdown of the Rust source tree. opts.rs dominates the line count because the inline test module holds 4,449 combinatorial CLI argument tests — the production CLI definition itself is the first 259 lines of that file.
| File | Lines | Tests | Role |
|---|---|---|---|
| src/main.rs | 9 | — | Binary entrypoint (shared by tp + temprs) |
| src/lib.rs | 10 | — | Library root; re-exports model + util |
| src/util/mod.rs | 4 | — | Util module declaration |
| src/util/consts.rs | 918 | 87 | Flag names, error strings, banner, separators (inline tests for string contracts) |
| src/util/utils.rs | 2,204 | 174 | Stack ops, master-record I/O, flock, filesystem helpers (inline unit tests) |
| src/model/mod.rs | 9 | — | Model module declaration |
| src/model/app.rs | 1,102 | 6 | Top-level dispatch — matches parsed flags to handlers (smoke tests) |
| src/model/state/mod.rs | 252 | — | State struct — in-memory mirror of the master record |
| src/model/state/tests.rs | 1,980 | 181 | State invariants — serialization round-trips, name uniqueness, edge cases |
| src/model/opts.rs (CLI def) | 259 | — | Production clap definition + cyberpunk help template |
| src/model/opts.rs (tests) | 46,866 | 4,449 | Combinatorial flag parsing tests — long form, short form, value parsing, mutual exclusion |
| src/model/apply_permutation_tests.rs | 1,531 | 162 | Stack mutation permutation suite — move/swap/sort/reverse correctness |
| tests/integration.rs | 5,900 | 530 | End-to-end CLI tests — spawn the binary, pipe stdin, assert stdout/stderr/files |
| tests/edge_cases.rs | 428 | 23 | Edge-case CLI scenarios — malformed input, overflow, exotic UTF-8 |
| tests/contract_input_output_master.rs | 267 | 9 | Master record input/output contract pins |
| tests/contract_algebra_round4.rs | 256 | 6 | Stack-algebra round-trip contract pins |
| tests/contract_name_invariants.rs | 193 | 8 | @name resolution invariants |
| tests/tag_alignment_after_mutation.rs | 214 | 3 | Name/path parallel-array sync across shift, pop, rev, remove |
| tests/master_record_tag_roundtrip.rs | 116 | 3 | Multi-tag master-record round-trip alignment |
| tests/expire_invalid_hours.rs | 194 | 3 | --expire rejects negative / NaN / inf hours |
| benches/benchmarks.rs | 144 | — | Criterion benchmarks — stack ops, master record round-trips |
| TOTAL Rust | 62,856 | 5,644 | All cargo test functions pass on every push to main |
Note: the test-attribute grep yields 4,449 + 162 + 181 + 174 + 87 + 6 + 530 + 23 + 9 + 6 + 8 + 3 + 3 + 3 = 5,644 across inline + tests/ files.
$DEPENDENCIES
Five direct runtime dependencies, one dev dependency. The lock file resolves to 99 transitive crates. Selection criteria: foundational, audit-friendly, durable — no flavor-of-the-month wrappers.
| Crate | Version | Role | Why |
|---|---|---|---|
| clap | 4.6 | CLI parser | De facto standard for Rust CLIs; derives long/short, mutual exclusion, value validation, custom help template |
| fs2 | 0.4 | File locking | FileExt::lock_exclusive / unlock — the master record's concurrency guard |
| log | 0.4 | Logging facade | Decouples library logging from the binary's choice of backend |
| simple_logger | 5.2 | Log backend (active) | The runtime logger: State::new calls simple_logger::init_with_level(Info) — zero-config, on by default |
| env_logger | 0.11 | Log backend (declared) | Declared in Cargo.toml for an env-driven backend; the binary's default init path uses simple_logger |
| criterion (dev) | 0.8 | Benchmark harness | Statistical benchmarking with regression detection — gated to [dev-dependencies] |
&STORAGE FORMAT
The persistent state of the stack lives in a single master record file. The format is null-byte delimited so any filename — including ones with newlines, tabs, spaces, or other shell-hostile characters — round-trips losslessly. Writes are atomic; reads are flock-protected.
Field delimiter
\0 separates fields within a record (filename, optional tag).
Record delimiter
\0\0 separates records. Stack order = file order.
Atomic writes
Write to temprs-stack.tmp, rename() to temprs-stack. A crash leaves the previous master intact.
Exclusive locking
fs2::FileExt::lock_exclusive on temprs-stack.lock wraps every read-modify-write. Concurrent shells serialize.
Auto-recovery
Empty or malformed records on read are silently skipped and dropped on the next write.
Storage root
$TMPDIR/temprs by default; override with TEMPRS_DIR. The master record (temprs-stack) lives alongside the tempfile<timestamp> payloads.
Purge safety
tp -c refuses to clear unless the directory basename contains temprs — a guard against TEMPRS_DIR=$HOME wiping unrelated files.
%DISPATCH & RESOLUTION
One code path drives both binaries. main() constructs a TempApp and calls run(); run() reads the parsed clap matches and dispatches each flag in turn. Index/name resolution is a single fall-through function shared by every operation that takes an INDEX.
Entry point
src/main.rs — TempApp::new() then app.run(). tp and temprs are the same binary built from one target.
Startup (State::new)
Init logger (simple_logger, Info), resolve TEMPRS_DIR (default $TMPDIR/temprs), create the directory if absent, then take an exclusive flock on temprs-stack.lock.
CLI definition
src/model/opts.rs (lines 1–259) — the clap Command with grouped cyberpunk help template, short + long forms, value names, and allow_hyphen_values so negative indices parse.
Dispatch
TempApp::run in src/model/app.rs — a flat sequence of get_flag / get_one / get_many checks, each calling the matching handler.
Index/name resolution
resolve_idx — parse::<i32>() first (negatives mapped via util_transform_idx), then a name lookup by position() over the parallel names vector. Unresolved arguments terminate with Invalid specified index.
Parallel arrays
The stack is two aligned vectors — file paths and optional tags — kept in sync across every mutation. tests/tag_alignment_after_mutation.rs pins that sync across shift, pop, rev, and remove.
@CAPABILITY SURFACE
Grouped by behavior. Every operation that takes INDEX also accepts a tag name — the resolver tries numeric parse first, then falls through to tag lookup. Negative indices count from top of stack.
I/O
Push stdin or load a file; read by index or tag; verbose mode also tees to stdout.
tp— push stdin to toptp FILE— load file to toptp -i N— write stdin into Ntp -o N— read N to stdouttp -A N— append stdin to Ntp -v— verbose (also tee to stdout)tp -q— quiet (suppress create output)
Stack mutation
Position-based operations with the full Perl/Ruby array vocabulary.
-ppop,-uunshift,-sshift-a Ninsert at N,-r Nremove-M src dstmove-S a bswap-x Nduplicate to top
Bulk
Whole-stack transforms and housekeeping.
--revreverse--sort name|size|mtime-cpurge all--expire HOURSpurge by mtime (fractional)
Listing
Five list modes; pick the verbosity you need.
-lnames only-Lnames + contents-nnumbered names-Nnumbered + contents-kstack size
Inspection
Read partial content or metadata without consuming the file.
--head N [LINES]--tail N [LINES]--wc N,--size N--path N— raw FS path for$(...)substitution-I N— metadata block
Search & transform
In-place edits and cross-stack search.
-g PAT— grep all tempfiles (exit 0/1)--replace N PAT REPL— in-place find/replace, prints replacement count
Combine & compare
Treat the stack as a working set; combine or diff entries.
-C a b c— concat to stdout in given order-D a b— unified diff of two tempfiles
Tagging
Optional string aliases. Travel with files through every mutation.
-w NAME— tag on creation-R src NAME— rename- Names display as
@namein listings
Editor
Direct handoff to $EDITOR.
-e N— open INDEX in$EDITOR- Fallback:
vi - Works with
-1for top of stack
!TEST POSTURE
Test suites cover three layers: argument parsing, in-memory state invariants, and end-to-end CLI behavior. Aggregate: 5,644 tests pass in cargo test, all green in CI.
| Suite | File | Tests | Coverage |
|---|---|---|---|
| CLI parsing | src/model/opts.rs | 4,449 | Combinatorial — every flag, long + short, value validation, mutual exclusion |
| Stack permutations | src/model/apply_permutation_tests.rs | 162 | Move/swap/sort/reverse correctness across every starting permutation |
| State invariants | src/model/state/tests.rs | 181 | Serialization round-trips, name uniqueness, malformed-record recovery |
| Integration | tests/integration.rs | 530 | End-to-end — spawn binary, pipe stdin, assert stdout/stderr/exit/files |
| Inline (consts + utils + app) | src/util + src/model/app.rs | 267 | String contracts, stack op smoke tests, master-record I/O, flock |
| Edge cases | tests/edge_cases.rs | 23 | Malformed input, overflow, exotic UTF-8 boundary conditions |
| Contract pins | tests/contract_*.rs + tag/master/expire | 32 | Master-record I/O, stack-algebra, @name invariants, parallel-array alignment, --expire input validation |
| TOTAL | cargo test | 5,644 | Wall-clock: ~3.4s (lib 0.35s + integration 3.01s); CI gate on every push to main |
#SHIP SURFACE
What you get when you cargo install temprs or unpack a release artifact.
Binaries
tp — short form. temprs — long form. Identical builds from one Cargo target; LTO + panic=abort + strip=true + codegen-units=1 for release.
zsh completion
completions/_temprs — dynamic resolution of stack indices, filenames, and @name tags. Tab-completion sees the live stack, not a static word list.
man pages
Two groff manuals under man/man1/: temprs.1 (320 lines, short reference, man temprs) and temprsall.1 (653 lines, full manual, man temprsall). Install to a directory on $MANPATH.
Library crate
temprs is also published as a library (src/lib.rs) — downstream crates can use temprs::* for state, dispatch, and stack ops without forking the binary.
CI workflow
.github/workflows/ci.yml runs check + test + fmt + clippy + doc + release build on every push to main and every pull request. Runnable manually via workflow dispatch.
Benchmarks
benches/benchmarks.rs — criterion harness over stack ops and master-record round-trips; run with cargo bench.
vVERSION HISTORY
Current crate: temprs 2.9.12. 297 commits on main. Last five commits:
| Hash | Subject |
|---|---|
| 56ad570ce8 | fix(clippy): use clear() instead of truncate(0) (manual_clear, rust 1.97) |
| 1bf770c36e | docs(hud): add crimson/toxic/vapor color schemes (5 -> 8) |
| b0ce670673 | cargo: author -> MenkeTechnologies |
| 63c0279409 | cargo: author -> MenkeTechnologies (company attribution, transferable) |
| 5f2d33355d | cli: --help footer copyright -> (c) MenkeTechnologies |
Full history: github.com/MenkeTechnologies/temprs/commits/main
>>> JACK IN. PUSH YOUR DATA. OWN YOUR TEMP FILES. <<<