Skip to main content

git_pincer/
cli.rs

1//! CLI definition and subcommand dispatch.
2
3use std::path::PathBuf;
4
5use anyhow::Result;
6use clap::{Parser, Subcommand};
7
8use crate::commands;
9
10/// Program command line
11#[derive(Debug, Parser)]
12#[command(
13    version,
14    about,
15    long_about = None
16)]
17pub struct Cli {
18    /// The specific subcommand to be executed
19    #[command(subcommand)]
20    pub command: Option<Commands>,
21    /// The Git repository path (default is current directory)
22    #[arg(short = 'C', long = "repo", global = true, value_name = "PATH")]
23    pub repo: Option<PathBuf>,
24    /// Echo executed git command
25    #[arg(short, long, global = true)]
26    pub verbose: bool,
27    /// theme
28    #[arg(long, global = true, value_enum, default_value = "auto")]
29    pub theme: ThemeArg,
30    /// UI language
31    #[arg(long, global = true, value_enum, default_value = "auto")]
32    pub lang: LangArg,
33}
34
35/// 界面语言选择(命令行与配置文件 `[ui] lang` 共用)。
36#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, serde::Deserialize)]
37#[serde(rename_all = "lowercase")]
38pub enum LangArg {
39    /// 按系统 locale 自动选择(zh 前缀用中文,其余英文)
40    Auto,
41    /// 中文
42    Zh,
43    /// 英文
44    En,
45}
46
47/// 界面主题选择(命令行与配置文件 `[ui] theme` 共用)。
48#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum, serde::Deserialize)]
49#[serde(rename_all = "lowercase")]
50pub enum ThemeArg {
51    /// 按终端环境自动选择(读 COLORFGBG,检测不到用深色)
52    Auto,
53    /// 深色(Tokyo Night)
54    Dark,
55    /// 浅色(Maple Light,适配浅色终端背景)
56    Light,
57}
58
59/// 支持的子命令。
60#[derive(Debug, Subcommand)]
61pub enum Commands {
62    /// Run `git merge` and take over conflicts if any
63    Merge(commands::run::MergeArgs),
64    /// Run `git rebase`, looping through every conflicted round
65    Rebase(commands::run::RebaseArgs),
66    /// Run `git pull` (all arguments are passed through)
67    Pull(commands::run::PullArgs),
68    /// Run `git cherry-pick` (all arguments are passed through)
69    CherryPick(commands::run::CherryPickArgs),
70    /// Run `git revert` (all arguments are passed through)
71    Revert(commands::run::RevertArgs),
72    /// Resolve a single conflict-marked file, no git required
73    File(commands::file::FileArgs),
74    /// Abort the operation in progress (merge / rebase / cherry-pick / revert / am)
75    Abort,
76    /// Generate a shell completion script to stdout
77    Completions(commands::completions::CompletionsArgs),
78}
79
80impl Cli {
81    /// Distribute and execute the selected subcommands.
82    pub fn run(self) -> Result<()> {
83        // 补全脚本生成先于配置加载:它随 shell 启动执行,
84        // 不应被配置文件错误或 git 环境问题阻断
85        if let Some(Commands::Completions(args)) = &self.command {
86            commands::completions::run(args.shell);
87            return Ok(());
88        }
89        // --lang 显式指定时最早生效,让配置文件自身的报错也用对语言;
90        // i18n::init 首次调用生效,因此调用顺序即优先级:命令行 > 配置 > 系统探测
91        match self.lang {
92            LangArg::Zh => crate::i18n::init(crate::i18n::Lang::Zh),
93            LangArg::En => crate::i18n::init(crate::i18n::Lang::En),
94            LangArg::Auto => {}
95        }
96        let config = crate::config::load()?;
97        crate::i18n::init(match config.ui.lang {
98            Some(LangArg::Zh) => crate::i18n::Lang::Zh,
99            Some(LangArg::En) => crate::i18n::Lang::En,
100            _ => crate::i18n::detect(),
101        });
102        crate::ui::keymap::init(&config.keys)?;
103        crate::ui::init_theme_overrides(&config.theme)?;
104        crate::ui::init_editor(config.ui.editor.clone());
105
106        let verbose = self.verbose || config.ui.verbose.unwrap_or(false);
107        let dir = self.repo.unwrap_or_else(|| PathBuf::from("."));
108        // 主题:命令行显式指定 > 配置文件 > 终端环境探测
109        let theme = match self.theme {
110            ThemeArg::Auto => config.ui.theme.unwrap_or(ThemeArg::Auto),
111            explicit => explicit,
112        };
113        let light = match theme {
114            ThemeArg::Light => true,
115            ThemeArg::Dark => false,
116            ThemeArg::Auto => crate::ui::detect_light(),
117        };
118        match self.command {
119            None => commands::menu::run(verbose, &dir, light),
120            Some(Commands::Merge(args)) => commands::run::merge(args, verbose, &dir, light),
121            Some(Commands::Rebase(args)) => commands::run::rebase(args, verbose, &dir, light),
122            Some(Commands::Pull(args)) => commands::run::pull(args, verbose, &dir, light),
123            Some(Commands::CherryPick(args)) => {
124                commands::run::cherry_pick(args, verbose, &dir, light)
125            }
126            Some(Commands::Revert(args)) => commands::run::revert(args, verbose, &dir, light),
127            Some(Commands::File(args)) => commands::file::run(args, light),
128            Some(Commands::Abort) => commands::abort::run(verbose, &dir),
129            // 已在配置加载前处理并早退
130            Some(Commands::Completions(_)) => Ok(()),
131        }
132    }
133}