From b030ce6a533c5271aa3497a04cd70a0ad87c93a6 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Sat, 1 Jul 2023 17:29:53 +0200 Subject: [PATCH] Add subcommand 'diff' to compare two OSC files --- README.md | 14 ++++ src/cli.rs | 7 ++ src/main.rs | 17 +++++ src/model/data_catalogue.rs | 12 +++- src/model/data_form.rs | 14 +++- src/model/mod.rs | 13 ++++ src/model/onkostar_editor.rs | 113 +++++++++++++++++++++++++++++++- src/model/property_catalogue.rs | 12 +++- src/model/unterformular.rs | 14 +++- 9 files changed, 209 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a5b37ee..b17d5bf 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,20 @@ Zum Auflisten der Inhalte einer Datei wird folgender Befehl verwendet: osc-variant list meine-beispieldatei.osc ``` +Zum Vergleich zweier OSC-Dateien wird der Unterbefehl `diff` verwendet. +Der optionale Parameter `--strict` vergleicht auch den Inhalt der OSC-Datei. +Ohne diesen wird nur das Vorhandensein von Inhalten und die Revision verglichen. + +``` +osc-variant list meine-beispieldatei.osc andere-beispieldatei.osc +``` + +bzw. + +``` +osc-variant list meine-beispieldatei.osc andere-beispieldatei.osc --strict +``` + Zum Anpassen des Inhalts einer Datei: ``` diff --git a/src/cli.rs b/src/cli.rs index 2899d4f..eb048a7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -53,4 +53,11 @@ pub enum Command { #[arg(long = "compact", help = "Kompakte Ausgabe, ohne Einrücken (Optional)")] compact: bool, }, + #[command(about = "Vergleiche zwei Dateien anhand der Revision der enthaltenen Inhalte")] + Diff { + inputfile_a: String, + inputfile_b: String, + #[arg(long = "strict", help = "Strikter Vergleich des Inhalts")] + strict: bool, + }, } diff --git a/src/main.rs b/src/main.rs index 6cc0ed9..79bbda3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ use std::ops::Add; use std::str::FromStr; use clap::Parser; +use console::style; use quick_xml::se::Serializer; use serde::Serialize; @@ -157,6 +158,22 @@ fn main() -> Result<(), Box> { } } } + Command::Diff { + inputfile_a, + inputfile_b, + strict, + } => { + println!( + "Vergleiche Datei A ({}) mit Datei B ({})", + style(&inputfile_a).yellow(), + style(&inputfile_b).yellow() + ); + + let data_a = &mut read_inputfile(inputfile_a)?; + let data_b = &mut read_inputfile(inputfile_b)?; + + data_a.print_diff(data_b, strict); + } }; Ok(()) diff --git a/src/model/data_catalogue.rs b/src/model/data_catalogue.rs index 4160e84..02d3dd7 100644 --- a/src/model/data_catalogue.rs +++ b/src/model/data_catalogue.rs @@ -25,7 +25,7 @@ use console::style; use serde::{Deserialize, Serialize}; -use crate::model::{Listable, Ordner, Sortable}; +use crate::model::{Comparable, Listable, Ordner, Sortable}; #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] @@ -72,6 +72,16 @@ impl Sortable for DataCatalogue { } } +impl Comparable for DataCatalogue { + fn get_name(&self) -> String { + self.name.clone() + } + + fn get_revision(&self) -> u16 { + self.revision + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct Entries { diff --git a/src/model/data_form.rs b/src/model/data_form.rs index 4314b23..eff5eab 100644 --- a/src/model/data_form.rs +++ b/src/model/data_form.rs @@ -26,8 +26,8 @@ use console::style; use serde::{Deserialize, Serialize}; use crate::model::{ - apply_profile_to_form_entry, Ansichten, Entries, Filter, FormEntry, FormEntryContainer, - Listable, MenuCategory, PlausibilityRules, Script, Sortable, + apply_profile_to_form_entry, Ansichten, Comparable, Entries, Filter, FormEntry, + FormEntryContainer, Listable, MenuCategory, PlausibilityRules, Script, Sortable, }; use crate::model::{Haeufigkeiten, Ordner}; use crate::profile::Profile; @@ -194,6 +194,16 @@ impl Sortable for DataForm { } } +impl Comparable for DataForm { + fn get_name(&self) -> String { + self.name.clone() + } + + fn get_revision(&self) -> u16 { + self.revision + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct DataCatalogues { diff --git a/src/model/mod.rs b/src/model/mod.rs index 6dc72cf..67783d2 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -24,6 +24,9 @@ use crate::profile::{FormReference, Profile}; use serde::{Deserialize, Serialize}; +use std::collections::hash_map::DefaultHasher; +use std::fmt::Debug; +use std::hash::{Hash, Hasher}; pub mod data_catalogue; pub mod data_form; @@ -238,6 +241,16 @@ pub trait Sortable { fn sorting_key(&self) -> String; } +pub trait Comparable: Debug { + fn get_name(&self) -> String; + fn get_revision(&self) -> u16; + fn get_hash(&self) -> String { + let mut h = DefaultHasher::new(); + format!("{:?}", self).hash(&mut h); + h.finish().to_string() + } +} + pub trait FormEntry { fn get_name(&self) -> String; fn get_type(&self) -> String; diff --git a/src/model/onkostar_editor.rs b/src/model/onkostar_editor.rs index 577be62..590cd71 100644 --- a/src/model/onkostar_editor.rs +++ b/src/model/onkostar_editor.rs @@ -25,13 +25,15 @@ use console::style; use quick_xml::de::from_str; use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::fmt::Debug; use std::str::FromStr; use crate::model::data_catalogue::DataCatalogue; use crate::model::data_form::DataForm; use crate::model::property_catalogue::PropertyCatalogue; use crate::model::unterformular::Unterformular; -use crate::model::{FormEntryContainer, Listable, Sortable}; +use crate::model::{Comparable, FormEntryContainer, Listable, Sortable}; use crate::profile::Profile; #[derive(Serialize, Deserialize, Debug)] @@ -89,6 +91,115 @@ impl OnkostarEditor { .unterformular .sort_unstable_by_key(|e| e.sorting_key()); } + + pub fn print_diff(&mut self, other: &mut Self, strict: bool) { + self.sorted(); + other.sorted(); + + Self::print_item_diff( + "Merkmalskataloge", + &self.editor.property_catalogue, + &other.editor.property_catalogue, + strict, + ); + Self::print_item_diff( + "Datenkataloge", + &self.editor.data_catalogue, + &other.editor.data_catalogue, + strict, + ); + Self::print_item_diff( + "Formulare", + &self.editor.data_form, + &other.editor.data_form, + strict, + ); + Self::print_item_diff( + "Unterformulare", + &self.editor.unterformular, + &other.editor.unterformular, + strict, + ); + } + + fn print_item_diff( + title: &str, + list_a: &[impl Comparable], + list_b: &[impl Comparable], + strict: bool, + ) { + println!("\n{}", style(title).underlined()); + + let mut has_diff = false; + + let names_a = list_a + .iter() + .map(|entry| entry.get_name()) + .collect::>(); + let names_b = list_b + .iter() + .map(|entry| entry.get_name()) + .collect::>(); + + names_b.iter().for_each(|entry| { + if !names_a.contains(entry) { + println!("{}: {}", entry, style("Nicht in Datei A enthalten!").red()); + has_diff = true; + } + }); + + names_a.iter().for_each(|entry| { + if !names_b.contains(entry) { + println!("{}: {}", entry, style("Nicht in Datei B enthalten!").red()); + has_diff = true; + } + }); + + list_a.iter().for_each(|entry_a| { + list_b.iter().for_each(|entry_b| { + if entry_a.get_name() == entry_b.get_name() { + match entry_a.get_revision().cmp(&entry_b.get_revision()) { + Ordering::Less => { + println!( + "{}: {} (Revision {} < Revision {})", + entry_a.get_name(), + style("Neuer in Datei B").yellow(), + style(entry_a.get_revision()).blue(), + style(entry_b.get_revision()).green() + ); + has_diff = true; + } + Ordering::Greater => { + println!( + "{}: {} (Revision {} > Revision {})", + entry_a.get_name(), + style("Neuer in Datei A").yellow(), + style(entry_a.get_revision()).green(), + style(entry_b.get_revision()).blue() + ); + has_diff = true; + } + _ => { + if strict && entry_a.get_hash() != entry_b.get_hash() { + println!( + "{}: {} (z.B. Reihenfolge von Unterelementen)", + entry_a.get_name(), + style("Inhaltlich verschieden").yellow() + ); + has_diff = true; + } else if strict { + println!("{}: {}", entry_a.get_name(), style("Identisch").green()) + } + } + } + } + }); + }); + + if !has_diff { + println!("Keine Unterschiede") + } + } } impl FromStr for OnkostarEditor { diff --git a/src/model/property_catalogue.rs b/src/model/property_catalogue.rs index 21f9393..72f8256 100644 --- a/src/model/property_catalogue.rs +++ b/src/model/property_catalogue.rs @@ -25,7 +25,7 @@ use console::style; use serde::{Deserialize, Serialize}; -use crate::model::{Listable, Ordner, Sortable}; +use crate::model::{Comparable, Listable, Ordner, Sortable}; #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] @@ -69,6 +69,16 @@ impl Sortable for PropertyCatalogue { } } +impl Comparable for PropertyCatalogue { + fn get_name(&self) -> String { + self.name.clone() + } + + fn get_revision(&self) -> u16 { + self.revision + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct Versions { diff --git a/src/model/unterformular.rs b/src/model/unterformular.rs index 491d256..01a60c1 100644 --- a/src/model/unterformular.rs +++ b/src/model/unterformular.rs @@ -26,8 +26,8 @@ use console::style; use serde::{Deserialize, Serialize}; use crate::model::{ - apply_profile_to_form_entry, Ansichten, Entries, Filter, FormEntry, FormEntryContainer, - Listable, MenuCategory, PlausibilityRules, Script, Sortable, + apply_profile_to_form_entry, Ansichten, Comparable, Entries, Filter, FormEntry, + FormEntryContainer, Listable, MenuCategory, PlausibilityRules, Script, Sortable, }; use crate::model::{Haeufigkeiten, Ordner}; use crate::profile::Profile; @@ -213,6 +213,16 @@ impl Sortable for Unterformular { } } +impl Comparable for Unterformular { + fn get_name(&self) -> String { + self.name.clone() + } + + fn get_revision(&self) -> u16 { + self.revision + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct DataCatalogues {