dotr_dear/cli/
mod.rs

1use std::path::{Path, PathBuf};
2
3use clap::{Args, Parser, Subcommand};
4
5use crate::{
6    config::{self, Config},
7    context::Context,
8};
9
10#[derive(Debug, Parser)]
11#[command(version)]
12pub struct Cli {
13    #[clap(subcommand)]
14    pub command: Option<Command>,
15    #[clap(short, long, global = true)]
16    pub working_dir: Option<String>,
17}
18
19#[derive(Debug, Subcommand)]
20pub enum Command {
21    Init(InitArgs),
22    Import(ImportArgs),
23    Deploy(DeployArgs),
24    Update(UpdateArgs),
25    Diff(DiffArgs),
26    PrintVars(PrintVarsArgs),
27}
28
29#[derive(Debug, Args)]
30#[command(name = "init", about = "Intialize dotfiles repository.")]
31pub struct InitArgs {}
32
33#[derive(Debug, Args, Default)]
34#[command(name = "print-vars", about = "Print all user variables.")]
35pub struct PrintVarsArgs {
36    #[arg(short, long)]
37    pub profile: Option<String>,
38}
39
40#[derive(Debug, Args, Default)]
41#[command(name = "import", about = "Import dotfile and update configuration.")]
42pub struct ImportArgs {
43    #[arg(value_name = "IMPORT_PATH")]
44    pub path: String,
45
46    #[arg(short, long)]
47    pub name: Option<String>,
48
49    #[arg(short, long)]
50    pub profile: Option<String>,
51}
52
53#[derive(Debug, Args, Default)]
54#[command(name = "diff", about = "Show differences between dotfiles.")]
55pub struct DiffArgs {
56    #[arg(num_args(0..), short, long)]
57    pub packages: Option<Vec<String>>,
58
59    #[arg(short = 'P', long)]
60    pub profile: Option<String>,
61
62    #[arg(long, default_value_t = false)]
63    pub ignore_errors: bool,
64}
65
66#[derive(Debug, Args, Default)]
67#[command(name = "update", about = "Update dotfiles from deployed versions.")]
68pub struct UpdateArgs {
69    #[arg(num_args(0..), short, long)]
70    pub packages: Option<Vec<String>>,
71
72    #[arg(short = 'P', long)]
73    pub profile: Option<String>,
74
75    #[arg(long, default_value_t = false)]
76    pub ignore_errors: bool,
77
78    #[arg(long, default_value_t = false)]
79    pub clean: bool,
80
81    #[arg(long, default_value_t = false)]
82    pub dry_run: bool,
83}
84
85#[derive(Debug, Args, Default)]
86#[command(name = "deploy", about = "Deploy dotfiles from repository.")]
87pub struct DeployArgs {
88    #[arg(num_args(0..), short, long)]
89    pub packages: Option<Vec<String>>,
90
91    #[arg(short = 'P', long)]
92    pub profile: Option<String>,
93
94    #[arg(long, default_value_t = false)]
95    pub ignore_errors: bool,
96
97    #[arg(long, default_value_t = false)]
98    pub clean: bool,
99
100    #[arg(long, default_value_t = false)]
101    pub dry_run: bool,
102}
103
104const BANNER: &str = r#"
105██████╗  ██████╗ ████████╗██████╗ 
106██╔══██╗██╔═══██╗╚══██╔══╝██╔══██╗
107██║  ██║██║   ██║   ██║   ██████╔╝
108██║  ██║██║   ██║   ██║   ██╔══██╗
109██████╔╝╚██████╔╝   ██║   ██║  ██║
110╚═════╝  ╚═════╝    ╚═╝   ╚═╝  ╚═╝
111"#;
112
113pub fn run_cli(args: Cli) -> Result<(), anyhow::Error> {
114    let mut working_dir = std::env::current_dir()?;
115    if let Some(wd) = args.working_dir {
116        working_dir = PathBuf::from(wd);
117    }
118
119    if !working_dir.exists() {
120        anyhow::bail!("The specified working directory does not exist");
121    }
122    working_dir = working_dir.canonicalize()?;
123
124    // Print working directory
125    // Print full working directory path
126    match args.command {
127        Some(Command::Init(_)) => {
128            println!("Initializing configuration...");
129            Config::init(&working_dir)?;
130            println!("Configuration initialized successfully.");
131        }
132        Some(Command::Import(args)) => {
133            let (mut conf, ctx) = init_config(&working_dir, &args.profile, true)?;
134            conf.import_package(&args, &ctx)?;
135        }
136        Some(Command::Deploy(args)) => {
137            let (conf, mut ctx) = init_config(&working_dir, &args.profile, false)?;
138            ctx.get_prompted_variables(&conf, &args.packages)?;
139            conf.deploy_packages(&ctx, &args)?;
140        }
141        Some(Command::Update(args)) => {
142            let (conf, mut ctx) = init_config(&working_dir, &args.profile, false)?;
143            ctx.get_prompted_variables(&conf, &args.packages)?;
144            conf.backup_packages(&ctx, &args)?;
145        }
146        Some(Command::Diff(args)) => {
147            let (conf, mut ctx) = init_config(&working_dir, &args.profile, false)?;
148            ctx.get_prompted_variables(&conf, &args.packages)?;
149            conf.diff_packages(&ctx, &args)?;
150        }
151        Some(Command::PrintVars(args)) => {
152            let (_, ctx) = init_config(&working_dir, &args.profile, false)?;
153            ctx.print_variables();
154        }
155        None => {
156            println!("No command provided. Use --help for more information.");
157        }
158    }
159    Ok(())
160}
161
162fn init_config(
163    working_dir: &Path,
164    profile: &Option<String>,
165    create_if_missing: bool,
166) -> anyhow::Result<(Config, Context)> {
167    let mut conf = config::Config::from_path(working_dir)?;
168    if conf.banner {
169        println!("{}", BANNER);
170    }
171    // Start with environment variables from Context::new()
172    let mut ctx = Context::new(working_dir)?;
173    ctx.extend_variables(conf.variables.clone());
174    conf.set_profile_context(profile, &mut ctx, create_if_missing)?;
175    Ok((conf, ctx))
176}