gitconf 0.1.0

Overlay utility for more convenient and flexible configuration of git
// Gitconf by DomesticMoth
//
// To the extent possible under law, the person who associated CC0 with
// gitconf has waived all copyright and related or neighboring rights
// to gitconf.
//
// You should have received a copy of the CC0 legalcode along with this
// work.  If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
use crate::cfg::Config;
use crate::cfg::OptionConfig;
use crate::pth::PathIter;

use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

fn prepend<T>(v: Vec<T>, s: T) -> Vec<T>
where
    T: Clone,
{
    let s = vec![s];
    let mut tmp: Vec<_> = s.to_owned();
    tmp.extend(v);
    tmp
}

pub fn get_current_config_for_path(mut cur_path: PathBuf) -> (Config, Option<String>) {
    let mut config_path: Option<String> = None;
    let mut opt = OptionConfig::new();
    let mut pathes = PathIter::new(cur_path.clone()).collect::<Vec<PathBuf>>();
    cur_path.push(".git");
    pathes = prepend(pathes, cur_path);
    for path in pathes.iter_mut().rev() {
        path.push(".gitconf");
        path.push("current");
    }
    for path in pathes.iter_mut().rev() {
        if let Ok(read_dir) = fs::read_dir(path.clone()) {
            let entrys: Vec<std::io::Result<std::fs::DirEntry>> = read_dir.collect();
            let mut files: Vec<PathBuf> = Vec::new();
            for entry in entrys {
                if let Ok(entry) = entry {
                    files.push(entry.path())
                }
            }
            if files.len() == 1 {
                let cur_conf: OptionConfig = match toml::from_str(
                    match fs::read_to_string(files[0].clone().into_os_string()) {
                        Ok(s) => s,
                        Err(_) => {
                            log::warn!("Cannot read config {:?}", files[0]);
                            continue;
                        }
                    }
                    .as_str(),
                ) {
                    Ok(cur_conf) => cur_conf,
                    Err(e) => {
                        log::warn!("Cannot parse config {:?} {:?}", files[0], e);
                        continue;
                    }
                };
                config_path = Some(files[0].clone().into_os_string().into_string().unwrap());
                opt.merge(&cur_conf);
            } else if files.len() > 1 {
                log::warn!("There can be only one current config; More than one config found in {:?}", path);
            }
        }
    }
    (opt.to_config(), config_path)
}

pub fn get_current_config() -> std::io::Result<(Config, Option<String>)> {
    let buf = std::env::current_dir()?;
    Ok(get_current_config_for_path(buf))
}

pub fn get_profiles_for_path(mut cur_path: PathBuf) -> HashMap<String, PathBuf> {
    let mut profiles: HashMap<String, PathBuf> = HashMap::new();
    let mut pathes = PathIter::new(cur_path.clone()).collect::<Vec<PathBuf>>();
    cur_path.push(".git");
    pathes = prepend(pathes, cur_path);
    for path in pathes.iter_mut().rev() {
        path.push(".gitconf");
        path.push("profiles");
    }
    for path in pathes.iter_mut().rev() {
        if let Ok(read_dir) = fs::read_dir(path.clone()) {
            let entrys: Vec<std::io::Result<std::fs::DirEntry>> = read_dir.collect();
            for entry in entrys {
                if let Ok(entry) = entry {
                    let path = entry.path();
                    let _: OptionConfig = match toml::from_str(
                        match fs::read_to_string(path.clone().into_os_string()) {
                            Ok(s) => s,
                            Err(_) => {
                                log::warn!("Cannot read config {:?}", path);
                                continue;
                            }
                        }
                        .as_str(),
                    ) {
                        Ok(cur_conf) => cur_conf,
                        Err(e) => {
                            log::warn!("Cannot parse config {:?} {:?}", path, e);
                            continue;
                        }
                    };
                    profiles.insert(path.file_name().unwrap().to_str().unwrap().to_string(), path);
                }
            }
        }
    }
    profiles
}

pub fn get_current_profiles() -> std::io::Result<HashMap<String, PathBuf>> {
    let buf = std::env::current_dir()?;
    Ok(get_profiles_for_path(buf))
}

pub fn set_profile(src: PathBuf, dst: PathBuf) -> bool {
    let mut dst = {
        let mut git = dst.clone();
        git.push(".git");
        if git.exists() {
            git.push(".gitconf");
            git.push("current");
            git
        } else {
            let mut dst = dst.clone();
            dst.push(".gitconf");
            dst.push("current");
            dst
        }
    };
    if dst.exists() {
        if dst.is_dir() {
            if let Err(e) = std::fs::remove_dir_all(dst.clone()) {
                log::error!("Cannot set profile {:?}", e);
                return false;
            }
        } else {
            if let Err(e) = std::fs::remove_file(dst.clone()) {
                log::error!("Cannot set profile {:?}", e);
                return false;
            }
        }
    }
    if let Err(e) = std::fs::create_dir_all(dst.clone()) {
        log::error!("Cannot set profile {:?}", e);
        return false;
    }
    dst.push(src.file_name().unwrap());
    if let Err(e) = std::fs::copy(src, dst) {
        log::error!("Cannot set profile {:?}", e);
        return false;
    }
    return true;
}