1use crate::{Result, cli::Commands, cli::OutputMode, config::ConfigService};
2use std::sync::Arc;
3
4pub async fn dispatch_command(
53 command: Commands,
54 config_service: Arc<dyn ConfigService>,
55) -> Result<()> {
56 dispatch_command_with_mode(command, config_service, OutputMode::Text).await
57}
58
59pub async fn dispatch_command_with_mode(
71 command: Commands,
72 config_service: Arc<dyn ConfigService>,
73 output_mode: OutputMode,
74) -> Result<()> {
75 match command {
76 Commands::Match(args) => {
77 crate::commands::match_command::execute_with_config(args, config_service).await
78 }
79 Commands::Convert(args) => {
80 crate::commands::convert_command::execute_with_config(args, config_service).await
81 }
82 Commands::Sync(args) => {
83 crate::commands::sync_command::execute_with_config(args, config_service).await
84 }
85 Commands::Config(args) => {
86 crate::commands::config_command::execute_with_config(args, config_service).await
87 }
88 Commands::GenerateCompletion(args) => run_generate_completion(args, output_mode),
89 Commands::Cache(args) => {
90 crate::commands::cache_command::execute_with_config(args, config_service).await
91 }
92 Commands::Translate(args) => {
93 crate::commands::translate_command::execute_with_config(args, config_service).await
94 }
95 Commands::DetectEncoding(args) => {
96 crate::commands::detect_encoding_command::detect_encoding_command_with_config(
97 args,
98 config_service.as_ref(),
99 )?;
100 Ok(())
101 }
102 }
103}
104
105pub async fn dispatch_command_with_ref(
112 command: Commands,
113 config_service: &dyn ConfigService,
114 output_mode: OutputMode,
115) -> Result<()> {
116 match command {
117 Commands::Match(args) => {
118 args.validate()
119 .map_err(crate::error::SubXError::CommandExecution)?;
120 crate::commands::match_command::execute(args, config_service).await
121 }
122 Commands::Convert(args) => {
123 crate::commands::convert_command::execute(args, config_service).await
124 }
125 Commands::Sync(args) => crate::commands::sync_command::execute(args, config_service).await,
126 Commands::Config(args) => {
127 crate::commands::config_command::execute(args, config_service).await
128 }
129 Commands::GenerateCompletion(args) => run_generate_completion(args, output_mode),
130 Commands::Cache(args) => crate::commands::cache_command::execute(args).await,
131 Commands::Translate(args) => {
132 crate::commands::translate_command::execute(args, config_service).await
133 }
134 Commands::DetectEncoding(args) => {
135 crate::commands::detect_encoding_command::detect_encoding_command_with_config(
136 args,
137 config_service,
138 )?;
139 Ok(())
140 }
141 }
142}
143
144fn run_generate_completion(
153 args: crate::cli::GenerateCompletionArgs,
154 output_mode: OutputMode,
155) -> Result<()> {
156 if output_mode.is_json() {
157 return Err(crate::error::SubXError::OutputModeUnsupported {
158 command: "generate-completion".to_string(),
159 });
160 }
161 let mut cmd = <crate::cli::Cli as clap::CommandFactory>::command();
162 let cmd_name = cmd.get_name().to_string();
163 let mut stdout = std::io::stdout();
164 clap_complete::generate(args.shell, &mut cmd, cmd_name, &mut stdout);
165 Ok(())
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::cli::{
172 CacheAction, CacheArgs, ClearArgs, ClearType, ConfigAction, ConfigArgs, ConvertArgs,
173 DetectEncodingArgs, GenerateCompletionArgs, MatchArgs, OutputSubtitleFormat, StatusArgs,
174 SyncArgs,
175 };
176 use crate::config::TestConfigService;
177 use clap_complete::Shell;
178
179 fn is_expected_test_error(e: &crate::error::SubXError) -> bool {
181 let msg = format!("{e:?}");
182 msg.contains("NotFound")
183 || msg.contains("No subtitle files found")
184 || msg.contains("No video files found")
185 || msg.contains("Config")
186 || msg.contains("no such file")
187 || msg.contains("cannot find")
188 || msg.contains("No input")
189 || msg.contains("No files")
190 || msg.contains("FileNotFound")
191 || msg.contains("IoError")
192 || msg.contains("PathNotFound")
193 || msg.contains("InvalidInput")
194 || msg.contains("CommandExecution")
195 || msg.contains("NoInputSpecified")
196 }
197
198 fn make_match_args_dry_run() -> MatchArgs {
199 MatchArgs {
200 path: Some("/nonexistent_subx_test_path".into()),
201 input_paths: vec![],
202 dry_run: true,
203 confidence: 80,
204 recursive: false,
205 backup: false,
206 copy: false,
207 move_files: false,
208 no_extract: false,
209 }
210 }
211
212 fn make_convert_args() -> ConvertArgs {
213 ConvertArgs {
214 input: Some("/nonexistent_subx_test_path".into()),
215 input_paths: vec![],
216 recursive: false,
217 format: Some(OutputSubtitleFormat::Srt),
218 output: None,
219 keep_original: false,
220 encoding: "utf-8".to_string(),
221 no_extract: false,
222 }
223 }
224
225 fn make_sync_args() -> SyncArgs {
226 SyncArgs {
227 positional_paths: vec![],
228 video: None,
229 subtitle: None,
230 input_paths: vec![],
231 recursive: false,
232 offset: Some(1.0),
233 method: None,
234 window: 30,
235 vad_sensitivity: None,
236 output: None,
237 verbose: false,
238 dry_run: true,
239 force: false,
240 batch: None,
241 no_extract: false,
242 }
243 }
244
245 fn make_config_args_list() -> ConfigArgs {
246 ConfigArgs {
247 action: ConfigAction::List,
248 }
249 }
250
251 fn make_generate_completion_args() -> GenerateCompletionArgs {
252 GenerateCompletionArgs { shell: Shell::Bash }
253 }
254
255 fn make_cache_status_args() -> CacheArgs {
256 CacheArgs {
257 action: CacheAction::Status(StatusArgs { json: false }),
258 }
259 }
260
261 fn make_cache_clear_args() -> CacheArgs {
262 CacheArgs {
263 action: CacheAction::Clear(ClearArgs {
264 r#type: ClearType::All,
265 }),
266 }
267 }
268
269 fn make_detect_encoding_args() -> DetectEncodingArgs {
270 DetectEncodingArgs {
271 verbose: false,
272 input_paths: vec![],
273 recursive: false,
274 file_paths: vec![],
275 no_extract: false,
276 }
277 }
278
279 #[tokio::test]
282 async fn test_dispatch_match_command() {
283 let config_service = Arc::new(TestConfigService::with_ai_settings(
284 "test_provider",
285 "test_model",
286 ));
287 let result =
288 dispatch_command(Commands::Match(make_match_args_dry_run()), config_service).await;
289 match result {
290 Ok(_) => {}
291 Err(e) => assert!(is_expected_test_error(&e), "Unexpected error: {e:?}"),
292 }
293 }
294
295 #[tokio::test]
296 async fn test_dispatch_convert_command() {
297 let config_service = Arc::new(TestConfigService::with_defaults());
298 let _result =
300 dispatch_command(Commands::Convert(make_convert_args()), config_service).await;
301 }
302
303 #[tokio::test]
304 async fn test_dispatch_sync_command() {
305 let config_service = Arc::new(TestConfigService::with_defaults());
306 let result = dispatch_command(Commands::Sync(make_sync_args()), config_service).await;
307 match result {
308 Ok(_) => {}
309 Err(e) => assert!(is_expected_test_error(&e), "Unexpected error: {e:?}"),
310 }
311 }
312
313 #[tokio::test]
314 async fn test_dispatch_config_list_command() {
315 let config_service = Arc::new(TestConfigService::with_defaults());
316 let result =
317 dispatch_command(Commands::Config(make_config_args_list()), config_service).await;
318 match result {
320 Ok(_) => {}
321 Err(e) => assert!(is_expected_test_error(&e), "Unexpected error: {e:?}"),
322 }
323 }
324
325 #[tokio::test]
326 async fn test_dispatch_generate_completion_command() {
327 let config_service = Arc::new(TestConfigService::with_defaults());
328 let result = dispatch_command(
330 Commands::GenerateCompletion(make_generate_completion_args()),
331 config_service,
332 )
333 .await;
334 assert!(
335 result.is_ok(),
336 "GenerateCompletion should succeed: {result:?}"
337 );
338 }
339
340 #[tokio::test]
341 async fn test_dispatch_cache_status_command() {
342 let config_service = Arc::new(TestConfigService::with_defaults());
343 let _result =
344 dispatch_command(Commands::Cache(make_cache_status_args()), config_service).await;
345 }
346
347 #[tokio::test]
348 async fn test_dispatch_cache_clear_command() {
349 let config_service = Arc::new(TestConfigService::with_defaults());
350 let _result =
351 dispatch_command(Commands::Cache(make_cache_clear_args()), config_service).await;
352 }
353
354 #[tokio::test]
355 async fn test_dispatch_detect_encoding_command() {
356 let config_service = Arc::new(TestConfigService::with_defaults());
357 let result = dispatch_command(
358 Commands::DetectEncoding(make_detect_encoding_args()),
359 config_service,
360 )
361 .await;
362 match result {
363 Ok(_) => {}
364 Err(e) => assert!(is_expected_test_error(&e), "Unexpected error: {e:?}"),
365 }
366 }
367
368 #[tokio::test]
371 async fn test_dispatch_with_ref_match_command() {
372 let config_service = TestConfigService::with_ai_settings("test_provider", "test_model");
373 let result = dispatch_command_with_ref(
374 Commands::Match(make_match_args_dry_run()),
375 &config_service,
376 OutputMode::Text,
377 )
378 .await;
379 match result {
380 Ok(_) => {}
381 Err(e) => assert!(is_expected_test_error(&e), "Unexpected error: {e:?}"),
382 }
383 }
384
385 #[tokio::test]
386 async fn test_dispatch_with_ref_match_validation_error() {
387 let config_service = TestConfigService::with_defaults();
389 let args = MatchArgs {
390 path: Some("/nonexistent_subx_test_path".into()),
391 input_paths: vec![],
392 dry_run: true,
393 confidence: 80,
394 recursive: false,
395 backup: false,
396 copy: true,
397 move_files: true,
398 no_extract: false,
399 };
400 let result =
401 dispatch_command_with_ref(Commands::Match(args), &config_service, OutputMode::Text)
402 .await;
403 assert!(result.is_err(), "Expected validation error for copy+move");
404 let msg = format!("{:?}", result.unwrap_err());
405 assert!(
406 msg.contains("CommandExecution") || msg.contains("copy") || msg.contains("move"),
407 "Error should mention the conflicting flags: {msg}"
408 );
409 }
410
411 #[tokio::test]
412 async fn test_dispatch_with_ref_convert_command() {
413 let config_service = TestConfigService::with_defaults();
414 let _result = dispatch_command_with_ref(
415 Commands::Convert(make_convert_args()),
416 &config_service,
417 OutputMode::Text,
418 )
419 .await;
420 }
421
422 #[tokio::test]
423 async fn test_dispatch_with_ref_sync_command() {
424 let config_service = TestConfigService::with_defaults();
425 let result = dispatch_command_with_ref(
426 Commands::Sync(make_sync_args()),
427 &config_service,
428 OutputMode::Text,
429 )
430 .await;
431 match result {
432 Ok(_) => {}
433 Err(e) => assert!(is_expected_test_error(&e), "Unexpected error: {e:?}"),
434 }
435 }
436
437 #[tokio::test]
438 async fn test_dispatch_with_ref_config_list_command() {
439 let config_service = TestConfigService::with_defaults();
440 let result = dispatch_command_with_ref(
441 Commands::Config(make_config_args_list()),
442 &config_service,
443 OutputMode::Text,
444 )
445 .await;
446 match result {
447 Ok(_) => {}
448 Err(e) => assert!(is_expected_test_error(&e), "Unexpected error: {e:?}"),
449 }
450 }
451
452 #[tokio::test]
453 async fn test_dispatch_with_ref_generate_completion_command() {
454 let config_service = TestConfigService::with_defaults();
455 let result = dispatch_command_with_ref(
456 Commands::GenerateCompletion(make_generate_completion_args()),
457 &config_service,
458 OutputMode::Text,
459 )
460 .await;
461 assert!(
462 result.is_ok(),
463 "GenerateCompletion should succeed: {result:?}"
464 );
465 }
466
467 #[tokio::test]
468 async fn test_dispatch_with_ref_cache_status_command() {
469 let config_service = TestConfigService::with_defaults();
470 let _result = dispatch_command_with_ref(
471 Commands::Cache(make_cache_status_args()),
472 &config_service,
473 OutputMode::Text,
474 )
475 .await;
476 }
477
478 #[tokio::test]
479 async fn test_dispatch_with_ref_cache_clear_command() {
480 let config_service = TestConfigService::with_defaults();
481 let _result = dispatch_command_with_ref(
482 Commands::Cache(make_cache_clear_args()),
483 &config_service,
484 OutputMode::Text,
485 )
486 .await;
487 }
488
489 #[tokio::test]
490 async fn test_dispatch_with_ref_detect_encoding_command() {
491 let config_service = TestConfigService::with_defaults();
492 let result = dispatch_command_with_ref(
493 Commands::DetectEncoding(make_detect_encoding_args()),
494 &config_service,
495 OutputMode::Text,
496 )
497 .await;
498 match result {
499 Ok(_) => {}
500 Err(e) => assert!(is_expected_test_error(&e), "Unexpected error: {e:?}"),
501 }
502 }
503
504 #[tokio::test]
507 async fn test_dispatch_config_get_command() {
508 let config_service = Arc::new(TestConfigService::with_defaults());
509 let args = ConfigArgs {
510 action: ConfigAction::Get {
511 key: "ai.provider".to_string(),
512 },
513 };
514 let result = dispatch_command(Commands::Config(args), config_service).await;
515 match result {
516 Ok(_) => {}
517 Err(e) => assert!(is_expected_test_error(&e), "Unexpected error: {e:?}"),
518 }
519 }
520
521 #[tokio::test]
522 async fn test_dispatch_config_set_command() {
523 let config_service = Arc::new(TestConfigService::with_defaults());
524 let args = ConfigArgs {
525 action: ConfigAction::Set {
526 key: "ai.provider".to_string(),
527 value: "openai".to_string(),
528 },
529 };
530 let result = dispatch_command(Commands::Config(args), config_service).await;
531 match result {
532 Ok(_) => {}
533 Err(e) => assert!(is_expected_test_error(&e), "Unexpected error: {e:?}"),
534 }
535 }
536
537 #[tokio::test]
538 async fn test_dispatch_generate_completion_zsh() {
539 let config_service = Arc::new(TestConfigService::with_defaults());
540 let result = dispatch_command(
541 Commands::GenerateCompletion(GenerateCompletionArgs { shell: Shell::Zsh }),
542 config_service,
543 )
544 .await;
545 assert!(result.is_ok(), "Zsh completion should succeed: {result:?}");
546 }
547
548 #[tokio::test]
549 async fn test_dispatch_generate_completion_fish() {
550 let config_service = Arc::new(TestConfigService::with_defaults());
551 let result = dispatch_command(
552 Commands::GenerateCompletion(GenerateCompletionArgs { shell: Shell::Fish }),
553 config_service,
554 )
555 .await;
556 assert!(result.is_ok(), "Fish completion should succeed: {result:?}");
557 }
558}