aboutsummaryrefslogtreecommitdiff
path: root/src/commands
diff options
context:
space:
mode:
authorottjk <joshott16@gmail.com>2023-12-30 19:40:27 -0500
committerottjk <joshott16@gmail.com>2023-12-30 19:40:27 -0500
commit6536f3aae95e266f80a5a73288e181ff10ce650b (patch)
tree2fa309c246ab4ad860da8673a82dcb4ead924919 /src/commands
downloaddfa-6536f3aae95e266f80a5a73288e181ff10ce650b.tar.gz
dfa-6536f3aae95e266f80a5a73288e181ff10ce650b.zip
initial commit
Diffstat (limited to 'src/commands')
-rw-r--r--src/commands/mod.rs88
-rw-r--r--src/commands/sync.rs125
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);
+ }
+}