aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlukeflo2025-10-09 14:28:55 +0200
committerlukeflo2025-10-09 14:28:55 +0200
commit7266a14753ed5d572aeed584b66b07d1b9921ca7 (patch)
tree2434200ae537da18855fe019731d695689e686d8
parent952dc94b412ffcff26a59c37f3112079c78058ff (diff)
downloadbibiman-7266a14753ed5d572aeed584b66b07d1b9921ca7.tar.gz
bibiman-7266a14753ed5d572aeed584b66b07d1b9921ca7.zip
rewrite cli parsing; need to implement format-citekeys cli parsing
-rw-r--r--src/bibiman/citekeys.rs105
-rw-r--r--src/cliargs.rs52
-rw-r--r--src/main.rs30
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()?;