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    /// Enable verbose logging
47    #[arg(short, long, global = true)]
48    pub verbose: bool,
49
50    /// Output directory for extracted files
51    #[arg(short, long)]
52    pub output: Option<String>,
53}
54
55impl Cli {
56    /// Resolve CLI arguments: subcommand -f takes precedence, falls back to top-level -f
57    pub fn resolve(self) -> ResolvedCli {
58        match self.command {
59            Some(cmd) => ResolvedCli::Command(cmd.resolve_with_top_level(self.file)),
60            None => {
61                if let Some(ref file) = self.file {
62                    ResolvedCli::Command(CliCommands::Open { file: Some(file.clone()) })
63                } else {
64                    ResolvedCli::NoFile
65                }
66            }
67        }
68    }
69
70    /// Validate export format
71    pub fn validate_export_format(format: &str) -> Result<(), String> {
72        let valid = ["dng", "prores", "h264", "hevc"];
73        let lower = format.to_lowercase();
74        if valid.contains(&lower.as_str()) {
75            Ok(())
76        } else {
77            Err(format!(
78                "Invalid export format '{}'. Valid formats: {}",
79                format,
80                valid.join(", ")
81            ))
82        }
83    }
84}
85
86impl CliCommands {
87    /// Merge top-level -f into subcommand if subcommand doesn't have its own -f
88    fn resolve_with_top_level(self, top_level_file: Option<String>) -> Self {
89        match self {
90            CliCommands::Open { file } => CliCommands::Open {
91                file: file.or(top_level_file),
92            },
93            CliCommands::Info { file } => CliCommands::Info {
94                file: file.or(top_level_file),
95            },
96            CliCommands::Export { file, format, output } => CliCommands::Export {
97                file: file.or(top_level_file),
98                format,
99                output,
100            },
101        }
102    }
103}
104
105pub enum ResolvedCli {
106    Command(CliCommands),
107    NoFile,
108}