fp-dotfiles-manager 0.2.5

Minimal, zero-dependency Chezmoi-based dotfiles manager
use crate::config_sync;
use crate::exec::{Cmd, SecretsArg};
use crate::logger::*;
use std::io;

pub fn run_update() -> io::Result<()> {
    log_section("Fetching and applying updates...");
    let outcome = Cmd::new().run("update", &["--force"], SecretsArg::Unsupported)?;
    outcome.report();
    log_done("System is up to date.");
    Ok(())
}

pub fn run_apply() -> io::Result<()> {
    log_section("Applying local changes...");
    let outcome = Cmd::new().run("apply", &["--force"], SecretsArg::Unsupported)?;
    outcome.report();
    log_done("Changes applied successfully.");
    Ok(())
}

/// Suppresses chezmoi's "config file template has changed" nag by repairing the
/// config up front. See [`crate::config_sync`].
pub fn resolve_config_drift() -> io::Result<()> {
    super::sync::resolve_config_drift()
}

pub fn run_cd() -> io::Result<()> {
    log_section("Opening a shell in the dotfiles source directory...");
    // Needs a real terminal, so stdio must be inherited.
    Cmd::new().status("cd", &[])?;
    Ok(())
}

pub fn run_forget(targets: &[String]) -> io::Result<()> {
    log_section("Removing tracked paths...");
    log_step(&format!("Forgetting {:?}...", targets));
    let mut argv: Vec<&str> = vec!["--force"];
    argv.extend(targets.iter().map(|t| t.as_str()));
    let outcome = Cmd::new().run("forget", &argv, SecretsArg::Unsupported)?;
    outcome.report();
    log_done("Untracked targets successfully.");
    Ok(())
}

pub fn run_status() -> io::Result<()> {
    log_section("Showing dotfiles status...");
    // Reuse the freshness probe's outcome so status is not run twice.
    let probe = Cmd::new().run("status", &[], SecretsArg::Unsupported)?;
    if config_sync::has_drift(&probe) {
        let repair = config_sync::ensure_current(Some(&probe));
        if let config_sync::Repair::Failed(reason) = repair {
            log_warn(&("Could not regenerate the chezmoi config: ".to_owned() + &reason));
        }
        // Re-run against the repaired config so the listing is accurate.
        let outcome = Cmd::new().run("status", &[], SecretsArg::Unsupported)?;
        print!("{}", outcome.stdout);
        outcome.report();
        return Ok(());
    }
    print!("{}", probe.stdout);
    probe.report();
    Ok(())
}

pub fn run_diff() -> io::Result<()> {
    log_section("Showing dotfiles diff...");
    // chezmoi diff may page its output, so it must keep the inherited terminal.
    Cmd::new().status("diff", &[])?;
    Ok(())
}

pub fn run_edit(target: &str) -> io::Result<()> {
    log_section("Opening secure edit session...");
    log_step(&("Editing ".to_owned() + target));
    // Needs a real terminal for the editor.
    Cmd::new().status("edit", &[target])?;
    Ok(())
}