diff options
| author | ottjk <joshott16@gmail.com> | 2023-12-30 19:40:27 -0500 |
|---|---|---|
| committer | ottjk <joshott16@gmail.com> | 2023-12-30 19:40:27 -0500 |
| commit | 6536f3aae95e266f80a5a73288e181ff10ce650b (patch) | |
| tree | 2fa309c246ab4ad860da8673a82dcb4ead924919 /src/commands | |
| download | dfa-6536f3aae95e266f80a5a73288e181ff10ce650b.tar.gz dfa-6536f3aae95e266f80a5a73288e181ff10ce650b.zip | |
initial commit
Diffstat (limited to 'src/commands')
| -rw-r--r-- | src/commands/mod.rs | 88 | ||||
| -rw-r--r-- | src/commands/sync.rs | 125 |
2 files changed, 213 insertions, 0 deletions
diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..b1c5f51 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,88 @@ +use std::{ + fs, + io::{self,Result,Write}, + path::PathBuf, +}; + +mod sync; + +use crate::{Config,Entry}; +use crate::arguments::*; + +fn save_config(config: &Config, path: &PathBuf) -> Result<()> { + let toml = toml::to_string(config).unwrap(); + fs::write(path.join("configs.toml"), toml)?; + Ok(()) +} + +pub fn modify_command( + config: &mut Config, + args: ModifyArgs, + configs_path: &PathBuf, +) -> Result<()> { + + if config.contains_key(&args.name) { loop { + let mut input = String::new(); + + print!("Entry already exists for {}. Overwrite it? (y/n) ", &args.name); + let _ = io::stdout().flush(); + io::stdin() + .read_line(&mut input) + .expect("Failed to read user input."); + + match input.to_lowercase().trim() { + "y" => break, + "n" => return Ok(()), + _ => { + println!("Try again :)"); + continue; + }, + }; + }} + + let entry = Entry { file: args.file, parent: args.dest }; + config.insert(args.name, entry); + save_config(&config, configs_path)?; + + Ok(()) +} + +pub fn remove_command( + config: &mut Config, + args: RemoveArgs, + configs_path: &PathBuf +) -> Result<()> { + + if !config.contains_key(&args.name) { + panic!("Such an entry does not exist."); + } + config.remove(&args.name); + save_config(&config, configs_path)?; + + Ok(()) +} + +pub fn list_command(config: &Config) { + for (key,entry) in config { + println!(r#""{}" - src: {}, dest: {}"#, key, entry.file.to_str().unwrap(), entry.parent.to_str().unwrap()); + } +} + +pub fn sync_command(config: &Config, args: SyncArgs, configs_path: &PathBuf) { + if args.all { + sync::sync_all(config, configs_path, args.force); + return + } + + for name in &args.names { + let entry = match config.get(name) { + Some(e) => e, + None => { + eprintln!(r#"Entry "{name}" not found."#); + continue; + }, + }; + + sync::sync_config(name, entry, configs_path, &args.force); + } +} diff --git a/src/commands/sync.rs b/src/commands/sync.rs new file mode 100644 index 0000000..eaf3dab --- /dev/null +++ b/src/commands/sync.rs @@ -0,0 +1,125 @@ +use std::{ + io::{self,ErrorKind,Write}, + fs, + path::PathBuf, +}; +use copy_dir::copy_dir; + +use crate::{resolve_home,Config,Entry}; + +#[derive(Debug, PartialEq, Eq)] +enum ConflictOption { + Remove, + Rename, + Skip, +} + +fn remove_ambiguous(path: &PathBuf) -> io::Result<()> { + if path.is_dir() { + fs::remove_dir_all(&path)?; + } else { + fs::remove_file(&path)?; + } + + Ok(()) +} + +fn rename_conflict(path: &PathBuf) -> io::Result<()> { + let rename_path = path.with_extension("old"); + + if rename_path.try_exists()? { + remove_ambiguous(&rename_path)?; + } + + fs::rename(&path, &rename_path)?; + println!("Old config renamed to {}.", rename_path.file_name().unwrap().to_str().unwrap()); + + Ok(()) +} + +fn remove_existing(name: &str, path: &PathBuf) -> io::Result<Option<ConflictOption>> { + loop { + let mut input = String::new(); + + print!("Remove existing config for {name}? (y/n/r[ename]) "); + let _ = io::stdout().flush(); + io::stdin() + .read_line(&mut input) + .expect("Failed to read user input."); + + let choice = match input.to_lowercase().trim() { + "y" => ConflictOption::Remove, + "n" => ConflictOption::Skip, + "r" => ConflictOption::Rename, + _ => { + println!("Try again :)"); + continue; + }, + }; + + match choice { + ConflictOption::Remove => + remove_ambiguous(path)?, + ConflictOption::Rename => + rename_conflict(path)?, + ConflictOption::Skip => + println!("Skipping {name}."), + }; + + return Ok(Some(choice)); + } +} + +fn sync_file( + name: &str, + src: &PathBuf, + dest: &PathBuf, + force: &bool +) -> io::Result<Option<ConflictOption>> { + + let copy_result = copy_dir(src, dest); + + if let Err(error) = copy_result { + if error.kind() == ErrorKind::AlreadyExists { + if *force { + remove_ambiguous(dest)?; + return Ok(Some(ConflictOption::Remove)); + } else { + return remove_existing(name, dest); + } + } + return Err(error); + } + + Ok(None) +} + +pub fn sync_config(name: &str, entry: &Entry, configs_path: &PathBuf, force: &bool) { + let src = configs_path.join(&entry.file); + let mut dest = entry.parent.join(&entry.file); + resolve_home(&mut dest); + + loop { + let sync_result = sync_file(name, &src, &dest, force); + + match sync_result { + Ok(None) => println!("Updated {}.", dest.to_str().unwrap()), + Ok(Some(choice)) => match choice { + ConflictOption::Skip => break, + _ => continue, + }, + Err(error) => { + eprintln!("Skipping sync of {src:?} to {dest:?}: {error:?}"); + break; + }, + }; + + break; + } +} + +pub fn sync_all(config: &Config, configs_path: &PathBuf, force: bool) { + for (name, entry) in config { + sync_config(&name, &entry, configs_path, &force); + } +} |