Skip to main content

gitkraft_tui/
cli.rs

1//! Command-line handling for theme selection (`--theme`, `--list-themes`).
2
3use gitkraft_core::{is_system_name, SYSTEM_THEME_NAME, THEME_NAMES};
4
5/// Every selectable theme name: `System` followed by all core presets.
6pub fn theme_names() -> Vec<&'static str> {
7    std::iter::once(SYSTEM_THEME_NAME)
8        .chain(THEME_NAMES.iter().copied())
9        .collect()
10}
11
12/// Resolve a user-supplied theme name (case-insensitive) to its canonical
13/// persisted name (`"System"` or a preset name).
14pub fn resolve_theme(name: &str) -> Option<&'static str> {
15    if is_system_name(name) {
16        return Some(SYSTEM_THEME_NAME);
17    }
18    THEME_NAMES
19        .iter()
20        .copied()
21        .find(|n| n.eq_ignore_ascii_case(name.trim()))
22}
23
24/// Parsed command line.
25#[derive(Debug, Default, PartialEq, Eq)]
26pub struct Cli {
27    pub repo_path: Option<String>,
28    pub theme: Option<String>,
29    pub list_themes: bool,
30}
31
32/// Parse arguments (without the program name).
33pub fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Result<Cli, String> {
34    let mut cli = Cli::default();
35    let mut it = args.into_iter();
36    while let Some(a) = it.next() {
37        if a == "--list-themes" {
38            cli.list_themes = true;
39        } else if a == "--theme" {
40            cli.theme = Some(it.next().ok_or("--theme requires a value")?);
41        } else if let Some(v) = a.strip_prefix("--theme=") {
42            cli.theme = Some(v.to_string());
43        } else if a.starts_with("--") {
44            return Err(format!("unknown option '{a}'"));
45        } else if cli.repo_path.is_none() {
46            cli.repo_path = Some(a);
47        }
48    }
49    Ok(cli)
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    fn p(a: &[&str]) -> Result<Cli, String> {
57        parse_args(a.iter().map(|s| s.to_string()))
58    }
59
60    #[test]
61    fn names_start_with_system_and_cover_all_presets() {
62        let n = theme_names();
63        assert_eq!(n[0], "System");
64        assert_eq!(n.len(), gitkraft_core::THEME_COUNT + 1);
65    }
66
67    #[test]
68    fn resolve_is_case_insensitive() {
69        assert_eq!(resolve_theme("system"), Some("System"));
70        assert_eq!(resolve_theme("dracula"), Some("Dracula"));
71        assert_eq!(resolve_theme("nope"), None);
72    }
73
74    #[test]
75    fn parses_flags_and_repo() {
76        assert_eq!(p(&[]).unwrap(), Cli::default());
77        let c = p(&["/tmp/r", "--theme", "Nord"]).unwrap();
78        assert_eq!(c.repo_path.as_deref(), Some("/tmp/r"));
79        assert_eq!(c.theme.as_deref(), Some("Nord"));
80        assert_eq!(
81            p(&["--theme=System"]).unwrap().theme.as_deref(),
82            Some("System")
83        );
84        assert!(p(&["--list-themes"]).unwrap().list_themes);
85        assert!(p(&["--theme"]).is_err());
86        assert!(p(&["--bogus"]).is_err());
87    }
88}