subx_cli/cli/
mod.rs

1//! Command-line interface for the SubX subtitle processing tool.
2//!
3//! This module provides the top-level CLI application structure and subcommands
4//! for AI-powered matching, subtitle format conversion, audio synchronization,
5//! encoding detection, configuration management, cache operations, and shell
6//! completion generation.
7//!
8//! # Architecture
9//!
10//! The CLI is built using `clap` and follows a subcommand pattern:
11//! - `match` - AI-powered subtitle file matching and renaming
12//! - `convert` - Subtitle format conversion between standards
13//! - `sync` - Audio-subtitle synchronization and timing adjustment
14//! - `detect-encoding` - Character encoding detection and conversion
15//! - `config` - Configuration management and inspection
16//! - `cache` - Cache inspection and dry-run management
17//! - `generate-completion` - Shell completion script generation
18//!
19//! # Examples
20//!
21//! ```bash
22//! # Basic subtitle matching
23//! subx match /path/to/videos /path/to/subtitles
24//!
25//! # Convert SRT to ASS format
26//! subx convert --input file.srt --output file.ass --format ass
27//!
28//! # Detect file encoding
29//! subx detect-encoding *.srt
30//! ```
31
32mod cache_args;
33mod config_args;
34mod convert_args;
35mod detect_encoding_args;
36mod generate_completion_args;
37mod input_handler;
38mod match_args;
39mod sync_args;
40pub mod table;
41pub mod ui;
42
43pub use cache_args::{CacheAction, CacheArgs};
44use clap::{Parser, Subcommand};
45pub use config_args::{ConfigAction, ConfigArgs};
46pub use convert_args::{ConvertArgs, OutputSubtitleFormat};
47pub use detect_encoding_args::DetectEncodingArgs;
48pub use generate_completion_args::GenerateCompletionArgs;
49pub use input_handler::InputPathHandler;
50pub use match_args::MatchArgs;
51pub use sync_args::{SyncArgs, SyncMethod, SyncMethodArg, SyncMode};
52pub use ui::{
53    create_progress_bar, display_ai_usage, display_match_results, print_error, print_success,
54    print_warning,
55};
56
57/// Main CLI application structure defining the top-level interface.
58#[derive(Parser, Debug)]
59#[command(name = "subx-cli")]
60#[command(about = "Intelligent subtitle processing CLI tool")]
61#[command(version = env!("CARGO_PKG_VERSION"))]
62pub struct Cli {
63    /// The subcommand to execute
64    #[command(subcommand)]
65    pub command: Commands,
66}
67
68/// Available subcommands for the SubX CLI application.
69#[derive(Subcommand, Debug)]
70pub enum Commands {
71    /// AI-powered subtitle file matching and intelligent renaming
72    Match(MatchArgs),
73
74    /// Convert subtitle files between different formats
75    Convert(ConvertArgs),
76
77    /// Detect and convert character encoding of subtitle files
78    DetectEncoding(DetectEncodingArgs),
79
80    /// Synchronize subtitle timing with audio tracks
81    Sync(SyncArgs),
82
83    /// Manage and inspect application configuration
84    Config(ConfigArgs),
85
86    /// Generate shell completion scripts
87    GenerateCompletion(GenerateCompletionArgs),
88
89    /// Manage cache and inspect dry-run results
90    Cache(CacheArgs),
91}
92
93/// Executes the SubX CLI application with parsed arguments.
94///
95/// This is the main entry point for CLI execution, routing parsed
96/// command-line arguments to their respective command handlers.
97///
98/// # Arguments Processing
99///
100/// The function takes ownership of parsed CLI arguments and dispatches
101/// them to the appropriate command implementation based on the selected
102/// subcommand.
103///
104/// # Error Handling
105///
106/// Returns a [`crate::Result<()>`] that wraps any errors encountered
107/// during command execution. Errors are propagated up to the main
108/// function for proper exit code handling.
109///
110/// # Examples
111///
112/// ```rust
113/// use subx_cli::cli::run;
114///
115/// # tokio_test::block_on(async {
116/// // This would typically be called from main()
117/// // run().await?;
118/// # Ok::<(), Box<dyn std::error::Error>>(())
119/// # });
120/// ```
121///
122/// # Async Context
123///
124/// This function is async because several subcommands perform I/O
125/// operations that benefit from async execution, particularly:
126/// - AI service API calls
127/// - Large file processing operations
128/// - Network-based configuration loading
129pub async fn run() -> crate::Result<()> {
130    // Create production configuration service
131    let config_service = std::sync::Arc::new(crate::config::ProductionConfigService::new()?);
132    run_with_config(config_service.as_ref()).await
133}
134
135/// Run the CLI with a provided configuration service.
136///
137/// This function enables dependency injection of configuration services,
138/// making it easier to test and providing better control over configuration
139/// management.
140///
141/// # Arguments
142///
143/// * `config_service` - The configuration service to use
144///
145/// # Errors
146///
147/// Returns an error if command execution fails.
148pub async fn run_with_config(
149    config_service: &dyn crate::config::ConfigService,
150) -> crate::Result<()> {
151    let cli = Cli::parse();
152
153    // Switch to workspace directory for file operations if specified via env or config
154    if let Some(ws_env) = std::env::var_os("SUBX_WORKSPACE") {
155        std::env::set_current_dir(&ws_env).map_err(|e| {
156            crate::error::SubXError::CommandExecution(format!(
157                "Failed to set workspace directory to {}: {}",
158                std::path::PathBuf::from(&ws_env).display(),
159                e
160            ))
161        })?;
162    } else if let Ok(config) = config_service.get_config() {
163        let ws_dir = &config.general.workspace;
164        if !ws_dir.as_os_str().is_empty() {
165            std::env::set_current_dir(ws_dir).map_err(|e| {
166                crate::error::SubXError::CommandExecution(format!(
167                    "Failed to set workspace directory to {}: {}",
168                    ws_dir.display(),
169                    e
170                ))
171            })?;
172        }
173    }
174
175    // Use the centralized dispatcher to avoid code duplication
176    crate::commands::dispatcher::dispatch_command_with_ref(cli.command, config_service).await
177}