Added installer
This commit is contained in:
		
							
								
								
									
										35
									
								
								src/config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/config.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use serde::Deserialize;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Deserialize)]
 | 
			
		||||
pub struct Module {
 | 
			
		||||
    #[serde(default)]
 | 
			
		||||
    pub ignore: Vec<String>,
 | 
			
		||||
    pub content: HashMap<String, String>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Deserialize)]
 | 
			
		||||
pub struct ModToml {
 | 
			
		||||
    pub modules: HashMap<String, Module>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn parse() -> Result<ModToml, String> {
 | 
			
		||||
    let mut files: ModToml = toml::from_str(std::fs::read_to_string("mod.toml").unwrap().as_str())
 | 
			
		||||
        .map_err(|e| format!("Error while parsing files.toml:\n{}", e.to_string()))?;
 | 
			
		||||
 | 
			
		||||
    let home_path = std::env::var("HOME").unwrap();
 | 
			
		||||
    for (_, m) in files.modules.iter_mut() {
 | 
			
		||||
        m.content = m.content.iter().map(|(n, p)| (
 | 
			
		||||
                n.clone(),
 | 
			
		||||
                p.replace("~", home_path.as_str())
 | 
			
		||||
            )).collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (name, _path) in files.modules.iter().flat_map(|(_, m)| m.content.iter()) {
 | 
			
		||||
        let p = std::path::Path::new(name);
 | 
			
		||||
        if !p.exists() {
 | 
			
		||||
            return Err(format!("Missing module entry {}", name));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    Ok(files)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
mod prompt;
 | 
			
		||||
mod repository;
 | 
			
		||||
mod config;
 | 
			
		||||
mod operations;
 | 
			
		||||
mod utils;
 | 
			
		||||
 | 
			
		||||
use strum::{EnumIter, IntoEnumIterator};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, EnumIter)]
 | 
			
		||||
enum MainMenu {
 | 
			
		||||
    INSTALL,
 | 
			
		||||
    COLLECT,
 | 
			
		||||
    DIFF,
 | 
			
		||||
    UPLOAD,
 | 
			
		||||
    QUIT
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToString for MainMenu {
 | 
			
		||||
    fn to_string(&self) -> String {
 | 
			
		||||
        match self {
 | 
			
		||||
            MainMenu::INSTALL => "Pull and install files",
 | 
			
		||||
            MainMenu::COLLECT => "Collect files",
 | 
			
		||||
            MainMenu::DIFF => "View git diff",
 | 
			
		||||
            MainMenu::UPLOAD => "(Commit) and push to git",
 | 
			
		||||
            MainMenu::QUIT => "Exit"
 | 
			
		||||
        }.to_string()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
    let repo = match repository::open_repo() {
 | 
			
		||||
        Ok(v) => v,
 | 
			
		||||
        Err(err) => {
 | 
			
		||||
            println!("Failed to open/clone repo!");
 | 
			
		||||
            println!("{}", err);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if !std::path::Path::new("mod.toml").exists() {
 | 
			
		||||
        println!("'mod.toml' doesn't exist!");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mods = match config::parse() {
 | 
			
		||||
        Ok(v) => v,
 | 
			
		||||
        Err(err) => {
 | 
			
		||||
            println!("Failed to process mod.toml!");
 | 
			
		||||
            println!("{}", err);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    'main_loop: loop {
 | 
			
		||||
        let res = prompt::select(Some("What do you want to do?"), MainMenu::iter().collect());
 | 
			
		||||
        match res {
 | 
			
		||||
            MainMenu::INSTALL => {
 | 
			
		||||
                if let Err(err) = repository::pull_repo(&repo) {
 | 
			
		||||
                    println!("Failed to pull repo!");
 | 
			
		||||
                    println!("{}", err);
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                operations::install(&mods);
 | 
			
		||||
            },
 | 
			
		||||
            MainMenu::COLLECT => operations::collect(&mods),
 | 
			
		||||
            MainMenu::DIFF => { std::process::Command::new("git").arg("diff").spawn().unwrap().wait().unwrap(); },
 | 
			
		||||
            MainMenu::UPLOAD => operations::upload(&repo),
 | 
			
		||||
            MainMenu::QUIT => break 'main_loop
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										113
									
								
								src/operations.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/operations.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,113 @@
 | 
			
		||||
use crate::config::{ModToml, Module};
 | 
			
		||||
use crate::prompt::multi_select;
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct NamedModule(String, Module);
 | 
			
		||||
impl ToString for NamedModule {
 | 
			
		||||
    fn to_string(&self) -> String {
 | 
			
		||||
        self.0.clone()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn install_mod(m: NamedModule) {
 | 
			
		||||
    let spinner = indicatif::ProgressBar::new_spinner().with_message(format!("Installing module {}...", m.0));
 | 
			
		||||
    'content_iter: for entry in m.1.content {
 | 
			
		||||
        let src = std::path::Path::new(entry.0.as_str());
 | 
			
		||||
        if src.is_file() {
 | 
			
		||||
            if let Err(err) = std::fs::copy(src, entry.1) {
 | 
			
		||||
                println!("Failed to copy {}:\n{}", entry.0, err);
 | 
			
		||||
            }
 | 
			
		||||
            spinner.tick();
 | 
			
		||||
        } else {
 | 
			
		||||
            let dst = std::path::Path::new(entry.1.as_str());
 | 
			
		||||
 | 
			
		||||
            if let Err(err) = std::fs::create_dir_all(dst) {
 | 
			
		||||
                println!("Failed to create directory {}:\n{}", entry.1, err);
 | 
			
		||||
                continue 'content_iter;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let to_remove = match crate::utils::get_dir_content_filtered(dst, &m.1.ignore) {
 | 
			
		||||
                Ok(v) => v,
 | 
			
		||||
                Err(err) => { println!("Failed to get destination content:\n{}", err); continue 'content_iter; }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            for file in to_remove.files {
 | 
			
		||||
                let _ = std::fs::remove_file(file);
 | 
			
		||||
                spinner.tick();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for dir in to_remove.directories.into_iter().rev() {
 | 
			
		||||
                let _ = std::fs::remove_dir(dir);
 | 
			
		||||
                spinner.tick();
 | 
			
		||||
            }
 | 
			
		||||
            if let Err(err) = crate::utils::copy_dir(src, dst, &spinner) {
 | 
			
		||||
                println!("Failed to copy directory:\n{}", err);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn collect_mod(m: NamedModule) {
 | 
			
		||||
    let spinner = indicatif::ProgressBar::new_spinner().with_message(format!("Collecting module {}...", m.0));
 | 
			
		||||
    'content_iter: for entry in m.1.content {
 | 
			
		||||
        let dst = std::path::Path::new(entry.0.as_str());
 | 
			
		||||
        let src = std::path::Path::new(entry.1.as_str());
 | 
			
		||||
        if src.is_file() {
 | 
			
		||||
            if let Err(err) = std::fs::copy(src, dst) {
 | 
			
		||||
                println!("Failed to copy {}:\n{}", entry.1, err);
 | 
			
		||||
            }
 | 
			
		||||
            spinner.tick();
 | 
			
		||||
        } else {
 | 
			
		||||
            let _ = std::fs::remove_dir_all(dst);
 | 
			
		||||
            if let Err(err) = std::fs::create_dir_all(dst) {
 | 
			
		||||
                println!("Failed to create directory {}:\n{}", entry.0, err);
 | 
			
		||||
                continue 'content_iter;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let to_copy = match crate::utils::get_dir_content_filtered(src, &m.1.ignore) {
 | 
			
		||||
                Ok(v) => crate::utils::trim_dir_content(v, src),
 | 
			
		||||
                Err(err) => { println!("Failed to get source content:\n{}", err); continue 'content_iter; }
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            for dir in to_copy.directories {
 | 
			
		||||
                std::fs::create_dir_all(dst.join(&dir)).unwrap();
 | 
			
		||||
                spinner.tick();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for file in to_copy.files {
 | 
			
		||||
                std::fs::copy(src.join(&file), dst.join(&file)).unwrap();
 | 
			
		||||
                spinner.tick();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn install(config: &ModToml) {
 | 
			
		||||
    let to_install = multi_select(
 | 
			
		||||
        Some("Which modules do you want to install?"),
 | 
			
		||||
        config.modules.iter().map(|v| NamedModule(v.0.clone(), v.1.clone())).collect()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    for m in to_install {
 | 
			
		||||
        install_mod(m);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn collect(config: &ModToml) {
 | 
			
		||||
    let to_collect = multi_select(
 | 
			
		||||
        Some("Which modules do you want to collect?"),
 | 
			
		||||
        config.modules.iter().map(|v| NamedModule(v.0.clone(), v.1.clone())).collect()
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    for m in to_collect {
 | 
			
		||||
        collect_mod(m);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn upload(repo: &git2::Repository) {
 | 
			
		||||
    if !crate::repository::is_clean(repo) {
 | 
			
		||||
        let msg = crate::prompt::input("Commit message");
 | 
			
		||||
        crate::repository::commit_changes(repo, msg);
 | 
			
		||||
    }
 | 
			
		||||
    crate::repository::push_repo(repo).expect("Failed to push to origin");
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								src/prompt.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/prompt.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
use dialoguer::{Input, MultiSelect, Select};
 | 
			
		||||
 | 
			
		||||
pub fn select<F>(title: Option<&str>, mut options: Vec<F>) -> F
 | 
			
		||||
    where F: ToString
 | 
			
		||||
{
 | 
			
		||||
    let mut sel = Select::new();
 | 
			
		||||
    sel.items(&options);
 | 
			
		||||
    sel.default(0);
 | 
			
		||||
    if let Some(title) = title {
 | 
			
		||||
        sel.with_prompt(title);
 | 
			
		||||
    }
 | 
			
		||||
    let sel = sel.interact().expect("User didn't enter anything");
 | 
			
		||||
 | 
			
		||||
    options.remove(sel)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn multi_select<F>(title: Option<&str>, options: Vec<F>) -> Vec<F>
 | 
			
		||||
    where F: ToString
 | 
			
		||||
{
 | 
			
		||||
    let mut sel = MultiSelect::new();
 | 
			
		||||
    sel.items(&options);
 | 
			
		||||
    if let Some(title) = title {
 | 
			
		||||
        sel.with_prompt(title);
 | 
			
		||||
    }
 | 
			
		||||
    let sel = sel.interact().expect("User didn't enter anything");
 | 
			
		||||
 | 
			
		||||
    options.into_iter()
 | 
			
		||||
        .enumerate()
 | 
			
		||||
        .filter_map(|(i, v)| sel.contains(&i).then_some(v))
 | 
			
		||||
        .collect()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn input(title: &str) -> String {
 | 
			
		||||
    Input::new()
 | 
			
		||||
        .with_prompt(title)
 | 
			
		||||
        .interact_text()
 | 
			
		||||
        .expect("User didn't enter anything")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										131
									
								
								src/repository.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								src/repository.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use git2::Repository;
 | 
			
		||||
 | 
			
		||||
fn generate_callbacks(use_ssh_agent: bool, pb: &mut indicatif::ProgressBar) -> git2::RemoteCallbacks {
 | 
			
		||||
    let mut callbacks = git2::RemoteCallbacks::new();
 | 
			
		||||
 | 
			
		||||
    if use_ssh_agent {
 | 
			
		||||
        callbacks.credentials(|_, username, _| git2::Cred::ssh_key_from_agent(username.unwrap()));
 | 
			
		||||
    } else {
 | 
			
		||||
        callbacks.credentials(|_, username, _| git2::Cred::ssh_key(
 | 
			
		||||
            username.unwrap(),
 | 
			
		||||
            None,
 | 
			
		||||
            Path::new(&format!("{}/.ssh/id_rsa", std::env::var("HOME").unwrap_or(String::new()))),
 | 
			
		||||
            None
 | 
			
		||||
        ));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    callbacks.transfer_progress(|progress| {
 | 
			
		||||
        if progress.total_deltas() == 0 {
 | 
			
		||||
            pb.set_length(progress.total_objects().max(1) as u64);
 | 
			
		||||
            pb.set_position(progress.received_objects() as u64);
 | 
			
		||||
            pb.set_message("Receiving objects");
 | 
			
		||||
        } else {
 | 
			
		||||
            pb.set_length(progress.total_deltas().max(1) as u64);
 | 
			
		||||
            pb.set_position(progress.total_objects() as u64);
 | 
			
		||||
            pb.set_message("Resolving deltas");
 | 
			
		||||
        }
 | 
			
		||||
        true
 | 
			
		||||
    });
 | 
			
		||||
    callbacks.push_transfer_progress(|cur, tot, _| {
 | 
			
		||||
        pb.set_length(tot.max(1) as u64);
 | 
			
		||||
        pb.set_position(cur as u64);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    callbacks
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn _clone_repo(callbacks: git2::RemoteCallbacks) -> Result<Repository, git2::Error> {
 | 
			
		||||
    let mut fo = git2::FetchOptions::new();
 | 
			
		||||
    fo.remote_callbacks(callbacks);
 | 
			
		||||
 | 
			
		||||
    let mut builder = git2::build::RepoBuilder::new();
 | 
			
		||||
    builder.fetch_options(fo);
 | 
			
		||||
 | 
			
		||||
    builder.clone(
 | 
			
		||||
        "git@ssh.gitlab.mattv.de:root/dotfiles.git",
 | 
			
		||||
        Path::new(".")
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn clone_repo() -> Result<Repository, git2::Error> {
 | 
			
		||||
    println!("Cloning repo...");
 | 
			
		||||
    let mut pb = indicatif::ProgressBar::new(1);
 | 
			
		||||
    pb.set_style(indicatif::ProgressStyle::with_template("{spinner} [{wide_bar}] {pos:>7}/{len:7} {msg}").unwrap().progress_chars("#>-"));
 | 
			
		||||
    _clone_repo(generate_callbacks(true, &mut pb))
 | 
			
		||||
        .or_else(|_| _clone_repo(generate_callbacks(false, &mut pb)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn _pull_repo(repo: &Repository, callbacks: git2::RemoteCallbacks) -> Result<(), git2::Error> {
 | 
			
		||||
    let mut fo = git2::FetchOptions::new();
 | 
			
		||||
    fo.remote_callbacks(callbacks);
 | 
			
		||||
 | 
			
		||||
    let mut co = git2::build::CheckoutBuilder::new();
 | 
			
		||||
    co.force();
 | 
			
		||||
 | 
			
		||||
    repo.find_remote("origin").unwrap().fetch(&["main"], Some(&mut fo), None)?;
 | 
			
		||||
 | 
			
		||||
    let mut commits = vec![];
 | 
			
		||||
    repo.fetchhead_foreach(|name, url, oid, was_merge| if was_merge { commits.push(repo.annotated_commit_from_fetchhead(name, &String::from_utf8_lossy(url), oid)); true} else { true })?;
 | 
			
		||||
    let commits = commits.into_iter().collect::<Result<Vec<_>, git2::Error>>()?;
 | 
			
		||||
    if commits.is_empty() {
 | 
			
		||||
        panic!("Failed to get heads from origin!");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    repo.merge(commits.iter().collect::<Vec<_>>().as_slice(), None, Some(&mut co))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn pull_repo(repo: &Repository) -> Result<(), git2::Error> {
 | 
			
		||||
    println!("Pulling repo...");
 | 
			
		||||
    let mut pb = indicatif::ProgressBar::new(1);
 | 
			
		||||
    pb.set_style(indicatif::ProgressStyle::with_template("{spinner} [{wide_bar}] {pos:>7}/{len:7} {msg}").unwrap().progress_chars("#>-"));
 | 
			
		||||
    _pull_repo(repo, generate_callbacks(true, &mut pb))
 | 
			
		||||
        .or_else(|_| _pull_repo(repo, generate_callbacks(false, &mut pb)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn _push_repo(repo: &Repository, callbacks: git2::RemoteCallbacks) -> Result<(), git2::Error> {
 | 
			
		||||
    let mut pu = git2::PushOptions::new();
 | 
			
		||||
    pu.remote_callbacks(callbacks);
 | 
			
		||||
 | 
			
		||||
    let mut remote = repo.find_remote("origin").unwrap();
 | 
			
		||||
 | 
			
		||||
    remote.push::<&str>(&["refs/heads/main:refs/heads/main"], Some(&mut pu))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn push_repo(repo: &Repository) -> Result<(), git2::Error> {
 | 
			
		||||
    println!("Pushing repo...");
 | 
			
		||||
    let mut pb = indicatif::ProgressBar::new(1);
 | 
			
		||||
    pb.set_style(indicatif::ProgressStyle::with_template("{spinner} [{wide_bar}] {bytes}/{total_bytes} {msg}").unwrap().progress_chars("#>-"));
 | 
			
		||||
    _push_repo(repo, generate_callbacks(true, &mut pb))
 | 
			
		||||
        .or_else(|_| _push_repo(repo, generate_callbacks(false, &mut pb)))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn open_repo() -> Result<Repository, String> {
 | 
			
		||||
    Repository::open(".")
 | 
			
		||||
        .or_else(|_| clone_repo())
 | 
			
		||||
        .map_err(|e| e.message().to_string())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn is_clean(repo: &Repository) -> bool {
 | 
			
		||||
    let mut so = git2::StatusOptions::new();
 | 
			
		||||
    so.show(git2::StatusShow::IndexAndWorkdir);
 | 
			
		||||
    so.include_untracked(true);
 | 
			
		||||
    so.include_ignored(false);
 | 
			
		||||
    so.include_unmodified(false);
 | 
			
		||||
 | 
			
		||||
    repo.statuses(Some(&mut so)).map_or(false, |s| s.is_empty())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn commit_changes(repo: &Repository, msg: String) {
 | 
			
		||||
    let sig = git2::Signature::now("Mutzi", "root@mattv.de").unwrap();
 | 
			
		||||
 | 
			
		||||
    let mut index = repo.index().unwrap();
 | 
			
		||||
    index.add_all(["*"], git2::IndexAddOption::DEFAULT, None).unwrap();
 | 
			
		||||
 | 
			
		||||
    let tree_id =  index.write_tree().unwrap();
 | 
			
		||||
    let tree = repo.find_tree(tree_id).unwrap();
 | 
			
		||||
    let parent_id = repo.refname_to_id("HEAD").unwrap();
 | 
			
		||||
    let parent = repo.find_commit(parent_id).unwrap();
 | 
			
		||||
 | 
			
		||||
    repo.commit(Some("HEAD"), &sig, &sig, msg.as_str(), &tree, &[&parent]).unwrap();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								src/utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/utils.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
use fs_extra::dir::DirContent;
 | 
			
		||||
 | 
			
		||||
pub fn get_dir_content_filtered(p: &std::path::Path, ignored: &Vec<String>) -> Result<DirContent, String> {
 | 
			
		||||
    let p = p.canonicalize().map_err(|e| e.to_string())?;
 | 
			
		||||
    let base_path_len = p.to_str().unwrap().len() + 1;
 | 
			
		||||
 | 
			
		||||
    let retain_fn = |p: &String| {
 | 
			
		||||
        if p.len() < base_path_len { return false; }
 | 
			
		||||
        let rel_path = p.split_at(base_path_len).1;
 | 
			
		||||
        !ignored.iter().any(|i| rel_path.starts_with(i))
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let mut content = fs_extra::dir::get_dir_content(p).map_err(|e| e.to_string())?;
 | 
			
		||||
    content.files.retain(retain_fn);
 | 
			
		||||
    content.directories.retain(retain_fn);
 | 
			
		||||
    Ok(content)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn trim_dir_content(mut content: DirContent, base: &std::path::Path) -> DirContent {
 | 
			
		||||
    let base_path_len = base.canonicalize().unwrap().to_str().unwrap().len() + 1;
 | 
			
		||||
 | 
			
		||||
    let iter_fn = |p: &mut String| {
 | 
			
		||||
        if p.len() < base_path_len { return; }
 | 
			
		||||
        let _ = p.drain(..base_path_len).collect::<Vec<_>>();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    content.files.iter_mut().for_each(iter_fn);
 | 
			
		||||
    content.directories.iter_mut().for_each(iter_fn);
 | 
			
		||||
    content
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn copy_dir(src_dir: &std::path::Path, dst_dir: &std::path::Path, spinner: &indicatif::ProgressBar) -> Result<(), String> {
 | 
			
		||||
    let content = src_dir.read_dir().map_err(|e| e.to_string())?;
 | 
			
		||||
    for entry in content {
 | 
			
		||||
        let entry = entry.map_err(|e| e.to_string())?;
 | 
			
		||||
        let new_dst = dst_dir.join(entry.file_name());
 | 
			
		||||
        if entry.path().is_file() {
 | 
			
		||||
            std::fs::copy(entry.path(), new_dst).map_err(|e| e.to_string())?;
 | 
			
		||||
            spinner.tick();
 | 
			
		||||
        } else {
 | 
			
		||||
            std::fs::create_dir_all(&new_dst).map_err(|e| e.to_string())?;
 | 
			
		||||
            spinner.tick();
 | 
			
		||||
            copy_dir(&entry.path(), &new_dst, spinner)?;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user