fp-dotfiles-manager 0.2.5

Minimal, zero-dependency Chezmoi-based dotfiles manager
mod commands;
mod config;
mod config_sync;
mod diagnostics;
mod exec;
mod lock;
mod logger;
mod paths;

use commands::{
    add::{run_add, run_edit_lists, run_manual_add, run_manual_add_secret},
    audit::run_audit_secrets,
    backup::run_backup,
    bootstrap::run_bootstrap,
    chezmoi::{
        resolve_config_drift, run_apply, run_cd, run_diff, run_edit, run_forget, run_status,
        run_update,
    },
    history::run_clean_history,
    init_config::run_init_config,
    logs::run_logs,
    setup::run_setup,
    sync::run_sync,
};
use config::Config;
use fp_dotfiles_catalog::format_cli_help;
use std::io;

fn print_help() {
    print!("{}", format_cli_help());
}

fn main() -> io::Result<()> {
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 2 {
        print_help();
        return Ok(());
    }

    let config = Config::load();
    let subcommand = &args[1];

    match subcommand.as_str() {
        "init-config" => {
            run_init_config()?;
        }
        "setup" => {
            let mut repo_url = None;
            let mut ssh_key = None;
            let mut age_pass = None;
            let mut force = false;

            let mut i = 2;
            while i < args.len() {
                match args[i].as_str() {
                    "--repo-url" => {
                        if i + 1 < args.len() {
                            repo_url = Some(args[i + 1].clone());
                            i += 2;
                        } else {
                            eprintln!("Error: Missing value for --repo-url");
                            std::process::exit(1);
                        }
                    }
                    "--ssh-key" => {
                        if i + 1 < args.len() {
                            ssh_key = Some(args[i + 1].clone());
                            i += 2;
                        } else {
                            eprintln!("Error: Missing value for --ssh-key");
                            std::process::exit(1);
                        }
                    }
                    "--age-pass" => {
                        if i + 1 < args.len() {
                            age_pass = Some(args[i + 1].clone());
                            i += 2;
                        } else {
                            eprintln!("Error: Missing value for --age-pass");
                            std::process::exit(1);
                        }
                    }
                    "--force" | "-f" => {
                        force = true;
                        i += 1;
                    }
                    _ => {
                        eprintln!("Error: Unknown option {}", args[i]);
                        std::process::exit(1);
                    }
                }
            }

            let final_repo_url = repo_url.unwrap_or(config.repo_url);
            let final_ssh_key = ssh_key.unwrap_or(config.ssh_key_path);
            let final_age_pass = age_pass.unwrap_or(config.age_pass);
            run_setup(&final_repo_url, &final_ssh_key, &final_age_pass, force)?;
        }
        "bootstrap" => {
            run_bootstrap(&config.bootstrap_dir, true)?;
        }
        "sync" => {
            let mut dry_run = false;
            if args.len() >= 3 {
                if args[2] == "--dry-run" || args[2] == "-n" {
                    dry_run = true;
                }
            }
            run_sync(config, dry_run)?;
        }
        "backup" => {
            let mut output_path = None;
            let mut age_pass = None;

            let mut i = 2;
            while i < args.len() {
                match args[i].as_str() {
                    "--age-pass" => {
                        if i + 1 < args.len() {
                            age_pass = Some(args[i + 1].clone());
                            i += 2;
                        } else {
                            eprintln!("Error: Missing value for --age-pass");
                            std::process::exit(1);
                        }
                    }
                    _ => {
                        if args[i].starts_with('-') {
                            eprintln!("Error: Unknown option {}", args[i]);
                            std::process::exit(1);
                        } else if output_path.is_none() {
                            output_path = Some(args[i].clone());
                            i += 1;
                        } else {
                            eprintln!("Error: Multiple output paths specified");
                            std::process::exit(1);
                        }
                    }
                }
            }

            let mut final_config = config;
            if let Some(pass) = age_pass {
                final_config.age_pass = pass;
            }

            run_backup(output_path, &final_config)?;
        }
        "add" => {
            if args.len() < 3 {
                eprintln!("Error: Missing targets");
                std::process::exit(1);
            }
            let targets = args[2..].to_vec();
            run_add(&targets, &config.tracked_file, &config.home)?;
        }
        "add-secret" => {
            if args.len() < 3 {
                eprintln!("Error: Missing targets");
                std::process::exit(1);
            }
            let targets = args[2..].to_vec();
            run_add(&targets, &config.tracked_enc_file, &config.home)?;
        }
        "manual-add" => {
            if args.len() < 3 {
                eprintln!("Error: Missing targets");
                std::process::exit(1);
            }
            let targets = args[2..].to_vec();
            run_manual_add(&targets, config.scan_policy())?;
        }
        "manual-add-secret" => {
            if args.len() < 3 {
                eprintln!("Error: Missing targets");
                std::process::exit(1);
            }
            let targets = args[2..].to_vec();
            run_manual_add_secret(&targets, config.scan_policy())?;
        }
        "audit-secrets" => {
            let mut fix = false;
            for arg in &args[2..] {
                match arg.as_str() {
                    "--fix" | "-f" => fix = true,
                    _ => {
                        eprintln!("Error: Unknown option {}", arg);
                        std::process::exit(1);
                    }
                }
            }
            run_audit_secrets(config, fix)?;
        }
        "update" => {
            resolve_config_drift()?;
            run_update()?;
        }
        "apply" => {
            resolve_config_drift()?;
            run_apply()?;
        }
        "cd" => {
            run_cd()?;
        }
        "forget" => {
            if args.len() < 3 {
                eprintln!("Error: Missing targets");
                std::process::exit(1);
            }
            let targets = args[2..].to_vec();
            run_forget(&targets)?;
        }
        "status" => {
            run_status()?;
        }
        "diff" => {
            run_diff()?;
        }
        "edit" => {
            if args.len() < 3 {
                eprintln!("Error: Missing target");
                std::process::exit(1);
            }
            let target = &args[2];
            run_edit(target)?;
        }
        "logs" => {
            run_logs(&args)?;
        }
        "edit-lists" => {
            run_edit_lists(&config.tracked_file, &config.tracked_enc_file)?;
        }
        "clean-history" => {
            let mut repo_path = None;
            let mut i = 2;
            while i < args.len() {
                match args[i].as_str() {
                    "--repo-path" => {
                        if i + 1 < args.len() {
                            repo_path = Some(args[i + 1].clone());
                            i += 2;
                        } else {
                            eprintln!("Error: Missing value for --repo-path");
                            std::process::exit(1);
                        }
                    }
                    _ => {
                        eprintln!("Error: Unknown option {}", args[i]);
                        std::process::exit(1);
                    }
                }
            }
            let final_repo_path = repo_path.unwrap_or(config.repo_path);
            run_clean_history(&final_repo_path)?;
        }
        "help" | "-h" | "--help" => {
            print_help();
        }
        _ => {
            eprintln!("Unknown subcommand: {}", subcommand);
            print_help();
            std::process::exit(1);
        }
    }

    Ok(())
}