adaptive_pipeline_bootstrap/cli/
parser.rs

1// /////////////////////////////////////////////////////////////////////////////
2// Adaptive Pipeline
3// Copyright (c) 2025 Michael Gardner, A Bit of Help, Inc.
4// SPDX-License-Identifier: BSD-3-Clause
5// See LICENSE file in the project root.
6// /////////////////////////////////////////////////////////////////////////////
7
8//! # CLI Parser
9//!
10//! Command-line interface parsing using clap.
11//!
12//! This module defines the CLI structure and handles argument parsing.
13//! Security validation happens in the validator module after parsing.
14
15use clap::{Parser, Subcommand};
16use std::path::PathBuf;
17
18/// Main CLI structure
19#[derive(Parser, Debug, Clone)]
20#[command(name = "pipeline")]
21#[command(about = concat!("Adaptive Pipeline v", env!("CARGO_PKG_VERSION")))]
22#[command(version)]
23pub struct Cli {
24    #[command(subcommand)]
25    pub command: Commands,
26
27    /// Enable verbose logging
28    #[arg(short, long)]
29    pub verbose: bool,
30
31    /// Configuration file path
32    #[arg(short, long)]
33    pub config: Option<PathBuf>,
34
35    // === Resource Configuration Flags ===
36    // Educational: These flags control the GlobalResourceManager's token allocation
37    // for CPU-bound and I/O-bound operations.
38    /// Override CPU worker thread count
39    ///
40    /// Controls the number of concurrent CPU-bound operations (compression,
41    /// encryption). Default: num_cpus - 1 (reserves 1 core for I/O and
42    /// coordination)
43    ///
44    /// Educational: Setting this too high causes thrashing, too low wastes
45    /// cores. Monitor CPU saturation metrics to tune appropriately.
46    #[arg(long)]
47    pub cpu_threads: Option<usize>,
48
49    /// Override I/O worker thread count
50    ///
51    /// Controls the number of concurrent I/O operations (file reads/writes).
52    /// Default: Device-specific (NVMe: 24, SSD: 12, HDD: 4)
53    ///
54    /// Educational: This should match your storage device's queue depth for
55    /// optimal throughput. Check --storage-type if auto-detection is
56    /// incorrect.
57    #[arg(long)]
58    pub io_threads: Option<usize>,
59
60    /// Specify storage device type for I/O optimization
61    ///
62    /// Affects default I/O thread count if --io-threads not specified.
63    /// Values: nvme (queue depth 24), ssd (12), hdd (4)
64    /// Default: auto-detect based on filesystem characteristics
65    ///
66    /// Educational: Different storage devices have different optimal queue
67    /// depths. NVMe handles more concurrent I/O than SSD, which handles
68    /// more than HDD.
69    #[arg(long, value_parser = parse_storage_type)]
70    pub storage_type: Option<String>,
71
72    /// Channel depth for pipeline stages (Reader → Workers → Writer)
73    ///
74    /// Controls backpressure in the three-stage pipeline architecture.
75    /// Default: 4
76    ///
77    /// Educational: Lower values reduce memory usage but may cause stalls.
78    /// Higher values increase buffering but consume more memory.
79    /// Optimal value depends on chunk processing time and I/O latency.
80    ///
81    /// Example: If chunk processing = 2ms and I/O = 1ms, depth=4 keeps pipeline
82    /// full.
83    #[arg(long, default_value = "4")]
84    pub channel_depth: usize,
85}
86
87/// CLI subcommands
88#[derive(Subcommand, Debug, Clone)]
89pub enum Commands {
90    /// Process a file through a pipeline
91    Process {
92        /// Input file path
93        #[arg(short, long)]
94        input: PathBuf,
95
96        /// Output file path
97        #[arg(short, long)]
98        output: PathBuf,
99
100        /// Pipeline name or ID
101        #[arg(short, long)]
102        pipeline: String,
103
104        /// Chunk size in MB
105        #[arg(long)]
106        chunk_size_mb: Option<usize>,
107
108        /// Number of parallel workers
109        #[arg(long)]
110        workers: Option<usize>,
111    },
112
113    /// Create a new pipeline
114    Create {
115        /// Pipeline name
116        #[arg(short, long)]
117        name: String,
118
119        /// Pipeline stages (comma-separated: compression,encryption,integrity)
120        #[arg(short, long)]
121        stages: String,
122
123        /// Save pipeline to file
124        #[arg(short, long)]
125        output: Option<PathBuf>,
126    },
127
128    /// List available pipelines
129    List,
130
131    /// Show pipeline details
132    Show {
133        /// Pipeline name
134        pipeline: String,
135    },
136
137    /// Delete a pipeline
138    Delete {
139        /// Pipeline name to delete
140        pipeline: String,
141
142        /// Skip confirmation prompt
143        #[arg(long)]
144        force: bool,
145    },
146
147    /// Benchmark system performance
148    Benchmark {
149        /// Test file path
150        #[arg(short, long)]
151        file: Option<PathBuf>,
152
153        /// Test data size in MB
154        #[arg(long, default_value = "100")]
155        size_mb: usize,
156
157        /// Number of iterations
158        #[arg(long, default_value = "3")]
159        iterations: usize,
160    },
161
162    /// Validate pipeline configuration
163    Validate {
164        /// Pipeline configuration file
165        config: PathBuf,
166    },
167
168    /// Validate .adapipe processed file
169    ValidateFile {
170        /// .adapipe file to validate
171        #[arg(short, long)]
172        file: PathBuf,
173
174        /// Perform full streaming validation (decrypt/decompress and verify
175        /// checksum)
176        #[arg(long)]
177        full: bool,
178    },
179
180    /// Restore original file from .adapipe file
181    Restore {
182        /// .adapipe file to restore from
183        #[arg(short, long)]
184        input: PathBuf,
185
186        /// Output directory for restored file (optional - uses original
187        /// directory if not specified)
188        #[arg(short, long)]
189        output_dir: Option<PathBuf>,
190
191        /// Create directories without prompting
192        #[arg(long)]
193        mkdir: bool,
194
195        /// Overwrite existing files without prompting
196        #[arg(long)]
197        overwrite: bool,
198    },
199
200    /// Compare original file against .adapipe file
201    Compare {
202        /// Original file to compare
203        #[arg(short, long)]
204        original: PathBuf,
205
206        /// .adapipe file to compare against
207        #[arg(short, long)]
208        adapipe: PathBuf,
209
210        /// Show detailed differences
211        #[arg(long)]
212        detailed: bool,
213    },
214}
215
216/// Parse and validate storage type from CLI argument
217///
218/// Educational: Custom value parser for clap that validates
219/// storage type strings and provides helpful error messages.
220fn parse_storage_type(s: &str) -> Result<String, String> {
221    match s.to_lowercase().as_str() {
222        "nvme" | "ssd" | "hdd" => Ok(s.to_lowercase()),
223        _ => Err(format!("Invalid storage type '{}'. Valid options: nvme, ssd, hdd", s)),
224    }
225}
226
227/// Parse CLI arguments
228///
229/// This is the entry point for CLI parsing. It uses clap to parse
230/// arguments and returns the parsed CLI structure.
231///
232/// # Returns
233///
234/// Parsed `Cli` structure with all arguments
235///
236/// # Panics
237///
238/// Clap will exit the process with appropriate error message if parsing fails
239pub fn parse_cli() -> Cli {
240    Cli::parse()
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_parse_storage_type_valid() {
249        assert_eq!(parse_storage_type("nvme").unwrap(), "nvme");
250        assert_eq!(parse_storage_type("SSD").unwrap(), "ssd");
251        assert_eq!(parse_storage_type("HDD").unwrap(), "hdd");
252    }
253
254    #[test]
255    fn test_parse_storage_type_invalid() {
256        assert!(parse_storage_type("invalid").is_err());
257        assert!(parse_storage_type("usb").is_err());
258    }
259}