wagon 0.4.0

A command line utility to manage configuration files in one place
use crate::{config::get_config, dest::get_dest, Link, CONFFILE_NAME, IGNOREFILE_NAME};
use anyhow::Result;
use ignore::{DirEntry, WalkBuilder};
use std::collections::HashSet;
use std::fs;
use std::path::{Path, PathBuf};

fn list_diritems(base: &Path) -> Result<HashSet<PathBuf>> {
    let mut items = HashSet::new();
    for d in get_config(base)?.and_then(|c| c.dirs).unwrap_or_default() {
        let full = match base.join(&d).canonicalize() {
            Ok(p) => p,
            Err(_) => continue,
        };
        if !fs::metadata(&full)?.is_dir() {
            continue;
        }
        items.insert(base.join(d));
    }
    Ok(items)
}

#[test]
fn test_list_diritems() -> Result<()> {
    let test_base = PathBuf::from("test/repo/zsh");
    let diritems = list_diritems(&test_base)?;
    tracing::info!("diritems: {diritems:?}");
    assert!(diritems.len() > 0);
    Ok(())
}

fn filter_ignores(e: &DirEntry) -> bool {
    let p = e.path().file_name().unwrap_or_default().to_string_lossy();
    !(p == CONFFILE_NAME
        || p == IGNOREFILE_NAME
        || p == ".git"
        || p == ".gitignore"
        || p == ".gitmodules")
}

fn list_dir(base: &Path, dir: &Path, dir_items: &HashSet<PathBuf>) -> Result<Vec<Link>> {
    let mut items = vec![];
    let pat = dir.to_str().unwrap_or_default().to_string();
    'walk: for r in WalkBuilder::new(&pat)
        .standard_filters(true)
        .hidden(false)
        .add_custom_ignore_filename(IGNOREFILE_NAME)
        .filter_entry(filter_ignores)
        .build()
    {
        match r {
            Ok(dent) => {
                let p = PathBuf::from(dent.path());
                let f = p.strip_prefix(&base).unwrap_or(&p);
                let dst = get_dest(&p)?.canonicalize()?.join(f);
                if fs::metadata(&p)?.is_file() {
                    for dir_item in dir_items {
                        if p.starts_with(dir_item) {
                            continue 'walk;
                        }
                    }
                    items.push(Link::new(p.canonicalize()?, dst, false));
                } else if fs::metadata(&p)?.is_dir() && dir_items.contains(&p) {
                    items.push(Link::new(p.canonicalize()?, dst, true));
                }
            }
            Err(err) => println!("{err:?}"),
        }
    }
    Ok(items)
}

pub fn list_items(base: &Path, ignore_dirlink: bool) -> Result<Vec<Link>> {
    let dirs = if ignore_dirlink {
        HashSet::new()
    } else {
        list_diritems(base)?
    };
    let items = list_dir(base, base, &dirs)?;
    Ok(items)
}

#[test]
fn test_list_items() -> Result<()> {
    let test_base = PathBuf::from("test/repo/bash");
    let items = list_items(&test_base, true)?;
    tracing::info!("items: {items:?}");
    assert!(items.len() > 0);
    Ok(())
}

#[test]
fn test_list_items_with_diritems() -> Result<()> {
    let test_base = PathBuf::from("test/repo/zsh");
    let items = list_items(&test_base, false)?;
    tracing::info!("items: {items:#?}");
    assert!(items.len() == 2);
    Ok(())
}