diff --git a/src/checks/mod.rs b/src/checks/mod.rs new file mode 100644 index 0000000..c1c9218 --- /dev/null +++ b/src/checks/mod.rs @@ -0,0 +1,160 @@ +/* + * 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; +} + +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/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/cli.rs b/src/cli.rs index 6f574ee..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 { @@ -101,6 +106,15 @@ pub enum SubCommand { #[arg(long = "strict", help = "Strikter Vergleich des Inhalts")] strict: bool, }, + #[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 dab6d8a..14255fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,9 +28,11 @@ 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 crate::checks::print_checks; use clap::Parser; use console::style; use dialoguer::Confirm; @@ -42,6 +44,7 @@ use crate::cli::{Cli, SubCommand}; use crate::model::onkostar_editor::OnkostarEditor; use crate::profile::Profile; +mod checks; mod cli; mod model; mod profile; @@ -153,6 +156,7 @@ fn main() -> Result<(), Box> { sorted, strip, interactive, + fix, } => { let data = &mut read_inputfile(inputfile)?; @@ -189,6 +193,10 @@ fn main() -> Result<(), Box> { .unwrap(); } + if fix { + // No operation as of now + } + if sorted { data.sorted(); } @@ -257,6 +265,15 @@ fn main() -> Result<(), Box> { } }; } + 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 { file, 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 eed540c..eae9980 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -27,9 +27,9 @@ use std::collections::hash_map::DefaultHasher; 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; diff --git a/src/model/onkostar_editor.rs b/src/model/onkostar_editor.rs index 051f84a..f612ef1 100644 --- a/src/model/onkostar_editor.rs +++ b/src/model/onkostar_editor.rs @@ -30,6 +30,7 @@ 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; @@ -409,6 +410,28 @@ impl FromStr for OnkostarEditor { } } +impl Checkable for OnkostarEditor { + fn check(&self) -> 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 + } +} + #[derive(Serialize, Deserialize, Debug)] #[serde(deny_unknown_fields)] pub struct InfoXML { 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 {