subx_cli/cli/
match_args.rs

1#![allow(clippy::needless_borrows_for_generic_args)]
2//! Command-line arguments for the AI-powered subtitle matching command.
3
4use crate::cli::InputPathHandler;
5use crate::error::SubXError;
6use clap::Args;
7use std::path::PathBuf;
8
9/// Arguments for AI-powered subtitle file matching and renaming.
10#[derive(Args, Debug)]
11pub struct MatchArgs {
12    /// Target directory path containing video and subtitle files
13    pub path: Option<PathBuf>,
14
15    /// Specify file or directory paths to process (new parameter), can be used multiple times
16    #[arg(short = 'i', long = "input", value_name = "PATH")]
17    pub input_paths: Vec<PathBuf>,
18
19    /// Enable dry-run mode to preview operations without making changes
20    #[arg(long)]
21    pub dry_run: bool,
22
23    /// Minimum confidence threshold for file matching (0-100)
24    #[arg(long, default_value = "80", value_parser = clap::value_parser!(u8).range(0..=100))]
25    pub confidence: u8,
26
27    /// Recursively process subdirectories
28    #[arg(short, long)]
29    pub recursive: bool,
30
31    /// Create backup copies of original files before renaming
32    #[arg(long)]
33    pub backup: bool,
34
35    /// Copy matched subtitle files to the same folder as their corresponding video files
36    #[arg(long, short = 'c')]
37    pub copy: bool,
38
39    /// Move matched subtitle files to the same folder as their corresponding video files
40    #[arg(long = "move", short = 'm')]
41    pub move_files: bool,
42}
43
44impl MatchArgs {
45    /// Validate that copy and move arguments are not used together
46    pub fn validate(&self) -> Result<(), String> {
47        if self.copy && self.move_files {
48            return Err(
49                "Cannot use --copy and --move together. Please choose one operation mode."
50                    .to_string(),
51            );
52        }
53        Ok(())
54    }
55
56    /// Get all input paths, combining path and input_paths parameters
57    pub fn get_input_handler(&self) -> Result<InputPathHandler, SubXError> {
58        let optional_paths = vec![self.path.clone()];
59        let merged_paths = InputPathHandler::merge_paths_from_multiple_sources(
60            &optional_paths,
61            &self.input_paths,
62            &[],
63        )?;
64
65        Ok(InputPathHandler::from_args(&merged_paths, self.recursive)?
66            .with_extensions(&["mp4", "mkv", "avi", "mov", "srt", "ass", "vtt", "sub"]))
67    }
68}
69
70// Test parameter parsing behavior
71#[cfg(test)]
72mod tests {
73    use crate::cli::{Cli, Commands};
74    use clap::Parser;
75    use std::path::PathBuf;
76
77    #[test]
78    fn test_match_args_default_values() {
79        let cli = Cli::try_parse_from(&["subx-cli", "match", "path"]).unwrap();
80        let args = match cli.command {
81            Commands::Match(m) => m,
82            _ => panic!("Expected Match command"),
83        };
84        assert_eq!(args.path, Some(PathBuf::from("path")));
85        assert!(args.input_paths.is_empty());
86        assert!(!args.dry_run);
87        assert!(!args.recursive);
88        assert!(!args.backup);
89        assert_eq!(args.confidence, 80);
90    }
91
92    #[test]
93    fn test_match_args_parsing() {
94        let cli = Cli::try_parse_from(&[
95            "subx-cli",
96            "match",
97            "path",
98            "--dry-run",
99            "--recursive",
100            "--backup",
101            "--confidence",
102            "50",
103        ])
104        .unwrap();
105        let args = match cli.command {
106            Commands::Match(m) => m,
107            _ => panic!("Expected Match command"),
108        };
109        assert_eq!(args.path, Some(PathBuf::from("path")));
110        assert!(args.input_paths.is_empty());
111        assert!(args.dry_run);
112        assert!(args.recursive);
113        assert!(args.backup);
114        assert_eq!(args.confidence, 50);
115    }
116
117    #[test]
118    fn test_match_args_invalid_confidence() {
119        let res = Cli::try_parse_from(&["subx-cli", "match", "path", "--confidence", "150"]);
120        assert!(res.is_err());
121    }
122
123    #[test]
124    fn test_match_args_copy_and_move_mutually_exclusive() {
125        let cli = Cli::try_parse_from(&["subx-cli", "match", "path", "--copy", "--move"]).unwrap();
126        let args = match cli.command {
127            Commands::Match(m) => m,
128            _ => panic!("Expected Match command"),
129        };
130        let result = args.validate();
131        assert!(result.is_err());
132        assert!(
133            result
134                .unwrap_err()
135                .contains("Cannot use --copy and --move together")
136        );
137    }
138
139    #[test]
140    fn test_match_args_copy_parameter() {
141        let cli = Cli::try_parse_from(&["subx-cli", "match", "path", "--copy"]).unwrap();
142        let args = match cli.command {
143            Commands::Match(m) => m,
144            _ => panic!("Expected Match command"),
145        };
146        assert!(args.copy);
147        assert!(!args.move_files);
148        assert!(args.validate().is_ok());
149    }
150
151    #[test]
152    fn test_match_args_move_parameter() {
153        let cli = Cli::try_parse_from(&["subx-cli", "match", "path", "--move"]).unwrap();
154        let args = match cli.command {
155            Commands::Match(m) => m,
156            _ => panic!("Expected Match command"),
157        };
158        assert!(!args.copy);
159        assert!(args.move_files);
160        assert!(args.validate().is_ok());
161    }
162
163    #[test]
164    fn test_match_args_input_paths() {
165        let cli = Cli::try_parse_from(&[
166            "subx-cli",
167            "match",
168            "-i",
169            "dir1",
170            "-i",
171            "dir2",
172            "--recursive",
173        ])
174        .unwrap();
175        let args = match cli.command {
176            Commands::Match(m) => m,
177            _ => panic!("Expected Match command"),
178        };
179        assert!(args.path.is_none());
180        assert_eq!(
181            args.input_paths,
182            vec![PathBuf::from("dir1"), PathBuf::from("dir2")]
183        );
184        assert!(args.recursive);
185    }
186}