Skip to main content

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