Skip to main content

yui/
cli.rs

1use anyhow::Result;
2use camino::Utf8PathBuf;
3use clap::{Parser, Subcommand};
4
5use crate::cmd;
6use crate::config::IconsMode;
7
8#[derive(Parser, Debug)]
9#[command(version, about, long_about = None)]
10pub struct Cli {
11    /// Path to dotfiles source repository ($DOTFILES)
12    #[arg(short, long, env = "YUI_SOURCE", global = true)]
13    pub source: Option<Utf8PathBuf>,
14
15    /// Increase log verbosity (-v, -vv, -vvv)
16    #[arg(short, long, action = clap::ArgAction::Count, global = true)]
17    pub verbose: u8,
18
19    #[command(subcommand)]
20    pub command: Command,
21}
22
23#[derive(Subcommand, Debug)]
24pub enum Command {
25    /// Initialize source repo skeleton
26    Init {
27        /// Install git pre-commit/pre-push hooks for render-drift check
28        #[arg(long)]
29        git_hooks: bool,
30    },
31
32    /// Render templates + link targets + auto-absorb (default workflow)
33    Apply {
34        #[arg(long)]
35        dry_run: bool,
36    },
37
38    /// Render templates only
39    Render {
40        /// Fail with non-zero exit if rendered output diverges (CI hook)
41        #[arg(long)]
42        check: bool,
43        #[arg(long)]
44        dry_run: bool,
45    },
46
47    /// Link / relink targets only
48    Link {
49        #[arg(long)]
50        dry_run: bool,
51    },
52
53    /// Unlink targets
54    Unlink { paths: Vec<Utf8PathBuf> },
55
56    /// Show drift status (link-broken / replaced / template-drift)
57    Status {
58        /// Override [ui] icons mode for this invocation
59        #[arg(long, value_name = "MODE")]
60        icons: Option<IconsMode>,
61        /// Disable color output (also respected via NO_COLOR env)
62        #[arg(long)]
63        no_color: bool,
64    },
65
66    /// List all src→dst link mappings (mount entries + .yuilink overrides)
67    List {
68        /// Include entries whose `when` evaluates false on the current host
69        #[arg(long)]
70        all: bool,
71        /// Override [ui] icons mode for this invocation
72        #[arg(long, value_name = "MODE")]
73        icons: Option<IconsMode>,
74        /// Disable color output (also respected via NO_COLOR env)
75        #[arg(long)]
76        no_color: bool,
77    },
78
79    /// Manually absorb a target into source (when auto-absorb skipped)
80    Absorb {
81        target: Utf8PathBuf,
82        #[arg(long)]
83        dry_run: bool,
84    },
85
86    /// Diagnose environment (symlink capability, source detection, etc)
87    Doctor,
88
89    /// Garbage-collect old backups
90    GcBackup {
91        /// e.g. "30d", "6m"
92        #[arg(long)]
93        older_than: Option<String>,
94    },
95
96    /// Manage `[[hook]]` scripts
97    Hooks {
98        #[command(subcommand)]
99        action: HookAction,
100    },
101}
102
103#[derive(Subcommand, Debug)]
104pub enum HookAction {
105    /// List configured hooks with their last-run state
106    List,
107    /// Run a hook (or every hook). The `when` filter is always honored;
108    /// `--force` bypasses the `when_run` state check (so a `once` hook
109    /// can be re-run, an `onchange` hook re-runs even with matching
110    /// hash).
111    Run {
112        /// Hook name (omit to run every hook per its `when_run` rule)
113        name: Option<String>,
114        /// Bypass the `when_run` state check
115        #[arg(long)]
116        force: bool,
117    },
118}
119
120impl Cli {
121    pub fn run(self) -> Result<()> {
122        let source = self.source;
123        match self.command {
124            Command::Init { git_hooks } => cmd::init(source, git_hooks),
125            Command::Apply { dry_run } => cmd::apply(source, dry_run),
126            Command::Render { check, dry_run } => cmd::render(source, check, dry_run),
127            Command::Link { dry_run } => cmd::link(source, dry_run),
128            Command::Unlink { paths } => cmd::unlink(source, paths),
129            Command::Status { icons, no_color } => cmd::status(source, icons, no_color),
130            Command::List {
131                all,
132                icons,
133                no_color,
134            } => cmd::list(source, all, icons, no_color),
135            Command::Absorb { target, dry_run } => cmd::absorb(source, target, dry_run),
136            Command::Doctor => cmd::doctor(source),
137            Command::GcBackup { older_than } => cmd::gc_backup(source, older_than),
138            Command::Hooks { action } => match action {
139                HookAction::List => cmd::hooks_list(source),
140                HookAction::Run { name, force } => cmd::hooks_run(source, name, force),
141            },
142        }
143    }
144}