rdfm 0.1.1

RDFM - A Rusty DotFiles Manager
use fs::File;
use fs_extra::{dir, dir::remove, file};

use crate::{
    error,
    setup::{get_dotfiles_folder, setup},
    RdfmError, Result,
};
use std::{env, ffi::OsStr, fs, io::Read, path::Path};

fn same<T>(a: T, b: T) -> Result<bool>
where
    T: AsRef<Path> + AsRef<OsStr>,
{
    let a_path = Path::new(&a);
    let b_path = Path::new(&b);

    if !a_path.exists() || !b_path.exists() {
        return Ok(false);
    }

    if a_path.is_dir() {
        if b_path.is_dir() {
            let mut a_entries = vec![];

            for entry in fs::read_dir(a_path)? {
                let entry = entry?;
                a_entries.push(entry.path());
            }

            let mut b_entries = vec![];

            for entry in fs::read_dir(b_path)? {
                let entry = entry?;
                b_entries.push(entry.path());
            }

            if b_entries.len() != a_entries.len() {
                return Ok(false);
            }

            for i in 0..a_entries.len() {
                if !same(&a_entries[i], &b_entries[i])? {
                    return Ok(false);
                }
            }

            Ok(true)
        } else {
            Ok(false)
        }
    } else {
        if b_path.is_dir() {
            Ok(false)
        } else {
            let mut a_bytes = vec![];
            let mut b_bytes = vec![];

            let mut a_file = File::open(a_path)?;
            let mut b_file = File::open(b_path)?;

            a_file.read_to_end(&mut a_bytes)?;
            b_file.read_to_end(&mut b_bytes)?;

            Ok(a_bytes == b_bytes)
        }
    }
}

pub fn proceed() -> Result<()> {
    let dotfiles_path = format!("{}/dotfiles.rdfm", get_dotfiles_folder()?);

    setup()?;

    let content = fs::read_to_string(dotfiles_path)?;
    let len = content
        .lines()
        .filter(|l| !l.starts_with("#"))
        .collect::<Vec<_>>()
        .len();

    println!("\x1b[0;32mLinking\x1b[0m of {} files...", len);
    for (index, line) in content.lines().enumerate() {
        if line.starts_with("#") {
            continue;
        }
        let splited = line.split("->").collect::<Vec<_>>();

        if splited.len() != 2 {
            return Err(error!(
                (format!(
                    "Invalid line format in {}/dotfiles.rdfm at line {} ({}).",
                    get_dotfiles_folder()?,
                    index + 1,
                    line
                ))
            ));
        }

        if same(splited[0], splited[1])? {
            println!(
                "`{}`->`{}`... \x1b[1;33mnot updated\x1b[0m",
                splited[0].replace(&env::var("HOME")?, "~"),
                splited[1].replace(&env::var("HOME")?, "~")
            );
            continue;
        }

        let dest = Path::new(splited[1]);
        let src = Path::new(splited[0]);

        if dest.exists() {
            if dest.is_dir() {
                remove(dest)?;
            } else {
                fs::remove_file(dest)?;
            }
        }

        if src.exists() {
            if src.is_dir() {
                fs::create_dir_all(dest)?;
                dir::copy(src, dest, &dir::CopyOptions::new())?;
            } else {
                fs::create_dir_all(dest.parent().unwrap())?;
                file::copy(src, dest, &file::CopyOptions::new())?;
            }
        } else {
            return Err(error!(
                (format!("No filesystem element named `{}`", dest.to_str().unwrap()))
            ));
        }
        println!(
            "`{}`->`{}`... \x1b[0;32mok\x1b[0m",
            splited[0].replace(&env::var("HOME")?, "~"),
            splited[1].replace(&env::var("HOME")?, "~")
        );
    }
    println!("\x1b[0;32mSuccessfully\x1b[0m linked {} files.", len);

    Ok(())
}