diff options
| author | lukeflo | 2025-10-09 14:28:55 +0200 |
|---|---|---|
| committer | lukeflo | 2025-10-09 14:28:55 +0200 |
| commit | 7266a14753ed5d572aeed584b66b07d1b9921ca7 (patch) | |
| tree | 2434200ae537da18855fe019731d695689e686d8 | |
| parent | 952dc94b412ffcff26a59c37f3112079c78058ff (diff) | |
| download | bibiman-7266a14753ed5d572aeed584b66b07d1b9921ca7.tar.gz bibiman-7266a14753ed5d572aeed584b66b07d1b9921ca7.zip | |
rewrite cli parsing; need to implement format-citekeys cli parsing
| -rw-r--r-- | src/bibiman/citekeys.rs | 105 | ||||
| -rw-r--r-- | src/cliargs.rs | 52 | ||||
| -rw-r--r-- | src/main.rs | 30 |
3 files changed, 124 insertions, 63 deletions
diff --git a/src/bibiman/citekeys.rs b/src/bibiman/citekeys.rs index b389da2..b7995ac 100644 --- a/src/bibiman/citekeys.rs +++ b/src/bibiman/citekeys.rs @@ -16,7 +16,7 @@ ///// use std::{ - fs::File, + fs::OpenOptions, io::Write, path::{Path, PathBuf}, }; @@ -38,7 +38,9 @@ pub enum CitekeyCase { #[derive(Debug, Default, Clone)] pub(crate) struct CitekeyFormatting { - bibfile_path: PathBuf, + /// bibfile to replace keys at. The optional fields defines a differing + /// output file to write to, otherwise original file will be overwritten. + bibfile_path: (PathBuf, Option<PathBuf>), bib_entries: Bibliography, fields: Vec<String>, case: Option<CitekeyCase>, @@ -54,6 +56,7 @@ impl CitekeyFormatting { pub fn new<P: AsRef<Path>>( cfg: &BibiConfig, path: P, + target: Option<P>, bib_entries: Bibliography, ) -> color_eyre::Result<Self> { let fields = cfg @@ -68,7 +71,10 @@ impl CitekeyFormatting { )); } Ok(Self { - bibfile_path: path.as_ref().to_path_buf(), + bibfile_path: ( + path.as_ref().to_path_buf(), + target.map(|p| p.as_ref().to_path_buf()), + ), bib_entries, fields, case: cfg.citekey_formatter.case.clone(), @@ -77,9 +83,9 @@ impl CitekeyFormatting { } /// Process the actual formatting. The citekey of every entry will be updated. - pub fn do_formatting(&mut self) { + pub fn do_formatting(&mut self) -> &mut Self { let mut old_new_keys: Vec<(String, String)> = Vec::new(); - for entry in self.bib_entries.iter_mut() { + for entry in self.bib_entries.iter() { old_new_keys.push(( entry.key.clone(), build_citekey(entry, &self.fields, self.case.as_ref()), @@ -87,16 +93,47 @@ impl CitekeyFormatting { } self.old_new_keys_map = old_new_keys; + + self } /// Write entries with updated citekeys to bibfile pub fn update_file(&self) -> color_eyre::Result<()> { - let mut file = File::open(&self.bibfile_path)?; + let source_file = self.bibfile_path.0.as_path(); + let target_file = if let Some(path) = &self.bibfile_path.1 { + path + } else { + source_file + }; + let mut content = std::fs::read_to_string(source_file)?; - file.write_all(self.bib_entries.to_biblatex_string().as_bytes())?; + for (old_key, new_key) in self.old_new_keys_map.iter() { + content = content.replace(old_key, new_key); + } + + let mut new_file = OpenOptions::new() + .truncate(true) + .write(true) + .create(true) + .open(target_file)?; + + new_file.write_all(content.as_bytes())?; Ok(()) } + + /// Sort the vector containing old/new citekey pairs by the length of the latter. + /// That will prevent the replacement longer key parts that equal a full shorter + /// key. + /// + /// You are **very encouraged** to call this method before `update_file()` to + /// prevent replacing citekeys partly which afterwards wont match the pattern + /// anymore. + pub fn rev_sort_new_keys_by_len(&mut self) -> &mut Self { + self.old_new_keys_map + .sort_by(|a, b| b.1.len().cmp(&a.1.len())); + self + } } /// Build the citekey from the patterns defined in the config file @@ -272,6 +309,18 @@ mod tests { #[test] fn format_citekey_test() { let src = r" + @article{bos_latex_metadata_and_publishing_workflows_2023, + title = {{LaTeX}, metadata, and publishing workflows}, + author = {Bos, Joppe W. and {McCurley}, Kevin S.}, + year = {2023}, + month = apr, + journal = {arXiv}, + number = {{arXiv}:2301.08277}, + doi = {10.48550/arXiv.2301.08277}, + url = {http://arxiv.org/abs/2301.08277}, + urldate = {2023-08-22}, + note = {type: article}, + } @book{bhambra_colonialism_social_theory_2021, title = {Colonialism and \textbf{Modern Social Theory}}, author = {Bhambra, Gurminder K. and Holmwood, John}, @@ -282,7 +331,7 @@ mod tests { "; let bibliography = Bibliography::parse(src).unwrap(); let mut formatting_struct = CitekeyFormatting { - bibfile_path: PathBuf::new(), + bibfile_path: (PathBuf::new(), None), bib_entries: bibliography, fields: vec![ "entrytype;;;;:".into(), @@ -294,29 +343,35 @@ mod tests { case: None, old_new_keys_map: Vec::new(), }; - formatting_struct.do_formatting(); - let keys = formatting_struct.bib_entries.keys().collect_vec(); + let _ = formatting_struct.do_formatting(); + assert_eq!( + formatting_struct.old_new_keys_map.get(0).unwrap().1, + "article:Bos-McCurley_LaT_met_and_pub_Empt_2023" + ); assert_eq!( - keys[0], + formatting_struct.old_new_keys_map.get(1).unwrap().1, "book:Bhambra-Holmwood_Col_and_Mod_Soc_Camb:and:Medf_2021" ); formatting_struct.case = Some(CitekeyCase::Lower); - formatting_struct.do_formatting(); - let keys = formatting_struct.bib_entries.keys().collect_vec(); + let _ = formatting_struct.do_formatting().rev_sort_new_keys_by_len(); + // now the longer citekey is processed first and its in lowercase! assert_eq!( - keys[0], + formatting_struct.old_new_keys_map.get(0).unwrap().1, "book:bhambra-holmwood_col_and_mod_soc_camb:and:medf_2021" ); - // let bib_string = formatting_struct.bib_entries.to_biblatex_string(); - // let new_entry = r" - // @book{book:Bhambra-Holmwood_Col_and_Mod_Soc_Camb:and:Medf_2021, - // title = {Colonialism and \textbf{Modern Social Theory}}, - // author = {Bhambra, Gurminder K. and Holmwood, John}, - // location = {Cambridge and Medford}, - // publisher = {Polity Press}, - // date = {2021}, - // } - // "; - // assert_eq!(new_entry, bib_string); + } + + #[test] + fn sorting_appended_citekeys() { + let mut keys: Vec<(String, String)> = vec![ + ("smith2000".into(), "smith_book_2000".into()), + ("smith2000a".into(), "smith_book_2000a".into()), + ("smith2000ab".into(), "smith_book_2000ab".into()), + ]; + keys.sort_by(|a, b| b.1.len().cmp(&a.1.len())); + let mut keys = keys.iter(); + assert_eq!(keys.next().unwrap().1, "smith_book_2000ab"); + assert_eq!(keys.next().unwrap().1, "smith_book_2000a"); + assert_eq!(keys.next().unwrap().1, "smith_book_2000"); } } diff --git a/src/cliargs.rs b/src/cliargs.rs index 082ecda..3b12fc3 100644 --- a/src/cliargs.rs +++ b/src/cliargs.rs @@ -18,20 +18,19 @@ use color_eyre::eyre::Result; use dirs::{config_dir, home_dir}; use lexopt::prelude::*; +use owo_colors::OwoColorize; use owo_colors::colors::css::LightGreen; use owo_colors::colors::*; -use owo_colors::OwoColorize; use std::env; use std::path::PathBuf; use walkdir::WalkDir; use crate::app; +use crate::config::BibiConfig; // struct for CLIArgs #[derive(Debug, Default, Clone)] pub struct CLIArgs { - pub helparg: bool, - pub versionarg: bool, pub pos_args: Vec<PathBuf>, pub cfg_path: Option<PathBuf>, pub light_theme: bool, @@ -39,7 +38,7 @@ pub struct CLIArgs { } impl CLIArgs { - pub fn parse_args() -> Result<CLIArgs, lexopt::Error> { + pub fn parse_args() -> color_eyre::Result<(CLIArgs, BibiConfig)> { let mut args = CLIArgs::default(); let mut parser = lexopt::Parser::from_env(); @@ -52,22 +51,57 @@ impl CLIArgs { None }; + // if parser + // .raw_args() + // .is_ok_and(|mut arg| arg.next_if(|a| a == "format-citekeys").is_some()) + // { + // todo!("Format citekeys options"); + // } + while let Some(arg) = parser.next()? { match arg { - Short('h') | Long("help") => args.helparg = true, - Short('v') | Long("version") => args.versionarg = true, + Short('h') | Long("help") => { + println!("{}", help_func()); + std::process::exit(0); + } + Short('v') | Long("version") => { + println!("{}", version_func()); + std::process::exit(0); + } Short('c') | Long("config-file") => args.cfg_path = Some(parser.value()?.parse()?), Long("light-terminal") => args.light_theme = true, Long("pdf-path") => { args.pdf_path = Some(parser.value()?.parse()?); } // Value(pos_arg) => parse_files(&mut args, pos_arg), - Value(pos_arg) => args.pos_args.push(pos_arg.into()), - _ => return Err(arg.unexpected()), + Value(pos_arg) => { + if args.pos_args.is_empty() && pos_arg == "format-citekeys" { + todo!("Write format citekeys function"); + } else { + args.pos_args.push(parser.value()?.into()); + } + } + _ => return Err(arg.unexpected().into()), } } - Ok(args) + if args + .cfg_path + .as_ref() + .is_some_and(|f| !f.try_exists().unwrap() || !f.is_file()) + { + BibiConfig::create_default_config(&args); + } + + let mut cfg = if args.cfg_path.is_some() { + BibiConfig::parse_config(&args)? + } else { + BibiConfig::new(&args) + }; + + cfg.cli_overwrite(&args); + + Ok((args, cfg)) } } diff --git a/src/main.rs b/src/main.rs index c956d7c..58805d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,35 +31,7 @@ pub mod tui; #[tokio::main] async fn main() -> Result<()> { // Parse CLI arguments - let mut parsed_args = CLIArgs::parse_args()?; - - // Print help if -h/--help flag is passed and exit - if parsed_args.helparg { - println!("{}", cliargs::help_func()); - std::process::exit(0); - } - - // Print version if -v/--version flag is passed and exit - if parsed_args.versionarg { - println!("{}", cliargs::version_func()); - std::process::exit(0); - } - - if parsed_args - .cfg_path - .as_ref() - .is_some_and(|f| !f.try_exists().unwrap() || !f.is_file()) - { - BibiConfig::create_default_config(&parsed_args); - } - - let mut cfg = if parsed_args.cfg_path.is_some() { - BibiConfig::parse_config(&parsed_args)? - } else { - BibiConfig::new(&parsed_args) - }; - - cfg.cli_overwrite(&parsed_args); + let (mut parsed_args, mut cfg) = CLIArgs::parse_args()?; init_error_hooks()?; |
