fp-dotfiles-manager 0.2.5

Minimal, zero-dependency Chezmoi-based dotfiles manager
use crate::exec::{Cmd, ScanPolicy};
use crate::logger::*;
use crate::paths::*;
use std::io::{self, Write};

pub fn run_add(targets: &[String], tracked_file: &str, home: &str) -> io::Result<()> {
    log_section("Updating tracked paths...");
    let parent_dir = std::path::Path::new(tracked_file).parent().unwrap();
    std::fs::create_dir_all(parent_dir)?;

    std::fs::OpenOptions::new()
        .create(true)
        .write(true)
        .open(tracked_file)?;

    let home_path = canonical_path(home);
    let mut added_any = false;

    let mut existing_paths = Vec::new();
    if let Ok(file) = std::fs::File::open(tracked_file) {
        use std::io::BufRead;
        for line in std::io::BufReader::new(file).lines() {
            if let Ok(line) = line {
                existing_paths.push(line.trim().to_string());
            }
        }
    }

    let mut file = std::fs::OpenOptions::new()
        .append(true)
        .open(tracked_file)?;

    for target in targets {
        let abs_path = canonical_path(target);
        if abs_path.starts_with(&home_path) {
            let rel_path = if abs_path == home_path {
                "".to_string()
            } else {
                let offset = if home_path.ends_with('/') {
                    home_path.len()
                } else {
                    home_path.len() + 1
                };
                abs_path[offset..].to_string()
            };

            if existing_paths.contains(&rel_path) {
                log_warn(&(rel_path.clone() + " is already present."));
            } else {
                writeln!(file, "{}", rel_path)?;
                log_success(&("Added ".to_owned() + &rel_path));
                added_any = true;
            }
        } else {
            log_warn(&("Skipping path outside home: ".to_owned() + target));
        }
    }

    if targets.is_empty() || added_any {
        let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nvim".to_string());
        log_step(&("Opening in ".to_owned() + &editor));
        std::process::Command::new(editor)
            .arg(tracked_file)
            .status()?;
    } else {
        log_done("No new tracked paths were added.");
    }

    Ok(())
}

pub fn run_edit_lists(tracked_file: &str, tracked_enc_file: &str) -> io::Result<()> {
    log_section("Opening tracking lists...");
    let editor = std::env::var("EDITOR").unwrap_or_else(|_| "nvim".to_string());
    log_step(&("Launching ".to_owned() + &editor));
    std::process::Command::new(editor)
        .arg("-p")
        .arg(tracked_file)
        .arg(tracked_enc_file)
        .status()?;
    Ok(())
}

pub fn run_manual_add(targets: &[String], policy: ScanPolicy) -> io::Result<()> {
    log_section("Manually adding paths...");
    log_step(&format!("Adding {:?} recursively...", targets));
    let chezmoi = Cmd::new();
    let mut argv: Vec<&str> = vec!["--recursive"];
    argv.extend(targets.iter().map(|t| t.as_str()));
    let outcome = chezmoi.run("add", &argv, chezmoi.secrets_arg("add", policy))?;
    outcome.report();
    outcome.report_summary();
    if outcome.success {
        log_done("Manually added targets recursively.");
    }
    Ok(())
}

pub fn run_manual_add_secret(targets: &[String], policy: ScanPolicy) -> io::Result<()> {
    log_section("Manually adding encrypted paths...");
    log_step(&format!(
        "Adding and encrypting {:?} recursively...",
        targets
    ));
    let chezmoi = Cmd::new();
    let mut argv: Vec<&str> = vec!["--encrypt", "--recursive"];
    argv.extend(targets.iter().map(|t| t.as_str()));
    let outcome = chezmoi.run("add", &argv, chezmoi.secrets_arg("add", policy))?;
    outcome.report();
    if outcome.success {
        log_done("Manually added and encrypted targets.");
    }
    Ok(())
}