1
0
mirror of https://github.com/pcvolkmer/osc-variant.git synced 2025-04-19 19:56:50 +00:00

Add subcommand 'diff' to compare two OSC files

This commit is contained in:
Paul-Christian Volkmer 2023-07-01 17:29:53 +02:00
parent b04ee563f2
commit b030ce6a53
9 changed files with 209 additions and 7 deletions

View File

@ -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:
```

View File

@ -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,
},
}

View File

@ -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<dyn Error>> {
}
}
}
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(())

View File

@ -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 {

View File

@ -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 {

View File

@ -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;

View File

@ -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::<Vec<_>>();
let names_b = list_b
.iter()
.map(|entry| entry.get_name())
.collect::<Vec<_>>();
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 {

View File

@ -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 {

View File

@ -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 {