Skip to main content

stuffr_cli/
cli.rs

1//! The `stuffr` command's argument surface.
2//!
3//! Pulled out of `main.rs` and re-exported from `lib.rs` so an integration
4//! test can ask clap itself — via [`clap::CommandFactory`] — what flags and
5//! subcommands exist, rather than keeping a second, hand-maintained list that
6//! the examples page could silently drift out of sync with. `main.rs` still
7//! owns everything that isn't the argument shape: parsing, dispatch, and the
8//! examples text itself.
9
10use clap::{Parser, Subcommand};
11
12#[derive(Parser)]
13#[command(
14    name = "stuffr",
15    version,
16    about = "Universal compression and archive toolkit"
17)]
18pub struct Cli {
19    #[command(subcommand)]
20    pub command: Option<Command>,
21    /// Print detailed usage examples covering every feature, then exit.
22    #[arg(long)]
23    pub examples: bool,
24}
25
26#[derive(Subcommand)]
27pub enum Command {
28    /// Compress a file, or collect several into an archive.
29    Pack {
30        /// Input paths, or `-` for stdin.
31        ///
32        /// More than one path needs an output that names a CONTAINER (`-o
33        /// bundle.tar`, or `--format tar`): a codec compresses one stream and
34        /// has nowhere to put a second.
35        #[arg(required = true, num_args = 1.., value_name = "PATHS")]
36        paths: Vec<String>,
37        /// Output path. Defaults to INPUT plus the format's extension.
38        #[arg(short, long)]
39        output: Option<String>,
40        /// Use this format instead of inferring one from the output name.
41        #[arg(long)]
42        format: Option<String>,
43        /// Compression level. gzip accepts 0-9.
44        #[arg(long)]
45        level: Option<i32>,
46        /// Overwrite an existing output.
47        #[arg(long)]
48        force: bool,
49        /// Skip the fsync that makes the output durable before it is published.
50        #[arg(long)]
51        no_sync: bool,
52        /// Use a weak fallback encoder that this build ships in place of the
53        /// format's real one. Worse ratio, and buffers the whole input.
54        #[arg(long)]
55        allow_weak_encoder: bool,
56        /// Encode with N worker threads. 0 means auto-detect.
57        ///
58        /// OMITTING this flag is not the same as passing 0: without it, stuffr
59        /// encodes single-threaded, so the same input always produces the same
60        /// bytes on any machine. Multi-threaded xz and zstd split the input
61        /// per worker, so their output depends on the worker count.
62        #[arg(long)]
63        threads: Option<usize>,
64        /// Use the full detected CPU budget, uncapped.
65        ///
66        /// Lifts the CPU cap only — `--memory-limit` still binds. Turbo means
67        /// "use my cores", not "ignore the OOM killer".
68        #[arg(long)]
69        turbo: bool,
70        /// Cap the memory stuffr will ask for, e.g. 512M or 2G.
71        ///
72        /// On encode this bounds the worker count. Defaults to 25% of
73        /// available RAM, honouring cgroup limits.
74        #[arg(long, value_name = "SIZE")]
75        memory_limit: Option<String>,
76        /// Fail (exit 4) if anything was approximated or lost.
77        ///
78        /// On the pack side that means something the walk met and could not
79        /// store — a socket, an undecodable name, an unreadable directory —
80        /// or an entry shape this container has none of, such as a directory
81        /// or a symlink in an `ar`.
82        #[arg(long)]
83        strict_fidelity: bool,
84    },
85    /// Decompress a file, or extract an archive's entries.
86    Unpack {
87        /// Input path, or `-` for stdin.
88        input: String,
89        /// Entries to extract; every entry when none are given.
90        ///
91        /// An exact entry name, or a directory selecting everything beneath
92        /// it. Needs -C, which is what asks for entry-aware extraction.
93        #[arg(value_name = "PATTERNS")]
94        patterns: Vec<String>,
95        /// Extract the entry at this position instead of naming it. Repeatable.
96        ///
97        /// 0-BASED, counting in archive order — the same number `stuffr list`
98        /// prints in its first column. This is how you reach an entry whose
99        /// name you cannot type: two entries sharing a name in a zip's index,
100        /// or `Makefile` and `makefile` from a case-sensitive filesystem on a
101        /// case-insensitive one. Needs -C, like PATTERNS, and cannot be
102        /// combined with them — they are two ways of saying the same thing.
103        #[arg(long, value_name = "N")]
104        index: Vec<usize>,
105        /// Extract the archive's entries into this directory.
106        ///
107        /// Absolute entry paths, `..` traversal and symlinks escaping this
108        /// directory are refused (exit 7), never silently sanitised.
109        #[arg(short = 'C', long, value_name = "DIR")]
110        directory: Option<String>,
111        /// Fail (exit 4) if anything was approximated or lost.
112        ///
113        /// On extraction that means an entry whose mode or mtime could not be
114        /// restored, or one skipped for having no shape on disk.
115        #[arg(long)]
116        strict_fidelity: bool,
117        /// Output path. Defaults to INPUT with its extension removed.
118        #[arg(short, long)]
119        output: Option<String>,
120        /// Overwrite an existing output.
121        #[arg(long)]
122        force: bool,
123        /// Use this format instead of detecting one. Required for formats with
124        /// no magic bytes and no extension.
125        #[arg(long)]
126        format: Option<String>,
127        /// Refuse a decode expanding by more than this ratio.
128        #[arg(long)]
129        max_ratio: Option<u64>,
130        /// Skip the fsync that makes the output durable before it is published.
131        #[arg(long)]
132        no_sync: bool,
133        /// Cap the memory stuffr will ask for, e.g. 512M or 2G.
134        ///
135        /// Defaults to 25% of available RAM, honouring cgroup limits.
136        #[arg(long, value_name = "SIZE")]
137        memory_limit: Option<String>,
138    },
139    /// Decompress a file, or stream an archive's entries, to stdout.
140    Cat {
141        /// Input path, or `-` for stdin.
142        input: String,
143        /// Entries whose bytes to stream, in archive order.
144        ///
145        /// An exact entry name, or a directory selecting everything beneath
146        /// it. Naming one is what asks for entry-aware streaming; without
147        /// any, `cat` decodes the input as a single stream.
148        #[arg(value_name = "PATTERNS")]
149        patterns: Vec<String>,
150        /// Stream the entry at this position instead of naming it. Repeatable.
151        ///
152        /// 0-BASED, counting in archive order — the same number `stuffr list`
153        /// prints in its first column. `stuffr cat a.zip --index 6 > out.sh`
154        /// is how you pull out an entry whose name is ambiguous or
155        /// untypeable. Like PATTERNS, naming one asks for entry-aware
156        /// streaming; the two cannot be combined.
157        #[arg(long, value_name = "N")]
158        index: Vec<usize>,
159        /// Use this format instead of detecting one. Required for formats with
160        /// no magic bytes and no extension.
161        #[arg(long)]
162        format: Option<String>,
163        /// Refuse a decode expanding by more than this ratio.
164        #[arg(long)]
165        max_ratio: Option<u64>,
166        /// Cap the memory stuffr will ask for, e.g. 512M or 2G.
167        ///
168        /// Defaults to 25% of available RAM, honouring cgroup limits.
169        #[arg(long, value_name = "SIZE")]
170        memory_limit: Option<String>,
171    },
172    /// Identify a stream without decoding it.
173    Info {
174        /// Input path, or `-` for stdin.
175        input: String,
176        /// Emit machine-readable JSON instead of the human-readable report.
177        #[arg(long)]
178        json: bool,
179        /// Cap the memory stuffr will ask for, e.g. 512M or 2G.
180        ///
181        /// Defaults to 25% of available RAM, honouring cgroup limits.
182        #[arg(long, value_name = "SIZE")]
183        memory_limit: Option<String>,
184    },
185    /// List the formats this build contains.
186    Formats,
187    /// List an archive's entries without extracting.
188    ///
189    /// The first column is the entry's 0-based position in archive order,
190    /// which is what `cat --index` and `unpack --index` select by.
191    #[command(alias = "ls")]
192    List {
193        /// Archive path, or `-` for stdin.
194        input: String,
195        /// Emit machine-readable JSON instead of one entry per line.
196        #[arg(long)]
197        json: bool,
198        /// Refuse a codec layer beneath the container expanding by more
199        /// than this ratio (e.g. a `.tar.gz` bomb).
200        #[arg(long)]
201        max_ratio: Option<u64>,
202        /// Cap the memory stuffr will ask for, e.g. 512M or 2G.
203        ///
204        /// Bounds the codec layer beneath the container, which `--max-ratio`
205        /// cannot see: a pure xz/lzma/lzip decoder sizes its dictionary from
206        /// a value the file declares in its own header, before producing any
207        /// output. Defaults to 25% of available RAM, honouring cgroup limits.
208        #[arg(long, value_name = "SIZE")]
209        memory_limit: Option<String>,
210        /// Fail (exit 4) if the read approximated or lost anything.
211        ///
212        /// A listing can be incomplete — a zip whose index shadows records
213        /// enumerates fewer entries than the archive declares — so `list`
214        /// gates on fidelity for the same reason `test` and `unpack -C` do.
215        #[arg(long)]
216        strict_fidelity: bool,
217    },
218    /// Verify every entry's integrity without extracting.
219    Test {
220        /// Archive path, or `-` for stdin.
221        input: String,
222        /// Refuse a codec layer beneath the container expanding by more
223        /// than this ratio (e.g. a `.tar.gz` bomb).
224        #[arg(long)]
225        max_ratio: Option<u64>,
226        /// Cap the memory stuffr will ask for, e.g. 512M or 2G.
227        ///
228        /// Bounds the codec layer beneath the container, which `--max-ratio`
229        /// cannot see: a pure xz/lzma/lzip decoder sizes its dictionary from
230        /// a value the file declares in its own header, before producing any
231        /// output. Defaults to 25% of available RAM, honouring cgroup limits.
232        #[arg(long, value_name = "SIZE")]
233        memory_limit: Option<String>,
234        /// Fail (exit 4) if the read approximated or lost anything.
235        #[arg(long)]
236        strict_fidelity: bool,
237    },
238}