Skip to main content

hexz_cli/
args.rs

1#![allow(missing_docs)]
2
3use clap::{Parser, Subcommand};
4use std::path::PathBuf;
5
6/// High-performance, deduplicated data archives
7#[derive(Debug, Parser)]
8#[command(name = "hexz", version, about = "High-performance, deduplicated data archives", long_about = None)]
9#[command(disable_help_flag = true)]
10#[command(styles = get_styles())]
11pub struct Cli {
12    /// Print help information.
13    #[arg(short, long, action = clap::ArgAction::SetTrue)]
14    pub help: bool,
15
16    /// Subcommand to execute.
17    #[command(subcommand)]
18    pub command: Option<Commands>,
19}
20
21fn get_styles() -> clap::builder::Styles {
22    use clap::builder::styling::{AnsiColor, Effects, Styles};
23    Styles::styled()
24        .header(AnsiColor::Yellow.on_default() | Effects::BOLD)
25        .usage(AnsiColor::Green.on_default() | Effects::BOLD)
26        .literal(AnsiColor::Cyan.on_default() | Effects::BOLD)
27        .placeholder(AnsiColor::Cyan.on_default())
28}
29
30#[derive(Subcommand, Debug, Clone)]
31pub enum RemoteCommand {
32    /// Add a new remote
33    Add {
34        /// Name of the remote.
35        name: String,
36        /// URL of the remote endpoint.
37        url: String,
38    },
39    /// Remove a remote
40    Remove {
41        /// Name of the remote to remove.
42        name: String,
43    },
44    /// List all remotes
45    List,
46}
47
48#[derive(Debug, Subcommand)]
49pub enum Commands {
50    // ------------------------------------------------------------------------
51    // Archive Operations
52    // ------------------------------------------------------------------------
53    /// Pack a file or directory into a deduplicated archive
54    #[command(display_order = 1)]
55    #[command(
56        long_about = "Creates a compressed and deduplicated archive (.hxz). Uses Content-Defined Chunking (CDC) to identify shared blocks across versions."
57    )]
58    #[command(after_help = "hexz pack ./folder data.hxz --compression zstd")]
59    Pack {
60        /// Path to input data
61        input: PathBuf,
62
63        /// Output archive path (.hxz)
64        output: PathBuf,
65
66        /// Base archive to diff against
67        #[arg(long, short)]
68        base: Option<PathBuf>,
69
70        /// Compression algorithm (lz4, zstd, none)
71        #[arg(long, default_value = "lz4")]
72        compression: String,
73
74        /// Enable encryption
75        #[arg(long)]
76        encrypt: bool,
77
78        /// Block size in bytes
79        #[arg(long, default_value_t = 65536, value_parser = clap::value_parser!(u32).range(1..))]
80        block_size: u32,
81
82        /// Number of compression worker threads (0 = auto)
83        #[arg(long)]
84        workers: Option<usize>,
85
86        /// Run adaptive CDC analysis
87        #[arg(long)]
88        dcam: bool,
89
90        /// Run extensive DCAM analysis to find globally optimal parameters (up to 16MB chunks)
91        #[arg(long)]
92        dcam_optimal: bool,
93
94        /// Suppress output
95        #[arg(long, short)]
96        silent: bool,
97    },
98
99    /// Extract an archive back to a file or directory
100    #[command(display_order = 2)]
101    #[command(after_help = "hexz extract data.hxz ./output")]
102    Extract {
103        /// Input .hxz archive
104        input: PathBuf,
105
106        /// Output path
107        output: PathBuf,
108    },
109
110    /// Show archive details and metadata
111    #[command(display_order = 3)]
112    #[command(alias = "inspect")]
113    #[command(after_help = "hexz show ./data.hxz --json")]
114    Show {
115        /// Path to archive
116        snap: PathBuf,
117
118        /// Output as JSON
119        #[arg(long)]
120        json: bool,
121    },
122
123    /// Compare two archives and show storage savings
124    #[command(display_order = 4)]
125    #[command(after_help = "hexz diff v1.hxz v2.hxz")]
126    Diff {
127        /// First archive
128        a: PathBuf,
129
130        /// Second archive
131        b: PathBuf,
132    },
133
134    /// List archives in a directory and show their lineage
135    #[command(display_order = 5)]
136    #[command(alias = "ls")]
137    Log {
138        /// Directory to scan
139        dir: PathBuf,
140    },
141
142    /// Convert external data formats into Hexz archives
143    #[command(display_order = 6)]
144    Convert {
145        /// Input format (tar)
146        format: String,
147
148        /// Input path
149        input: PathBuf,
150
151        /// Output archive path
152        output: PathBuf,
153
154        /// Compression algorithm
155        #[arg(long, default_value = "lz4")]
156        compression: String,
157
158        /// Block size
159        #[arg(long, default_value_t = 65536)]
160        block_size: u32,
161
162        /// Suppress output
163        #[arg(long, short)]
164        silent: bool,
165    },
166
167    /// Predict compression and deduplication potential
168    #[command(display_order = 7)]
169    Predict {
170        /// Path to analyze
171        path: PathBuf,
172
173        /// Block size to test
174        #[arg(long, default_value_t = 65536)]
175        block_size: u32,
176
177        /// Minimum chunk size for CDC
178        #[arg(long)]
179        min_chunk: Option<u32>,
180
181        /// Average chunk size for CDC
182        #[arg(long)]
183        avg_chunk: Option<u32>,
184
185        /// Maximum chunk size for CDC
186        #[arg(long)]
187        max_chunk: Option<u32>,
188
189        /// Output as JSON
190        #[arg(long)]
191        json: bool,
192    },
193
194    // ------------------------------------------------------------------------
195    // Filesystem Operations
196    // ------------------------------------------------------------------------
197    /// Mount an archive as a FUSE filesystem
198    #[cfg(feature = "fuse")]
199    #[command(display_order = 10)]
200    #[command(
201        long_about = "Exposes the archive's content as a read-only filesystem. Only requested blocks are fetched/decompressed on-demand."
202    )]
203    #[command(after_help = "hexz mount data.hxz /mnt/data")]
204    Mount {
205        /// Archive to mount
206        snap: String,
207
208        /// Mount point directory
209        mountpoint: PathBuf,
210
211        /// Optional write layer directory for modifications
212        #[arg(long, short)]
213        overlay: Option<PathBuf>,
214
215        /// Make the mount writable by automatically using a temporary overlay
216        #[arg(long, short = 'e')]
217        editable: bool,
218
219        /// Run as daemon
220        #[arg(short, long)]
221        daemon: bool,
222
223        /// Cache size (e.g., "1G")
224        #[arg(long)]
225        cache_size: Option<String>,
226
227        /// User ID for files
228        #[arg(long, default_value_t = 0)]
229        uid: u32,
230
231        /// Group ID for files
232        #[arg(long, default_value_t = 0)]
233        gid: u32,
234    },
235
236    /// Unmount a previously mounted archive
237    #[cfg(feature = "fuse")]
238    #[command(display_order = 11)]
239    Unmount {
240        /// Mount point to unmount
241        mountpoint: PathBuf,
242    },
243
244    /// Drop into a shell within a mounted archive
245    #[cfg(feature = "fuse")]
246    #[command(display_order = 11)]
247    #[command(
248        long_about = "Mounts the archive to a temporary directory and drops you into a subshell. When the shell exits, the archive is automatically unmounted and the temporary directory is cleaned up."
249    )]
250    #[command(after_help = "hexz shell data.hxz --editable")]
251    Shell {
252        /// Archive to mount
253        snap: String,
254
255        /// Optional write layer directory for modifications
256        #[arg(long, short)]
257        overlay: Option<PathBuf>,
258
259        /// Make the mount writable by automatically using a temporary overlay
260        #[arg(long, short = 'e')]
261        editable: bool,
262
263        /// Cache size (e.g., "1G")
264        #[arg(long)]
265        cache_size: Option<String>,
266    },
267
268    /// Commit changes from a writable mount to a new thin archive
269    #[cfg(feature = "fuse")]
270    #[command(display_order = 12)]
271    #[command(
272        long_about = "Takes a writable mount point and saves the modifications as a new thin archive linked to the original base."
273    )]
274    #[command(after_help = "hexz commit v2.hxz")]
275    Commit {
276        /// Output archive path (.hxz)
277        output: PathBuf,
278
279        /// Mount point directory or workspace path (defaults to current directory)
280        mountpoint: Option<PathBuf>,
281
282        /// Base archive to link against (optional if can be inferred)
283        #[arg(long, short)]
284        base: Option<PathBuf>,
285    },
286
287    /// Initialize a workspace from an archive (Git-style)
288    #[cfg(feature = "fuse")]
289    #[command(display_order = 13)]
290    #[command(alias = "co")]
291    Checkout {
292        /// Archive to use as base
293        archive: PathBuf,
294
295        /// Directory to create the workspace in
296        path: PathBuf,
297    },
298
299    /// Show changes in the current workspace
300    #[cfg(feature = "fuse")]
301    #[command(display_order = 14)]
302    #[command(alias = "st")]
303    Status {
304        /// Workspace path (defaults to current directory)
305        path: Option<PathBuf>,
306    },
307
308    /// Initialize a new empty workspace
309    #[cfg(feature = "fuse")]
310    #[command(display_order = 15)]
311    Init {
312        /// Directory to create the workspace in (defaults to current directory)
313        path: Option<PathBuf>,
314    },
315
316    /// Manage remote endpoints for the workspace
317    #[cfg(feature = "fuse")]
318    #[command(display_order = 16)]
319    Remote {
320        #[command(subcommand)]
321        action: RemoteCommand,
322    },
323
324    /// Push thin archives to a remote endpoint
325    #[cfg(feature = "fuse")]
326    #[command(display_order = 17)]
327    Push {
328        /// Remote name (defaults to "origin")
329        #[arg(default_value = "origin")]
330        remote: String,
331
332        /// Archive to push (defaults to the workspace's base archive)
333        archive: Option<PathBuf>,
334    },
335
336    /// Pull thin archives from a remote endpoint
337    #[cfg(feature = "fuse")]
338    #[command(display_order = 18)]
339    Pull {
340        /// Remote name (defaults to "origin")
341        #[arg(default_value = "origin")]
342        remote: String,
343
344        /// Specific archive to pull (omit to pull all new archives)
345        archive: Option<String>,
346    },
347
348    // ------------------------------------------------------------------------
349    // Networking & Security
350    // ------------------------------------------------------------------------
351    /// Serve an archive over the network (HTTP range requests)
352    #[cfg(feature = "server")]
353    #[command(display_order = 20)]
354    Serve {
355        /// Archive to serve
356        snap: String,
357
358        /// Server port
359        #[arg(long, default_value_t = 8080)]
360        port: u16,
361
362        /// Bind address
363        #[arg(long, default_value = "127.0.0.1")]
364        bind: String,
365
366        /// Run as daemon
367        #[arg(short, long)]
368        daemon: bool,
369    },
370
371    /// Generate an Ed25519 signing keypair
372    #[cfg(feature = "signing")]
373    #[command(display_order = 21)]
374    Keygen {
375        /// Output directory for keys
376        #[arg(short, long)]
377        output_dir: Option<PathBuf>,
378    },
379
380    /// Sign an archive
381    #[cfg(feature = "signing")]
382    #[command(display_order = 22)]
383    Sign {
384        /// Private key path
385        key: PathBuf,
386
387        /// Archive to sign
388        image: PathBuf,
389    },
390
391    /// Verify an archive's signature
392    #[cfg(feature = "signing")]
393    #[command(display_order = 23)]
394    Verify {
395        /// Public key path
396        key: PathBuf,
397
398        /// Archive to verify
399        image: PathBuf,
400    },
401
402    /// Run system health check and diagnostics
403    #[command(display_order = 24)]
404    Doctor,
405}