From 6452316cfb37df498f66860ac42dc469477d18ff Mon Sep 17 00:00:00 2001 From: Filip Niklas <118931755+Firgrep@users.noreply.github.com> Date: Sat, 31 Aug 2024 12:23:21 +0200 Subject: [PATCH] feat: external settings file in json --- Cargo.lock | 43 ++++++++ Cargo.toml | 1 + src/lib.rs | 22 ++-- src/main.rs | 27 +++-- src/utils.rs | 176 ++++++++++++++++++++++++------- src/validator.rs | 5 - tests/integration_test_verify.rs | 58 ++++++++-- 7 files changed, 264 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5eca57f..2f64333 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,12 @@ dependencies = [ "unscanny", ] +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + [[package]] name = "hashbrown" version = "0.12.3" @@ -52,6 +58,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -64,6 +76,24 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.19", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "numerals" version = "0.1.4" @@ -83,6 +113,7 @@ dependencies = [ "biblatex", "regex", "serde", + "serde_json", "serde_yaml", ] @@ -165,6 +196,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c62115693d0a9ed8c32d1c760f0fdbe7d4b05cb13c135b9b54137ac0d59fccb" +dependencies = [ + "dtoa", + "itoa", + "num-traits 0.1.43", + "serde", +] + [[package]] name = "serde_yaml" version = "0.8.26" diff --git a/Cargo.toml b/Cargo.toml index 3b7154b..63bc0b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ biblatex = "0.9" serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.8" regex = "1.10.5" +serde_json = "=1.0.1" diff --git a/src/lib.rs b/src/lib.rs index f6fa086..1921317 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,25 +87,29 @@ pub mod validator; use std::io::Error; -pub use crate::utils::VerifiedConfig; +pub use crate::utils::Config; use biblatex::Entry; -use utils::{BiblatexUtils, CoreUtils}; +use utils::{BiblatexUtils, BibliographyError, LoadOrCreateSettingsTestMode, Utils}; use validator::ArticleFileData; pub struct Prepyrus {} impl Prepyrus { - pub fn verify_config(args: &Vec) -> VerifiedConfig { - CoreUtils::verify_config(args) + pub fn verify_config( + args: &Vec, + test_mode: Option, + ) -> Result { + Utils::verify_config(args, test_mode) } - pub fn get_all_bib_entries( - bib_file: &str, - ) -> Result, Box> { + pub fn get_all_bib_entries(bib_file: &str) -> Result, BibliographyError> { Ok(BiblatexUtils::retrieve_bibliography_entries(bib_file)?) } - pub fn get_mdx_paths(target_path: &str) -> Result, Box> { - Ok(CoreUtils::extract_paths(target_path)?) + pub fn get_mdx_paths( + target_path: &str, + ignore_paths: Option>, + ) -> Result, Box> { + Ok(Utils::extract_paths(target_path, ignore_paths)?) } pub fn verify( diff --git a/src/main.rs b/src/main.rs index 3ffec91..9f2dfb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,29 @@ -use prepyrus::{Prepyrus, VerifiedConfig}; +use prepyrus::Prepyrus; fn main() { let args: Vec = std::env::args().collect(); - let VerifiedConfig { - bib_file, - target_path, - mode, - } = Prepyrus::verify_config(&args); + let _ = run(args).unwrap_or_else(|e| { + eprintln!("Error: {}", e); + std::process::exit(1); + }); - let all_entries = Prepyrus::get_all_bib_entries(&bib_file).unwrap(); - let mdx_paths = Prepyrus::get_mdx_paths(&target_path).unwrap(); + println!("===Prepyrus completed successfully!"); +} + +fn run(args: Vec) -> Result<(), Box> { + let config = Prepyrus::verify_config(&args, None)?; + let all_entries = Prepyrus::get_all_bib_entries(&config.bib_file).unwrap(); + let mdx_paths = + Prepyrus::get_mdx_paths(&config.target_path, Some(config.settings.ignore_paths))?; // Phase 1: Verify MDX files - let articles_file_data = Prepyrus::verify(mdx_paths, &all_entries).unwrap(); + let articles_file_data = Prepyrus::verify(mdx_paths, &all_entries)?; // Phase 2: Process MDX files (requires mode to be set to "process") - if mode == "process" { + if config.mode == "process" { Prepyrus::process(articles_file_data); } - println!("===Prepyrus completed successfully!"); + Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index f478ca7..2ceded0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,17 +1,32 @@ use biblatex::{Bibliography, Chunk, Date, DateValue, Entry, PermissiveType, Spanned}; -use std::{fs, io, path::Path}; +use serde::{Deserialize, Serialize}; +use std::{ + fs::{self, create_dir_all, File}, + io::{self, Write}, + path::Path, +}; pub struct BiblatexUtils; -pub struct CoreUtils; +pub struct Utils; + +#[derive(Debug)] +pub enum BibliographyError { + IoError(std::io::Error), + ParseError(biblatex::ParseError), +} impl BiblatexUtils { - pub fn retrieve_bibliography_entries(bibliography_path: &str) -> io::Result> { - let bibliography_path = fs::read_to_string(bibliography_path).unwrap(); - let bibliography = Bibliography::parse(&bibliography_path).unwrap(); + pub fn retrieve_bibliography_entries( + bibliography_path: &str, + ) -> Result, BibliographyError> { + let bibliography_path = + fs::read_to_string(bibliography_path).map_err(BibliographyError::IoError)?; + let bibliography = + Bibliography::parse(&bibliography_path).map_err(BibliographyError::ParseError)?; Ok(bibliography.into_vec()) } - pub fn extract_year(date: &PermissiveType, reference: String) -> Result { + pub fn extract_year(date: &PermissiveType, reference: String) -> Result { match date { PermissiveType::Typed(date) => match date.value { DateValue::At(datetime) => Ok(datetime.year), @@ -19,10 +34,7 @@ impl BiblatexUtils { DateValue::Before(datetime) => Ok(datetime.year), DateValue::Between(start, _end) => Ok(start.year), // Or use end.year }, - _ => Err(io::Error::new( - io::ErrorKind::InvalidData, - format!("Unable to retrieve year for: {}", reference), - )), + _ => return Err(format!("Unable to retrieve year for: {}", reference)), } } @@ -72,59 +84,87 @@ impl BiblatexUtils { } } -pub struct VerifiedConfig { +pub struct Config { pub bib_file: String, pub target_path: String, pub mode: String, + pub settings: Settings, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Settings { + #[serde(default)] + pub ignore_paths: Vec, } -impl CoreUtils { - pub fn extract_paths(path: &str) -> io::Result> { - let exceptions = vec![ - "src/pages/contributing/", - "src/pages/privacy.mdx", - "src/pages/terms.mdx", - "src/pages/team.mdx", - "src/pages/acknowledgements.mdx", - "src/pages/index.mdx", - "src/pages/_app.mdx", - ] - .iter() - .map(|&s| s.to_string()) - .collect::>(); +pub enum LoadOrCreateSettingsTestMode { + Test, +} + +impl Utils { + fn load_or_create_settings( + settings_path: &str, + test_mode: Option, + ) -> Result> { + if let Some(LoadOrCreateSettingsTestMode::Test) = test_mode { + return Ok(Settings { + ignore_paths: vec!["tests/mocks/data/development.mdx".to_string()], + }); + } + if !std::path::Path::new(settings_path).exists() { + create_dir_all(std::path::Path::new(settings_path).parent().unwrap())?; + + let default_settings = Settings { + ignore_paths: Vec::new(), + }; + let config_json = serde_json::to_string_pretty(&default_settings)?; + + let mut file = File::create(settings_path)?; + file.write_all(config_json.as_bytes())?; + } + + let file = File::open(settings_path)?; + let settings: Settings = serde_json::from_reader(file)?; + Ok(settings) + } + + pub fn extract_paths(path: &str, exceptions: Option>) -> io::Result> { + let exceptions = exceptions.unwrap_or_else(|| Vec::new()); let mdx_paths_raw = Self::extract_mdx_paths(path).unwrap(); let mdx_paths = Self::filter_mdx_paths_for_exceptions(mdx_paths_raw, exceptions); Ok(mdx_paths) } - pub fn verify_config(args: &Vec) -> VerifiedConfig { + pub fn verify_config( + args: &Vec, + test_mode: Option, + ) -> Result { if args.len() < 3 { - eprintln!("Arguments missing: "); - std::process::exit(1); + return Err("Arguments missing: "); } if !args[0].ends_with(".bib") { - eprintln!("Invalid file format. Please provide a file with .bib extension."); - std::process::exit(1); + return Err("Invalid file format. Please provide a file with .bib extension."); } let target_arg = &args[1]; if !Path::new(target_arg).is_dir() && !target_arg.ends_with(".mdx") { - eprintln!("Invalid target. Please provide a directory or a single MDX file."); - std::process::exit(1); + return Err("Invalid target. Please provide a directory or a single MDX file."); } if !args[2].eq("verify") && !args[2].eq("process") { - eprintln!("Invalid mode. Please provide either 'verify' or 'process'."); - std::process::exit(1); + return Err("Invalid mode. Please provide either 'verify' or 'process'."); } - let config = VerifiedConfig { + let settings = Self::load_or_create_settings("prepyrus_settings.json", test_mode).unwrap(); + + let config = Config { bib_file: args[0].clone(), target_path: args[1].clone(), mode: args[2].clone(), + settings, }; - config + Ok(config) } /// Excavates all MDX files in a directory and its subdirectories @@ -176,3 +216,67 @@ impl CoreUtils { filtered_paths } } + +#[cfg(test)] +mod tests_utils { + use super::*; + + #[test] + fn load_or_create_settings_with_test_mode() { + let settings = Utils::load_or_create_settings( + "test_prepyrus_settings.json", + Some(LoadOrCreateSettingsTestMode::Test), + ) + .expect("Failed to load or create settings"); + + assert_eq!( + settings.ignore_paths, + vec!["tests/mocks/data/development.mdx"] + ); + } + + #[test] + fn load_or_create_settings_with_dummy_data() { + let test_settings_path = "test_prepyrus_settings.json"; + + // Setup: make sure test starts with no existing file + if std::path::Path::new(test_settings_path).exists() { + fs::remove_file(test_settings_path) + .expect("Failed to remove existing test settings file"); + } + + // 1. Create file with default settings + let _ = Utils::load_or_create_settings(test_settings_path, None) + .expect("Failed to create settings"); + assert!(std::path::Path::new(test_settings_path).exists()); + + // 2. Write to file with test settings + let mut file = fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(test_settings_path) + .expect("Failed to open the settings file for writing"); + let modified_settings = Settings { + ignore_paths: vec![ + "tests/mocks/data/engels.mdx".to_string(), + "tests/mocks/data/marx.mdx".to_string(), + ], + }; + let config_json = serde_json::to_string_pretty(&modified_settings) + .expect("Failed to serialize modified settings"); + file.write_all(config_json.as_bytes()) + .expect("Failed to write to the settings file"); + + // 3. Read and verify test settings file + let reloaded_settings = Utils::load_or_create_settings(test_settings_path, None) + .expect("Failed to reload settings"); + assert_eq!( + reloaded_settings.ignore_paths, + vec!["tests/mocks/data/engels.mdx", "tests/mocks/data/marx.mdx"] + ); + + // Cleanup: remove test settings file + fs::remove_file(test_settings_path).expect("Failed to remove the test settings file"); + assert!(!std::path::Path::new(test_settings_path).exists()); + } +} diff --git a/src/validator.rs b/src/validator.rs index 1a8c977..0576ad0 100644 --- a/src/validator.rs +++ b/src/validator.rs @@ -7,17 +7,12 @@ use std::io::{self, BufReader, Error, Read}; #[derive(Debug, Deserialize)] pub struct Metadata { - #[allow(dead_code)] pub title: String, - #[allow(dead_code)] pub description: String, #[serde(rename = "isArticle")] pub is_article: bool, - #[allow(dead_code)] pub authors: Option, - #[allow(dead_code)] pub editors: Option, - #[allow(dead_code)] pub contributors: Option, } diff --git a/tests/integration_test_verify.rs b/tests/integration_test_verify.rs index 23eb9a4..ebec467 100644 --- a/tests/integration_test_verify.rs +++ b/tests/integration_test_verify.rs @@ -1,4 +1,7 @@ -use prepyrus::{utils::VerifiedConfig, Prepyrus}; +use prepyrus::{ + utils::{Config, LoadOrCreateSettingsTestMode}, + Prepyrus, +}; #[test] fn run_verify_with_directory() { @@ -7,14 +10,49 @@ fn run_verify_with_directory() { "tests/mocks/data".to_string(), "verify".to_string(), ]; - let VerifiedConfig { + let Config { bib_file, target_path, mode, - } = Prepyrus::verify_config(&args); + settings, + } = Prepyrus::verify_config(&args, Some(LoadOrCreateSettingsTestMode::Test)).unwrap_or_else( + |e| { + eprintln!("Error: {}", e); + std::process::exit(1); + }, + ); let all_entries = Prepyrus::get_all_bib_entries(&bib_file).unwrap(); - let mdx_paths = Prepyrus::get_mdx_paths(&target_path).unwrap(); + let mdx_paths = Prepyrus::get_mdx_paths(&target_path, Some(settings.ignore_paths)).unwrap(); + let articles_file_data = Prepyrus::verify(mdx_paths, &all_entries).unwrap(); + + println!("{:?}", articles_file_data); + assert!(mode == "verify"); + assert!(articles_file_data.len() > 1); + assert!(!articles_file_data.is_empty()); +} + +#[test] +fn run_verify_with_directory_with_ignored_paths() { + let args = vec![ + "tests/mocks/test.bib".to_string(), + "tests/mocks/data".to_string(), + "verify".to_string(), + ]; + let Config { + bib_file, + target_path, + mode, + settings, + } = Prepyrus::verify_config(&args, Some(LoadOrCreateSettingsTestMode::Test)).unwrap_or_else( + |e| { + eprintln!("Error: {}", e); + std::process::exit(1); + }, + ); + + let all_entries = Prepyrus::get_all_bib_entries(&bib_file).unwrap(); + let mdx_paths = Prepyrus::get_mdx_paths(&target_path, Some(settings.ignore_paths)).unwrap(); let articles_file_data = Prepyrus::verify(mdx_paths, &all_entries).unwrap(); println!("{:?}", articles_file_data); @@ -30,14 +68,20 @@ fn run_verify_with_single_file() { "tests/mocks/data/science-of-logic-introduction.mdx".to_string(), "verify".to_string(), ]; - let VerifiedConfig { + let Config { bib_file, target_path, mode, - } = Prepyrus::verify_config(&args); + settings, + } = Prepyrus::verify_config(&args, Some(LoadOrCreateSettingsTestMode::Test)).unwrap_or_else( + |e| { + eprintln!("Error: {}", e); + std::process::exit(1); + }, + ); let all_entries = Prepyrus::get_all_bib_entries(&bib_file).unwrap(); - let mdx_paths = Prepyrus::get_mdx_paths(&target_path).unwrap(); + let mdx_paths = Prepyrus::get_mdx_paths(&target_path, Some(settings.ignore_paths)).unwrap(); let articles_file_data = Prepyrus::verify(mdx_paths, &all_entries).unwrap(); println!("{:?}", articles_file_data);