diff --git a/Cargo.lock b/Cargo.lock index 12b49e6..63b0606 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,9 +219,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.7" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ "clap_builder", "clap_derive", @@ -229,9 +229,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.7" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" dependencies = [ "anstream", "anstyle", @@ -317,9 +317,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ "libc", "windows-sys", @@ -347,9 +347,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -421,9 +421,9 @@ dependencies = [ [[package]] name = "keepass" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69be91e6aeada33fca98962d6e801504dd013231721a29926677da2823e961b" +checksum = "8a6df0c3b53406095e6b36d23efb89640c66ddc6dffdce2160e0023713764260" dependencies = [ "aes", "base64", @@ -467,9 +467,9 @@ checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "log" @@ -503,9 +503,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] @@ -521,23 +521,23 @@ dependencies = [ [[package]] name = "rpassword" -version = "7.2.0" +version = "7.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" +checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" dependencies = [ "libc", "rtoolbox", - "winapi", + "windows-sys", ] [[package]] name = "rtoolbox" -version = "0.0.1" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" +checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" dependencies = [ "libc", - "winapi", + "windows-sys", ] [[package]] @@ -553,9 +553,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ "bitflags", "errno", @@ -584,18 +584,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.190" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.190" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -627,9 +627,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.38" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -638,9 +638,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] @@ -704,9 +704,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" +checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ "getrandom", "serde", @@ -892,9 +892,9 @@ checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index ae14b88..656e0f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,12 @@ license = "MIT" readme = "README.md" version = "1.2.0" authors = ["Joern Bernhardt "] +edition = "2021" # See https://crates.io/category_slugs categories = ["command-line-utilities"] -exclude = [ - "docs/*" -] +exclude = ["docs/*"] [dependencies] base64 = "0.21.5" diff --git a/src/diff/entry.rs b/src/diff/entry.rs index ea3c48c..a6e533c 100644 --- a/src/diff/entry.rs +++ b/src/diff/entry.rs @@ -1,43 +1,29 @@ -use base64::{engine::general_purpose, Engine as _}; -use keepass::db::Value; use std::collections::HashMap; -use crate::diff::field::{Field, ValueType}; +use crate::diff::field::Field; use crate::diff::{Diff, DiffResult, DiffResultFormat}; #[derive(Clone, Debug, PartialEq, Eq)] pub struct Entry { - pub fields: HashMap, + fields: HashMap, use_verbose: bool, mask_passwords: bool, } impl Entry { - pub fn from_keepass(e: &keepass::db::Entry, use_verbose: bool, mask_passwords: bool) -> Self { + pub fn from_keepass( + entry: &keepass::db::Entry, + use_verbose: bool, + mask_passwords: bool, + ) -> Self { // username, password, etc. are just fields - let fields = e + let fields = entry .fields .iter() - .map(|(k, v)| { + .map(|(key, value)| { ( - k.to_owned(), - Field { - name: k.to_owned(), - value: match v { - Value::Bytes(b) => general_purpose::STANDARD_NO_PAD.encode(b), - Value::Unprotected(v) => v.to_owned(), - Value::Protected(p) => String::from_utf8(p.unsecure().to_owned()) - .unwrap() - .to_owned(), - }, - kind: match v { - Value::Bytes(_) => ValueType::Binary, - Value::Unprotected(_) => ValueType::Unprotected, - Value::Protected(_) => ValueType::Protected, - }, - use_verbose, - mask_passwords, - }, + key.to_owned(), + Field::from_keepass(key.to_owned(), value, use_verbose, mask_passwords), ) }) .collect(); @@ -56,11 +42,10 @@ impl Diff for Entry { crate::diff::diff_entry(&self.fields, &other.fields); if has_differences { - let mut inner_differences: Vec> = Vec::new(); - - for dr in field_differences { - inner_differences.push(Box::new(dr)) - } + let inner_differences = field_differences + .into_iter() + .map(|dr| Box::new(dr) as Box) + .collect(); DiffResult::InnerDifferences { left: self, @@ -78,18 +63,7 @@ impl Diff for Entry { impl std::fmt::Display for Entry { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let name = self - .fields - .get("Title") - .unwrap_or(&Field { - name: "Title".to_string(), - value: "".to_string(), - kind: ValueType::Unprotected, - use_verbose: self.use_verbose, - mask_passwords: self.mask_passwords, - }) - .value - .clone(); + let name = self.fields.get("Title").map(|f| f.value()).unwrap_or(""); if self.use_verbose { write!(f, "Entry '{}'", name) } else { diff --git a/src/diff/field.rs b/src/diff/field.rs index 60225e5..d30c749 100644 --- a/src/diff/field.rs +++ b/src/diff/field.rs @@ -1,3 +1,6 @@ +use base64::{engine::general_purpose, Engine}; +use keepass::db::Value; + use crate::diff::{Diff, DiffResult}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -9,11 +12,42 @@ pub enum ValueType { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Field { - pub name: String, - pub value: String, - pub kind: ValueType, - pub use_verbose: bool, - pub mask_passwords: bool, + name: String, + value: String, + kind: ValueType, + use_verbose: bool, + mask_passwords: bool, +} + +impl Field { + pub fn from_keepass( + name: String, + value: &Value, + use_verbose: bool, + mask_passwords: bool, + ) -> Self { + Field { + name, + value: match value { + Value::Bytes(b) => general_purpose::STANDARD_NO_PAD.encode(b), + Value::Unprotected(v) => v.to_owned(), + Value::Protected(p) => String::from_utf8(p.unsecure().to_owned()) + .unwrap() + .to_owned(), + }, + kind: match value { + Value::Bytes(_) => ValueType::Binary, + Value::Unprotected(_) => ValueType::Unprotected, + Value::Protected(_) => ValueType::Protected, + }, + use_verbose, + mask_passwords, + } + } + + pub fn value(&self) -> &str { + self.value.as_str() + } } impl Diff for Field { @@ -34,26 +68,14 @@ impl Diff for Field { impl std::fmt::Display for Field { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let password_str = match (self.mask_passwords, self.kind) { + (true, ValueType::Protected) => "***", + _ => self.value.as_str(), + }; if self.use_verbose { - write!( - f, - "Field '{}' = '{}'", - self.name, - match (self.mask_passwords, self.kind) { - (true, ValueType::Protected) => "***".to_owned(), - _ => self.value.to_owned(), - } - ) + write!(f, "Field '{}' = '{}'", self.name, password_str) } else { - write!( - f, - "{} = {}", - self.name, - match (self.mask_passwords, self.kind) { - (true, ValueType::Protected) => "***".to_owned(), - _ => self.value.to_owned(), - } - ) + write!(f, "{} = {}", self.name, password_str) } } } diff --git a/src/diff/group.rs b/src/diff/group.rs index 9826eff..91bf86a 100644 --- a/src/diff/group.rs +++ b/src/diff/group.rs @@ -23,23 +23,21 @@ impl Group { let mut child_groups: HashMap> = HashMap::new(); for node in group.children.iter() { - match node { - keepass::db::Node::Group(g) => child_groups + if let keepass::db::Node::Group(g) = node { + child_groups .entry(g.name.clone()) - .or_insert(Vec::new()) - .push(Group::from_keepass(g, use_verbose, mask_passwords)), - _ => {} + .or_default() + .push(Group::from_keepass(g, use_verbose, mask_passwords)); } } let mut entries: HashMap> = HashMap::new(); for node in group.children.iter() { - match node { - keepass::db::Node::Entry(e) => entries + if let keepass::db::Node::Entry(e) = node { + entries .entry(e.get("Title").unwrap_or_default().to_owned()) - .or_insert(Vec::new()) - .push(Entry::from_keepass(e, use_verbose, mask_passwords)), - _ => {} + .or_default() + .push(Entry::from_keepass(e, use_verbose, mask_passwords)); } } @@ -72,15 +70,15 @@ impl Diff for Group { crate::diff::diff_hashmap(&self.entries, &other.entries); if has_differences_groups || has_differences_entries { - let mut inner_differences: Vec> = Vec::new(); - - for dr in acc_groups { - inner_differences.push(Box::new(dr)); - } - - for dr in acc_entries { - inner_differences.push(Box::new(dr)); - } + let inner_differences = acc_groups + .into_iter() + .map(|dr| Box::new(dr) as Box) + .chain( + acc_entries + .into_iter() + .map(|dr| Box::new(dr) as Box), + ) + .collect(); DiffResult::InnerDifferences { left: self, diff --git a/src/diff/mod.rs b/src/diff/mod.rs index 3a6f88d..c38666a 100644 --- a/src/diff/mod.rs +++ b/src/diff/mod.rs @@ -1,11 +1,10 @@ use std::collections::{HashMap, HashSet}; use termcolor::Color; -use stack::Stack; - -pub mod entry; -pub mod field; -pub mod group; +mod entry; +mod field; +mod group; +pub use group::Group; /// The possible outcomes of diffing two objects against another #[derive(Debug)] @@ -39,7 +38,7 @@ pub trait DiffResultFormat: std::fmt::Debug { fn diff_result_format( &self, f: &mut std::fmt::Formatter<'_>, - path: &Stack<&String>, + path: &[String], use_color: bool, use_verbose: bool, mask_passwords: bool, @@ -47,18 +46,30 @@ pub trait DiffResultFormat: std::fmt::Debug { } /// Helper wrapper to impl Display for a DiffResult with user-specified settings -pub struct DiffDisplay<'a, T: DiffResultFormat> { - pub inner: T, - pub path: Stack<&'a String>, - pub use_color: bool, - pub use_verbose: bool, - pub mask_passwords: bool, +pub struct DiffDisplay { + inner: T, + path: Vec, + use_color: bool, + use_verbose: bool, + mask_passwords: bool, } -impl<'a, T: DiffResultFormat> std::fmt::Display for DiffDisplay<'a, T> { - fn fmt(&self, mut f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl DiffDisplay { + pub fn new(inner: T, use_color: bool, use_verbose: bool, mask_passwords: bool) -> Self { + Self { + inner, + path: Vec::new(), + use_color, + use_verbose, + mask_passwords, + } + } +} + +impl std::fmt::Display for DiffDisplay { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let result = self.inner.diff_result_format( - &mut f, + f, &self.path, self.use_color, self.use_verbose, @@ -78,8 +89,8 @@ where { fn diff_result_format( &self, - mut f: &mut std::fmt::Formatter<'_>, - path: &Stack<&String>, + f: &mut std::fmt::Formatter<'_>, + path: &[String], use_color: bool, use_verbose: bool, mask_passwords: bool, @@ -92,26 +103,18 @@ where } if use_verbose { let indent = " ".repeat(path.len()); - write!(f, "- {}{}\n", indent, left)?; + writeln!(f, "- {}{}", indent, left)?; } else { - write!( - f, - "- {}\n", - path.append(&format!("{}", left)).mk_string("[", ", ", "]") - )?; + writeln!(f, "- [{}, {}]", path.join(", "), left)?; } if use_color { crate::set_fg(Some(Color::Green)); } if use_verbose { let indent = " ".repeat(path.len()); - write!(f, "+ {}{}\n", indent, right) + writeln!(f, "+ {}{}", indent, right) } else { - write!( - f, - "+ {}\n", - path.append(&format!("{}", right)).mk_string("[", ", ", "]") - ) + writeln!(f, "+ [{}, {}]", path.join(", "), right) } } DiffResult::InnerDifferences { @@ -124,16 +127,12 @@ where crate::set_fg(Some(Color::Yellow)); } let indent = " ".repeat(path.len()); - write!(f, "~ {}{}\n", indent, left)?; + writeln!(f, "~ {}{}", indent, left)?; } for id in inner_differences { - id.diff_result_format( - &mut f, - &path.append(&format!("{}", left)), - use_color, - use_verbose, - mask_passwords, - )?; + let mut new_path = path.to_owned(); + new_path.push(left.to_string()); + id.diff_result_format(f, &new_path, use_color, use_verbose, mask_passwords)?; } Ok(()) } @@ -143,13 +142,9 @@ where } if use_verbose { let indent = " ".repeat(path.len()); - write!(f, "- {}{}\n", indent, left) + writeln!(f, "- {}{}", indent, left) } else { - write!( - f, - "- {}\n", - path.append(&format!("{}", left)).mk_string("[", ", ", "]") - ) + writeln!(f, "- [{}, {}]", path.join(", "), left) } } DiffResult::OnlyRight { right } => { @@ -158,13 +153,9 @@ where } if use_verbose { let indent = " ".repeat(path.len()); - write!(f, "+ {}{}\n", indent, right) + writeln!(f, "+ {}{}", indent, right) } else { - write!( - f, - "+ {}\n", - path.append(&format!("{}", right)).mk_string("[", ", ", "]") - ) + writeln!(f, "+ [{}, {}]", path.join(", "), right) } } }; @@ -182,11 +173,13 @@ pub fn diff_entry<'a, A>( where A: Diff, { - let mut keys = HashSet::new(); - keys.extend(a.keys()); - keys.extend(b.keys()); + let mut keys = a + .keys() + .chain(b.keys()) + .collect::>() + .into_iter() + .collect::>(); - let mut keys: Vec<_> = keys.iter().collect(); keys.sort(); let mut acc: Vec> = Vec::new(); @@ -194,8 +187,8 @@ where let mut has_differences = false; for key in keys { - let el_a: Option<&A> = a.get(*key); - let el_b: Option<&A> = b.get(*key); + let el_a: Option<&A> = a.get(key); + let el_b: Option<&A> = b.get(key); match (el_a, el_b) { // both a and b have the key @@ -237,11 +230,13 @@ pub fn diff_hashmap<'a, A>( where A: Diff, { - let mut keys = HashSet::new(); - keys.extend(a.keys()); - keys.extend(b.keys()); + let mut keys = a + .keys() + .chain(b.keys()) + .collect::>() + .into_iter() + .collect::>(); - let mut keys: Vec<_> = keys.iter().collect(); keys.sort(); let mut acc: Vec> = Vec::new(); @@ -249,13 +244,13 @@ where let mut has_differences = false; for key in keys { - let el_a: Option<&Vec> = a.get(*key); - let el_b: Option<&Vec> = b.get(*key); + let el_a: Option<&Vec> = a.get(key); + let el_b: Option<&Vec> = b.get(key); match (el_a, el_b) { // both a and b have the key (Some(v_a), Some(v_b)) => { - v_a.into_iter() + v_a.iter() .enumerate() .for_each(|(index, value_a)| match v_b.get(index) { Some(value_b) => { @@ -274,7 +269,7 @@ where if v_a.len() < v_b.len() { has_differences = true; v_b[v_a.len()..] - .into_iter() + .iter() .for_each(|value_b| acc.push(DiffResult::OnlyRight { right: value_b })); } } @@ -282,13 +277,13 @@ where // only a has the key (Some(v_a), None) => { has_differences = true; - v_a.into_iter() + v_a.iter() .for_each(|e| acc.push(DiffResult::OnlyLeft { left: e })); } // only b has the key (None, Some(v_b)) => { has_differences = true; - v_b.into_iter() + v_b.iter() .for_each(|e| acc.push(DiffResult::OnlyRight { right: e })); } @@ -302,9 +297,7 @@ where #[cfg(test)] mod test { - - use super::*; - use diff::group::Group; + use super::{group::Group, *}; #[test] fn diff_empty_groups() { @@ -312,6 +305,6 @@ mod test { let b = HashMap::>::new(); let (has_differences, _) = diff_hashmap(&a, &b); - assert_eq!(false, has_differences); + assert!(!has_differences); } } diff --git a/src/main.rs b/src/main.rs index 44f243b..9f8e8f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,13 @@ -extern crate base64; -extern crate clap; -extern crate keepass; -extern crate rpassword; -extern crate termcolor; - -pub mod diff; -pub mod stack; +use std::fs::File; use clap::Parser; -use diff::{group::Group, Diff, DiffDisplay}; +use diff::DiffDisplay; use keepass::{error::DatabaseOpenError, Database, DatabaseKey}; - use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; -use std::fs::File; +use crate::diff::{Diff, Group}; + +mod diff; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] @@ -34,7 +28,7 @@ struct Args { #[clap(short = 'v', long)] verbose: bool, - /// Enables verbose output + /// Replaces passwords with placeholders #[clap(short = 'm', long = "mask-passwords")] mask_passwords: bool, @@ -82,68 +76,59 @@ struct Args { fn main() -> Result<(), ()> { let arguments = Args::parse(); - match (arguments.input_a, arguments.input_b) { - (file_a, file_b) => { - let pass_a = match ( - arguments.password_a, - arguments.passwords.clone(), - arguments.same_password, - arguments.no_password_a, - arguments.no_passwords, - ) { - (Some(password), _, _, _, _) => Some(String::from(password)), - (_, Some(password), _, _, _) => Some(String::from(password)), - (_, _, true, _, _) => prompt_password("Password for both files: "), - (_, _, _, true, _) => None, - (_, _, _, _, true) => None, - _ => prompt_password(format!("Password for file {}: ", file_a).as_str()), - }; - let pass_b = match ( - arguments.password_b, - arguments.passwords.clone(), - arguments.same_password, - arguments.no_password_b, - arguments.no_passwords, - ) { - (Some(password), _, _, _, _) => Some(String::from(password)), - (_, Some(password), _, _, _) => Some(String::from(password)), - (_, _, true, _, _) => pass_a.clone(), - (_, _, _, true, _) => None, - (_, _, _, _, true) => None, - _ => prompt_password(format!("Password for file {}: ", file_b).as_str()), - }; - let keyfile_a: Option = arguments.keyfile_a.or(arguments.keyfiles.clone()); - let keyfile_b: Option = arguments.keyfile_b.or(arguments.keyfiles.clone()); - let use_color: bool = !arguments.no_color; - let use_verbose: bool = arguments.verbose; - let mask_passwords: bool = arguments.mask_passwords; - - let db_a = kdbx_to_group(file_a, pass_a, keyfile_a, use_verbose, mask_passwords) - .expect("Error opening database A"); - let db_b = kdbx_to_group(file_b, pass_b, keyfile_b, use_verbose, mask_passwords) - .expect("Error opening database B"); - - let delta = db_a.diff(&db_b); - - println!( - "{}", - DiffDisplay { - inner: delta, - path: stack::Stack::empty(), - use_color, - use_verbose, - mask_passwords, - } - ); - } - } + let (file_a, file_b) = (arguments.input_a, arguments.input_b); + let pass_a = match ( + arguments.password_a, + arguments.passwords.clone(), + arguments.same_password, + arguments.no_password_a, + arguments.no_passwords, + ) { + (Some(password), _, _, _, _) => Some(password), + (_, Some(password), _, _, _) => Some(password), + (_, _, true, _, _) => prompt_password("Password for both files: "), + (_, _, _, true, _) => None, + (_, _, _, _, true) => None, + _ => prompt_password(format!("Password for file {}: ", file_a).as_str()), + }; + let pass_b = match ( + arguments.password_b, + arguments.passwords.clone(), + arguments.same_password, + arguments.no_password_b, + arguments.no_passwords, + ) { + (Some(password), _, _, _, _) => Some(password), + (_, Some(password), _, _, _) => Some(password), + (_, _, true, _, _) => pass_a.clone(), + (_, _, _, true, _) => None, + (_, _, _, _, true) => None, + _ => prompt_password(format!("Password for file {}: ", file_b).as_str()), + }; + let keyfile_a = arguments.keyfile_a.or(arguments.keyfiles.clone()); + let keyfile_b = arguments.keyfile_b.or(arguments.keyfiles.clone()); + let use_color = !arguments.no_color; + let use_verbose = arguments.verbose; + let mask_passwords = arguments.mask_passwords; + + let db_a = kdbx_to_group(file_a, pass_a, keyfile_a, use_verbose, mask_passwords) + .expect("Error opening database A"); + let db_b = kdbx_to_group(file_b, pass_b, keyfile_b, use_verbose, mask_passwords) + .expect("Error opening database B"); + + let delta = db_a.diff(&db_b); + + println!( + "{}", + DiffDisplay::new(delta, use_color, use_verbose, mask_passwords) + ); Ok(()) } fn prompt_password(prompt: &str) -> Option { rpassword::prompt_password(prompt) - .map(|s| if s == "" { None } else { Some(s) }) + .map(|s| if s.is_empty() { None } else { Some(s) }) .unwrap_or(None) } diff --git a/src/stack/mod.rs b/src/stack/mod.rs deleted file mode 100644 index 5eb73ae..0000000 --- a/src/stack/mod.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::rc::Rc; - -pub struct Stack { - head: Link, -} - -type Link = Option>>; - -struct Node { - value: T, - next: Link, -} - -impl Stack { - pub fn empty() -> Self { - Stack { head: None } - } - - pub fn append(&self, value: T) -> Stack { - Stack { - head: Some(Rc::new(Node { - value, - next: self.head.clone(), - })), - } - } - - pub fn head(&self) -> Option<&T> { - self.head.as_ref().map(|node| &node.value) - } - - pub fn tail(&self) -> Stack { - Stack { - head: self.head.as_ref().and_then(|node| node.next.clone()), - } - } - - pub fn len(&self) -> usize { - match self.head() { - Some(_) => 1 + self.tail().len(), - None => 0, - } - } -} - -impl Stack { - pub fn to_string(&self) -> String { - self.mk_string("Stack(", ", ", ")") - } - - pub fn mk_string( - &self, - start: &'static str, - separator: &'static str, - end: &'static str, - ) -> String { - match self.head() { - Some(value) => { - let tail = self.tail(); - let tail_string = tail.mk_string_helper(separator); - format!("{}{}{}{}", start, tail_string, value, end) - } - None => format!("{}{}", start, end), - } - } - - fn mk_string_helper(&self, separator: &'static str) -> String { - match self.head() { - Some(value) => format!( - "{}{}{}", - self.tail().mk_string_helper(separator), - value, - separator, - ), - None => format!(""), - } - } -} - -impl Drop for Stack { - fn drop(&mut self) { - let mut head = self.head.take(); - while let Some(node) = head { - if let Ok(mut node) = Rc::try_unwrap(node) { - head = node.next.take(); - } else { - break; - } - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - fn stack_abcd<'a>() -> Stack<&'a str> { - Stack::empty() - .append("a") - .append("b") - .append("c") - .append("d") - } - - #[test] - fn basics() { - let stack = Stack::empty(); - - assert_eq!(stack.head(), None); - - let stack = stack.append("a").append("b").append("c"); - - assert_eq!(stack.head(), Some(&"c")); - - let stack = stack.tail(); - assert_eq!(stack.head(), Some(&"b")); - let stack = stack.tail(); - assert_eq!(stack.head(), Some(&"a")); - let stack = stack.tail(); - assert_eq!(stack.head(), None); - } - - #[test] - fn correct_len() { - let stack: Stack<&str> = Stack::empty(); - assert_eq!(0, stack.len()); - - assert_eq!(2, stack.append("one").append("two").len()) - } - - #[test] - fn empty_stack() { - let stack: Stack<&str> = Stack::empty(); - assert_eq!("Stack()", format!("{}", stack.to_string())) - } - - #[test] - fn single_element_stack() { - let stack = Stack::empty().append("hello"); - assert_eq!("Stack(hello)", format!("{}", stack.to_string())) - } - - #[test] - fn two_elements_stack() { - let stack = Stack::empty().append("hello").append("bye"); - assert_eq!("Stack(hello, bye)", format!("{}", stack.to_string())) - } - - #[test] - fn shows_its_strings() { - let stack = stack_abcd(); - assert_eq!("Stack(a, b, c, d)", format!("{}", stack.to_string())) - } - - #[test] - fn mk_string_shows_correct() { - let stack = stack_abcd(); - assert_eq!( - "[a, b, c, d]", - format!("{}", stack.mk_string("[", ", ", "]")) - ) - } -}