image_optimizer/cli/
cli_args.rs

1use clap::Parser;
2use std::path::PathBuf;
3
4/// Command-line interface configuration for the image optimizer tool.
5///
6/// This struct defines all available command-line arguments and flags for the image optimization
7/// tool. It uses the `clap` crate for parsing and validation of command-line arguments.
8///
9/// ## Examples
10///
11/// ```rust
12/// use image_optimizer::cli::Cli;
13/// use clap::Parser;
14///
15/// // Parse CLI arguments
16/// let cli = Cli::parse();
17/// ```
18#[derive(Parser)]
19#[command(name = "image-optimizer")]
20#[command(about = "CLI tool for optimizing images (JPEG, PNG, WebP, SVG)")]
21#[command(long_about = None)]
22#[command(version = env!("CARGO_PKG_VERSION"))]
23#[allow(clippy::struct_excessive_bools)]
24pub struct Cli {
25    /// Input directory or file to process
26    #[arg(short, long)]
27    pub input: Option<PathBuf>,
28
29    /// Output directory (if not specified, optimizes in place)
30    #[arg(short, long)]
31    pub output: Option<PathBuf>,
32
33    /// Create backup files (.bak)
34    #[arg(long)]
35    pub backup: bool,
36
37    /// Use lossless compression
38    #[arg(long)]
39    pub webp_lossless: bool,
40
41    /// JPEG quality (1-100), ignored if lossless is set (applies to raster formats only)
42    #[arg(long, default_value = "85")]
43    pub jpeg_quality: u8,
44
45    /// Recursively scan subdirectories
46    #[arg(short, long)]
47    pub recursive: bool,
48
49    /// Maximum size for the longer edge (resizes if larger, applies to raster formats only)
50    #[arg(long)]
51    pub max_size: Option<u32>,
52
53    /// Oxipng optimization level (0-6 or max)
54    #[arg(long, default_value = "2")]
55    pub png_optimization_level: String,
56
57    /// Zopfli iterations for optimization (1-255)
58    #[arg(long, default_value = "15")]
59    pub zopfli_iterations: std::num::NonZeroU8,
60
61    #[arg(long)]
62    pub no_zopfli: bool,
63
64    #[arg(long)]
65    pub no_parallel: bool,
66
67    /// Update to the latest version
68    #[arg(long)]
69    pub update: bool,
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use clap::CommandFactory;
76
77    #[test]
78    fn test_cli_defaults() {
79        let cli = Cli::parse_from(["image-optimizer"]);
80        assert_eq!(cli.input, None);
81        assert_eq!(cli.output, None);
82        assert!(!cli.backup);
83        assert!(!cli.webp_lossless);
84        assert_eq!(cli.jpeg_quality, 85);
85        assert!(!cli.recursive);
86        assert_eq!(cli.max_size, None);
87        assert_eq!(cli.png_optimization_level, "2");
88        assert_eq!(cli.zopfli_iterations.get(), 15);
89        assert!(!cli.update);
90    }
91
92    #[test]
93    fn test_cli_with_input() {
94        let cli = Cli::parse_from(["image-optimizer", "-i", "/path/to/images"]);
95        assert_eq!(cli.input, Some(PathBuf::from("/path/to/images")));
96    }
97
98    #[test]
99    fn test_cli_with_all_flags() {
100        let cli = Cli::parse_from([
101            "image-optimizer",
102            "-i",
103            "/input",
104            "-o",
105            "/output",
106            "--backup",
107            "--webp-lossless",
108            "--jpeg-quality",
109            "90",
110            "--recursive",
111            "--max-size",
112            "1024",
113            "--png-optimization-level",
114            "max",
115            "--zopfli-iterations",
116            "25",
117            "--update",
118        ]);
119
120        assert_eq!(cli.input, Some(PathBuf::from("/input")));
121        assert_eq!(cli.output, Some(PathBuf::from("/output")));
122        assert!(cli.backup);
123        assert!(cli.webp_lossless);
124        assert_eq!(cli.jpeg_quality, 90);
125        assert!(cli.recursive);
126        assert_eq!(cli.max_size, Some(1024));
127        assert_eq!(cli.png_optimization_level, "max");
128        assert_eq!(cli.zopfli_iterations.get(), 25);
129        assert!(cli.update);
130        assert!(!cli.no_zopfli);
131        assert!(!cli.no_parallel);
132    }
133
134    #[test]
135    fn test_cli_quality_bounds() {
136        let cli = Cli::parse_from(["image-optimizer", "--jpeg-quality", "1"]);
137        assert_eq!(cli.jpeg_quality, 1);
138
139        let cli = Cli::parse_from(["image-optimizer", "--jpeg-quality", "100"]);
140        assert_eq!(cli.jpeg_quality, 100);
141    }
142
143    #[test]
144    fn test_cli_help_generation() {
145        let mut cmd = Cli::command();
146        let help = cmd.render_help();
147        assert!(help.to_string().contains("CLI tool for optimizing images"));
148    }
149}