Skip to main content

mcraw_tui/
cli.rs

1use clap::{Parser, Subcommand};
2
3#[derive(Subcommand, Debug)]
4pub enum CliCommands {
5    /// Open a .mcraw file in the TUI
6    Open {
7        /// Path to the .mcraw file
8        #[arg()]
9        file: Option<String>,
10    },
11    /// Show file metadata and exit
12    Info {
13        /// Path to the .mcraw file
14        #[arg(short, long)]
15        file: Option<String>,
16    },
17    /// Export a .mcraw file to another format
18    Export {
19        /// Path to the .mcraw file
20        #[arg(short, long)]
21        file: Option<String>,
22        /// Export format: dng, prores, h264, hevc
23        #[arg(short = 'F', long)]
24        format: String,
25        /// Output path or directory
26        #[arg(short, long)]
27        output: String,
28    },
29}
30
31#[derive(Parser, Debug)]
32#[command(name = "mcraw-tui", about = "Cross-platform TUI for MotionCam .mcraw files")]
33pub struct Cli {
34    /// Path to the .mcraw file to open (backward compatibility)
35    #[arg(short, long)]
36    pub file: Option<String>,
37
38    /// CLI subcommand
39    #[command(subcommand)]
40    pub command: Option<CliCommands>,
41
42    /// Number of frames to load (default: all)
43    #[arg(short = 'n', long)]
44    pub frames: Option<usize>,
45
46    /// Path to custom placeholder sixel file (for idle/loading animation)
47    #[arg(long)]
48    pub placeholder_path: Option<String>,
49
50    /// Enable verbose logging
51    #[arg(short, long, global = true)]
52    pub verbose: bool,
53
54    /// Output directory for extracted files
55    #[arg(short, long)]
56    pub output: Option<String>,
57}
58
59impl Cli {
60    /// Resolve CLI arguments: subcommand -f takes precedence, falls back to top-level -f
61    pub fn resolve(self) -> ResolvedCli {
62        match self.command {
63            Some(cmd) => ResolvedCli::Command(cmd.resolve_with_top_level(self.file)),
64            None => {
65                if let Some(ref file) = self.file {
66                    ResolvedCli::Command(CliCommands::Open { file: Some(file.clone()) })
67                } else {
68                    ResolvedCli::NoFile
69                }
70            }
71        }
72    }
73
74    /// Validate export format
75    pub fn validate_export_format(format: &str) -> Result<(), String> {
76        let valid = ["dng", "prores", "h264", "hevc"];
77        let lower = format.to_lowercase();
78        if valid.contains(&lower.as_str()) {
79            Ok(())
80        } else {
81            Err(format!(
82                "Invalid export format '{}'. Valid formats: {}",
83                format,
84                valid.join(", ")
85            ))
86        }
87    }
88}
89
90impl CliCommands {
91    /// Merge top-level -f into subcommand if subcommand doesn't have its own -f
92    fn resolve_with_top_level(self, top_level_file: Option<String>) -> Self {
93        match self {
94            CliCommands::Open { file } => CliCommands::Open {
95                file: file.or(top_level_file),
96            },
97            CliCommands::Info { file } => CliCommands::Info {
98                file: file.or(top_level_file),
99            },
100            CliCommands::Export { file, format, output } => CliCommands::Export {
101                file: file.or(top_level_file),
102                format,
103                output,
104            },
105        }
106    }
107}
108
109pub enum ResolvedCli {
110    Command(CliCommands),
111    NoFile,
112}