From a55db66e57ee51b0f9d992c509293fa4cac95ec4 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Mon, 6 Nov 2023 09:37:03 +0100 Subject: [PATCH 1/4] Issue #15: Implement check subcommand cli --- src/cli.rs | 2 ++ src/main.rs | 7 +++++++ src/model/mod.rs | 24 +++++++++++++++++++++++- src/model/onkostar_editor.rs | 10 +++++++++- 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 6f574ee..d258298 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -101,6 +101,8 @@ pub enum SubCommand { #[arg(long = "strict", help = "Strikter Vergleich des Inhalts")] strict: bool, }, + #[command(about = "Überprüfe OSC-Datei auf bekannte Fehler")] + Check { file: String }, #[cfg(feature = "unzip-osb")] #[command(about = "Entpackt eine OSB-Datei")] UnzipOsb { diff --git a/src/main.rs b/src/main.rs index dab6d8a..f34d635 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,7 @@ use sha256::digest; use crate::cli::{Cli, SubCommand}; use crate::model::onkostar_editor::OnkostarEditor; +use crate::model::Checkable; use crate::profile::Profile; mod cli; @@ -257,6 +258,12 @@ fn main() -> Result<(), Box> { } }; } + SubCommand::Check { file } => { + read_inputfile(file)? + .check() + .iter() + .for_each(|check_notice| println!("{}", check_notice)); + } #[cfg(feature = "unzip-osb")] SubCommand::UnzipOsb { file, diff --git a/src/model/mod.rs b/src/model/mod.rs index eed540c..98d056a 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -24,7 +24,7 @@ use std::cmp::Ordering; use std::collections::hash_map::DefaultHasher; -use std::fmt::Debug; +use std::fmt::{Debug, Display, Formatter}; use std::hash::{Hash, Hasher}; use crate::model::requirements::Requires; @@ -329,3 +329,25 @@ pub trait FolderContent { "ONKOSTAR Bibliothek" == self.get_library_folder() } } + +pub enum CheckNotice { + /// This will result in Error if importing file + Error { code: String, description: String }, + /// Other known issues + Warning { description: String }, +} + +impl Display for CheckNotice { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CheckNotice::Error { code, description } => { + write!(f, "[ERROR] ({}) {}", code, description) + } + CheckNotice::Warning { description } => write!(f, "[WARNING] {}", description), + } + } +} + +pub trait Checkable { + fn check(&self) -> Vec; +} diff --git a/src/model/onkostar_editor.rs b/src/model/onkostar_editor.rs index 051f84a..20a4df8 100644 --- a/src/model/onkostar_editor.rs +++ b/src/model/onkostar_editor.rs @@ -35,7 +35,9 @@ use crate::model::data_form::DataForm; use crate::model::property_catalogue::PropertyCatalogue; use crate::model::requirements::Requires; use crate::model::unterformular::Unterformular; -use crate::model::{Comparable, FolderContent, FormEntryContainer, Listable, Sortable}; +use crate::model::{ + CheckNotice, Checkable, Comparable, FolderContent, FormEntryContainer, Listable, Sortable, +}; use crate::profile::Profile; #[derive(Serialize, Deserialize, Debug)] @@ -409,6 +411,12 @@ impl FromStr for OnkostarEditor { } } +impl Checkable for OnkostarEditor { + fn check(&self) -> Vec { + vec![] + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct InfoXML { From e2d5eedd02f14b3e3c9237316a1a4482dfda3115 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Mon, 6 Nov 2023 13:31:34 +0100 Subject: [PATCH 2/4] Issue #15: Check OSC files for known issues --- src/checks/mod.rs | 114 +++++++++++++++++++++++++++++++++++ src/checks/osc.rs | 87 ++++++++++++++++++++++++++ src/main.rs | 8 +-- src/model/data_form.rs | 7 +++ src/model/mod.rs | 26 +------- src/model/onkostar_editor.rs | 23 +++++-- src/model/unterformular.rs | 7 +++ 7 files changed, 240 insertions(+), 32 deletions(-) create mode 100644 src/checks/mod.rs create mode 100644 src/checks/osc.rs diff --git a/src/checks/mod.rs b/src/checks/mod.rs new file mode 100644 index 0000000..bf2396a --- /dev/null +++ b/src/checks/mod.rs @@ -0,0 +1,114 @@ +/* + * MIT License + * + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +pub mod osc; + +use console::style; +use std::fmt::{Display, Formatter}; + +pub enum CheckNotice { + /// This will result in Error if importing file and has a support code + ErrorWithCode { + code: String, + description: String, + line: Option, + example: Option, + }, + /// This will result in Error if importing file + Error { + description: String, + line: Option, + }, + #[allow(dead_code)] + /// Other known issues + Warning { + description: String, + line: Option, + }, +} + +impl Display for CheckNotice { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + CheckNotice::ErrorWithCode { + code, + description, + line, + example, + } => match line { + Some(line) => write!( + f, + "{} ({}) at Line {}: {}{}", + style("ERROR").red().bold(), + code, + line, + description, + match example { + Some(example) => format!(" -> '{}'", style(example).dim()), + _ => String::new(), + } + ), + None => write!( + f, + "{} ({}): {}{}", + style("ERROR").red().bold(), + code, + description, + match example { + Some(example) => format!(" -> '{}'", style(example).dim()), + _ => String::new(), + } + ), + }, + CheckNotice::Error { description, line } => match line { + Some(line) => write!( + f, + "{} at Line {}: {}", + style("ERROR").red().bold(), + line, + description + ), + None => write!(f, "{}: {}", style("ERROR").red().bold(), description), + }, + CheckNotice::Warning { description, line } => match line { + Some(line) => write!( + f, + "{} at Line {}: {}", + style("WARNING").yellow().bold(), + line, + description + ), + None => write!(f, "{}: {}", style("WARNING").yellow().bold(), description), + }, + } + } +} + +pub trait Checkable { + fn check(&self) -> Vec; +} + +pub trait Fixable { + fn fix(&mut self) -> bool; +} diff --git a/src/checks/osc.rs b/src/checks/osc.rs new file mode 100644 index 0000000..8ec9a97 --- /dev/null +++ b/src/checks/osc.rs @@ -0,0 +1,87 @@ +/* + * MIT License + * + * Copyright (c) 2023 Comprehensive Cancer Center Mainfranken + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +use crate::checks::{CheckNotice, Checkable}; +use crate::model::onkostar_editor::OnkostarEditor; +use std::fs; +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; +use std::str::FromStr; + +pub fn check(file: &Path) -> Vec { + let mut result = match File::open(file) { + Ok(file) => BufReader::new(file) + .lines() + .enumerate() + .flat_map(|(line, content)| match content { + Ok(content) => check_line(line, content), + _ => { + return vec![CheckNotice::Error { + description: "Cannot read line".to_string(), + line: Some(line), + }] + } + }) + .collect::>(), + _ => { + return vec![CheckNotice::Error { + description: "Kann Datei nicht lesen".to_string(), + line: None, + }] + } + }; + + let inner_checks = &mut match fs::read_to_string(file) { + Ok(content) => match OnkostarEditor::from_str(content.as_str()) { + Ok(data) => data.check(), + Err(err) => vec![CheckNotice::Error { + description: format!("Interner Fehler: {}", err), + line: None, + }], + }, + _ => vec![CheckNotice::Error { + description: "Kann Datei nicht lesen".to_string(), + line: None, + }], + }; + result.append(inner_checks); + + result +} + +fn check_line(line: usize, content: String) -> Vec { + let mut result = vec![]; + + if content.contains(" ") { + result.append(&mut vec![CheckNotice::ErrorWithCode { + code: "OSTARSUPP-13334".to_string(), + description: "Leerzeichen am Ende der Plausibilitätsregel-Bezeichnung".to_string(), + line: Some(line), + example: Some(content.trim().to_string()), + }]) + } + + result +} diff --git a/src/main.rs b/src/main.rs index f34d635..88cbf8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,9 +28,10 @@ use std::fs; use std::fs::OpenOptions; use std::io::Write; use std::ops::Add; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::str::FromStr; +use crate::checks::osc::check; use clap::Parser; use console::style; use dialoguer::Confirm; @@ -40,9 +41,9 @@ use sha256::digest; use crate::cli::{Cli, SubCommand}; use crate::model::onkostar_editor::OnkostarEditor; -use crate::model::Checkable; use crate::profile::Profile; +mod checks; mod cli; mod model; mod profile; @@ -259,8 +260,7 @@ fn main() -> Result<(), Box> { }; } SubCommand::Check { file } => { - read_inputfile(file)? - .check() + check(Path::new(file.as_str())) .iter() .for_each(|check_notice| println!("{}", check_notice)); } diff --git a/src/model/data_form.rs b/src/model/data_form.rs index 56e89b2..baacec2 100644 --- a/src/model/data_form.rs +++ b/src/model/data_form.rs @@ -28,6 +28,7 @@ use std::collections::HashSet; use console::style; use serde::{Deserialize, Serialize}; +use crate::checks::{CheckNotice, Checkable}; use crate::model::onkostar_editor::OnkostarEditor; use crate::model::requirements::{Requirement, Requires}; use crate::model::{ @@ -380,6 +381,12 @@ impl FolderContent for DataForm { } } +impl Checkable for DataForm { + fn check(&self) -> Vec { + vec![] + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct DataCatalogues { diff --git a/src/model/mod.rs b/src/model/mod.rs index 98d056a..eae9980 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -24,12 +24,12 @@ use std::cmp::Ordering; use std::collections::hash_map::DefaultHasher; -use std::fmt::{Debug, Display, Formatter}; +use std::fmt::Debug; use std::hash::{Hash, Hasher}; -use crate::model::requirements::Requires; use serde::{Deserialize, Serialize}; +use crate::model::requirements::Requires; use crate::profile::{FormField, FormReference, Profile}; pub mod data_catalogue; @@ -329,25 +329,3 @@ pub trait FolderContent { "ONKOSTAR Bibliothek" == self.get_library_folder() } } - -pub enum CheckNotice { - /// This will result in Error if importing file - Error { code: String, description: String }, - /// Other known issues - Warning { description: String }, -} - -impl Display for CheckNotice { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - CheckNotice::Error { code, description } => { - write!(f, "[ERROR] ({}) {}", code, description) - } - CheckNotice::Warning { description } => write!(f, "[WARNING] {}", description), - } - } -} - -pub trait Checkable { - fn check(&self) -> Vec; -} diff --git a/src/model/onkostar_editor.rs b/src/model/onkostar_editor.rs index 20a4df8..f612ef1 100644 --- a/src/model/onkostar_editor.rs +++ b/src/model/onkostar_editor.rs @@ -30,14 +30,13 @@ use console::style; use quick_xml::de::from_str; use serde::{Deserialize, Serialize}; +use crate::checks::{CheckNotice, Checkable}; use crate::model::data_catalogue::DataCatalogue; use crate::model::data_form::DataForm; use crate::model::property_catalogue::PropertyCatalogue; use crate::model::requirements::Requires; use crate::model::unterformular::Unterformular; -use crate::model::{ - CheckNotice, Checkable, Comparable, FolderContent, FormEntryContainer, Listable, Sortable, -}; +use crate::model::{Comparable, FolderContent, FormEntryContainer, Listable, Sortable}; use crate::profile::Profile; #[derive(Serialize, Deserialize, Debug)] @@ -413,7 +412,23 @@ impl FromStr for OnkostarEditor { impl Checkable for OnkostarEditor { fn check(&self) -> Vec { - vec![] + let mut result = self + .editor + .data_form + .iter() + .flat_map(|entity| entity.check()) + .collect::>(); + + let other = &mut self + .editor + .unterformular + .iter() + .flat_map(|entity| entity.check()) + .collect::>(); + + result.append(other); + + result } } diff --git a/src/model/unterformular.rs b/src/model/unterformular.rs index 423c8fb..1ca3cf7 100644 --- a/src/model/unterformular.rs +++ b/src/model/unterformular.rs @@ -25,6 +25,7 @@ use std::cmp::Ordering; use std::collections::HashSet; +use crate::checks::{CheckNotice, Checkable}; use console::style; use serde::{Deserialize, Serialize}; @@ -377,6 +378,12 @@ impl FolderContent for Unterformular { } } +impl Checkable for Unterformular { + fn check(&self) -> Vec { + vec![] + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct DataCatalogues { From 6da1c48c2808772e34a597f88387bc83db3a8d13 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Mon, 6 Nov 2023 13:59:08 +0100 Subject: [PATCH 3/4] Issue #15: Show list of available checks --- src/checks/mod.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/cli.rs | 11 +++++++++-- src/main.rs | 13 +++++++++---- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/checks/mod.rs b/src/checks/mod.rs index bf2396a..c1c9218 100644 --- a/src/checks/mod.rs +++ b/src/checks/mod.rs @@ -112,3 +112,49 @@ pub trait Checkable { pub trait Fixable { fn fix(&mut self) -> bool; } + +pub fn print_checks() { + println!( + "{}", + style("Die folgenden Probleme sind bekannt\n") + .yellow() + .bold() + ); + + struct Problem<'a> { + code: &'a str, + name: &'a str, + description: &'a str, + fixable: bool, + } + + impl<'a> Display for Problem<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} {} {}\n\n{}", + style(self.code).bold(), + style(self.name).underlined(), + match self.fixable { + true => style("(Behebbar)").green(), + false => style("(Nicht behebbar)").red(), + }, + self.description + ) + } + } + + vec![Problem { + code: "2023-0001", + name: "Leerzeichen am Ende der Plausibilitätsregel-Bezeichnung (OSTARSUPP-13334)", + description: "Treten Leerzeichen am Ende der Plausibilitätsregel-Bezeichnung auf,\n\ + führt dies zu Fehlern beim Import der OSC-Datei.\n\ + \n\ + Das Problem wird beim Verwenden des Unterbefehls 'modify' automatisch\n\ + behoben und Leerzeichen entfernt. + ", + fixable: true, + }] + .iter() + .for_each(|problem| println!("{}\n", problem)) +} diff --git a/src/cli.rs b/src/cli.rs index d258298..5cb4e99 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -101,8 +101,15 @@ pub enum SubCommand { #[arg(long = "strict", help = "Strikter Vergleich des Inhalts")] strict: bool, }, - #[command(about = "Überprüfe OSC-Datei auf bekannte Fehler")] - Check { file: String }, + #[command(about = "Überprüfe OSC-Datei auf bekannte Problemen")] + Check { + file: String, + #[arg( + long = "list", + help = "Prüfe nicht und zeige Liste mit Checks auf bekannte Problemen" + )] + list: bool, + }, #[cfg(feature = "unzip-osb")] #[command(about = "Entpackt eine OSB-Datei")] UnzipOsb { diff --git a/src/main.rs b/src/main.rs index 88cbf8e..034c12a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use crate::checks::osc::check; +use crate::checks::print_checks; use clap::Parser; use console::style; use dialoguer::Confirm; @@ -259,10 +260,14 @@ fn main() -> Result<(), Box> { } }; } - SubCommand::Check { file } => { - check(Path::new(file.as_str())) - .iter() - .for_each(|check_notice| println!("{}", check_notice)); + SubCommand::Check { file, list } => { + if list { + print_checks(); + } else { + check(Path::new(file.as_str())) + .iter() + .for_each(|check_notice| println!("{}", check_notice)); + } } #[cfg(feature = "unzip-osb")] SubCommand::UnzipOsb { From 7b13251d348e73823e14b93eef1a70a635ef8516 Mon Sep 17 00:00:00 2001 From: Paul-Christian Volkmer Date: Mon, 6 Nov 2023 14:00:20 +0100 Subject: [PATCH 4/4] Issue #15: Add flag '--fix' to modify sub command --- src/cli.rs | 5 +++++ src/main.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/cli.rs b/src/cli.rs index 5cb4e99..31fe61f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -93,6 +93,11 @@ pub enum SubCommand { help = "Starte interaktiven Dialog zum Modifizieren von OSC-Dateien" )] interactive: bool, + #[arg( + long = "fix", + help = "Erweiterte Problembehandlung und Reparatur der OSC-Datei" + )] + fix: bool, }, #[command(about = "Vergleiche zwei Dateien anhand der Revision der enthaltenen Inhalte")] Diff { diff --git a/src/main.rs b/src/main.rs index 034c12a..14255fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -156,6 +156,7 @@ fn main() -> Result<(), Box> { sorted, strip, interactive, + fix, } => { let data = &mut read_inputfile(inputfile)?; @@ -192,6 +193,10 @@ fn main() -> Result<(), Box> { .unwrap(); } + if fix { + // No operation as of now + } + if sorted { data.sorted(); }