diff options
| author | lukeflo | 2025-10-07 15:05:47 +0200 |
|---|---|---|
| committer | lukeflo | 2025-10-07 15:05:47 +0200 |
| commit | 67afd67d4d51a00079269d431a7058fc50750886 (patch) | |
| tree | d8d259a74fcc52b0272f004fa39a4be95cdb7d6b | |
| parent | 34170cfa62df5443a0d8675106c553efec035687 (diff) | |
| download | bibiman-67afd67d4d51a00079269d431a7058fc50750886.tar.gz bibiman-67afd67d4d51a00079269d431a7058fc50750886.zip | |
implement basic citekey formatting:
* Reads patterns and parses them.
TODO:
* **Fully** sanitize Latex macros
* Preprocess complex and regularly used fields like `author`
* Write changes to original bib file
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/bibiman.rs | 4 | ||||
| -rw-r--r-- | src/bibiman/bibisetup.rs | 10 | ||||
| -rw-r--r-- | src/bibiman/citekeys.rs | 167 | ||||
| -rw-r--r-- | src/config.rs | 8 |
5 files changed, 187 insertions, 4 deletions
@@ -8,7 +8,7 @@ readme = "README.md" description = "TUI for interacting with BibLaTeX databases" keywords = ["tui", "biblatex", "bibliography", "bibtex", "latex"] categories = ["command-line-utilities"] -edition = "2021" +edition = "2024" exclude = ["/tests", ".*"] [profile.release-git] diff --git a/src/bibiman.rs b/src/bibiman.rs index c423ce1..3158d73 100644 --- a/src/bibiman.rs +++ b/src/bibiman.rs @@ -40,6 +40,7 @@ use std::result::Result::Ok; use tui_input::Input; pub mod bibisetup; +pub mod citekeys; pub mod entries; pub mod keywords; pub mod search; @@ -88,13 +89,14 @@ pub struct Bibiman { } impl Bibiman { - // Constructs a new instance of [`App`]. + /// Constructs a new instance of [`Bibiman`]. pub fn new(args: &mut CLIArgs, cfg: &mut BibiConfig) -> Result<Self> { let mut main_bibfiles: Vec<PathBuf> = args.pos_args.clone(); if cfg.general.bibfiles.is_some() { main_bibfiles.append(cfg.general.bibfiles.as_mut().unwrap()) }; let main_bibfiles = cliargs::parse_files(main_bibfiles); + // TODO: insert workflow for formatting citekeys let main_biblio = BibiSetup::new(&main_bibfiles, cfg); let tag_list = TagList::new(main_biblio.keyword_list.clone()); let search_struct = BibiSearch::default(); diff --git a/src/bibiman/bibisetup.rs b/src/bibiman/bibisetup.rs index b3f788c..a83a507 100644 --- a/src/bibiman/bibisetup.rs +++ b/src/bibiman/bibisetup.rs @@ -246,8 +246,14 @@ impl BibiData { } impl BibiSetup { + /// Setup the TUI: + /// * Getting files + /// * Parse files into `biblatex::Bibliography` struct + /// * If wanted, format citekeys + /// * Get citekey vector + /// * Collect all keywords + /// * Build the entry list to be displayed pub fn new(main_bibfiles: &[PathBuf], cfg: &BibiConfig) -> Self { - // TODO: Needs check for config file path as soon as config file is impl Self::check_files(main_bibfiles); let bibfilestring = Self::bibfiles_to_string(main_bibfiles); let bibliography = biblatex::Bibliography::parse(&bibfilestring).unwrap(); @@ -264,7 +270,7 @@ impl BibiSetup { } } - // Check which file format the passed file has + /// Check which file format the passed file has fn check_files(main_bibfiles: &[PathBuf]) { if main_bibfiles.is_empty() { println!( diff --git a/src/bibiman/citekeys.rs b/src/bibiman/citekeys.rs new file mode 100644 index 0000000..4c36e80 --- /dev/null +++ b/src/bibiman/citekeys.rs @@ -0,0 +1,167 @@ +use biblatex::Bibliography; +use color_eyre::eyre::eyre; +use owo_colors::OwoColorize; + +use crate::config::BibiConfig; + +#[derive(Debug, Default, Clone)] +pub(crate) struct CitekeyFormatting { + bib_entries: Bibliography, + fields: Vec<String>, +} + +impl CitekeyFormatting { + /// Start Citekey formatting with building a new instance of `CitekeyFormatting` + /// Formatting is processed file by file, because `bibman` can handle + /// multi-file setups. + /// The `Bibliography` inserted will be edited in place with the new citekeys. + /// Thus, in the end the `bib_entries` field will hold the updated `Bibliography` + pub fn new(cfg: &BibiConfig, bib_entries: Bibliography) -> color_eyre::Result<Self> { + let fields = cfg.citekey_formatter.fields.clone(); + if fields.is_empty() { + return Err(eyre!( + "To format all citekeys, you need to provide {} values in the config file", + "fields".bold() + )); + } + Ok(Self { + bib_entries, + fields, + }) + } + + pub fn do_formatting(&mut self) { + for entry in self.bib_entries.iter_mut() { + let mut new_citekey = String::new(); + for pattern in self.fields.iter() { + let (field, word_count, char_count, inner_delimiter, trailing_delimiter) = + split_formatting_pat(pattern); + let formatted_field_str = { + let mut formatted_str = String::new(); + let field = entry.get_as::<String>(field).expect(&format!( + "Couldn't find field {}", + field.bold().bright_red() + )); + let mut split_field = field.split_whitespace(); + let mut words_passed = 0; + loop { + if let Some(field_slice) = split_field.next() { + formatted_str = formatted_str + format_word(field_slice, char_count); + words_passed += 1; + if word_count.is_some_and(|count| count == words_passed) { + formatted_str = formatted_str + trailing_delimiter.unwrap_or(""); + break; + } else { + formatted_str = formatted_str + inner_delimiter.unwrap_or("") + } + } else { + formatted_str = formatted_str + trailing_delimiter.unwrap_or(""); + break; + }; + } + formatted_str + }; + new_citekey = new_citekey + &formatted_field_str; + } + entry.key = new_citekey; + } + } +} + +fn preformat_field() {} + +/// Cut of word at char count index if its set +fn format_word(word: &str, count: Option<usize>) -> &str { + if let Some(len) = count + && len < word.chars().count() + { + &word[..len] + } else { + word + } +} + +/// Split a formatting pattern of kind +/// `<field>;<word count>;<char count>;<inside delimiter>;<trailing delimiter>`, +/// e.g.: `title;3;3;_;:` will give `("title", 3, 3, "_", ":")` +fn split_formatting_pat( + pattern: &str, +) -> ( + &str, + Option<usize>, + Option<usize>, + Option<&str>, + Option<&str>, +) { + let mut splits = pattern.split(';'); + ( + splits + .next() + .expect("Need field value for formatting citekey"), + if let Some(next) = splits.next() + && next.len() > 0 + { + next.parse::<usize>().ok() + } else { + None + }, + if let Some(next) = splits.next() + && next.len() > 0 + { + next.parse::<usize>().ok() + } else { + None + }, + splits.next(), + splits.next(), + ) +} + +#[cfg(test)] +mod tests { + use biblatex::Bibliography; + use itertools::Itertools; + + use crate::bibiman::citekeys::{CitekeyFormatting, split_formatting_pat}; + + #[test] + fn split_citekey_pattern() { + let pattern = "title;3;5;_;_"; + + assert_eq!( + split_formatting_pat(pattern), + ("title", Some(3), Some(5), Some("_"), Some("_")) + ); + + let pattern = "year"; + + assert_eq!( + split_formatting_pat(pattern), + ("year", None, None, None, None) + ); + + let pattern = "author;1;;;_"; + assert_eq!( + split_formatting_pat(pattern), + ("author", Some(1), None, Some(""), Some("_")) + ); + } + + #[test] + fn format_citekey_test() { + let src = r"@book{tolkien1937, author = {Tolkien}, title = {\enquote{Lord} of the \textbf{Rings}}, year = {1937}}"; + let bibliography = Bibliography::parse(src).unwrap(); + let mut formatting_struct = CitekeyFormatting { + bib_entries: bibliography, + fields: vec![ + "author;1;;-;_".into(), + "title;3;3;_;_".into(), + "year".into(), + ], + }; + formatting_struct.do_formatting(); + let keys = formatting_struct.bib_entries.keys().collect_vec(); + assert_eq!(keys[0], "Tolkien_Lor_of_the_1937"); + assert_eq!(keys[0].to_lowercase(), "tolkien_lor_of_the_1937"); + } +} diff --git a/src/config.rs b/src/config.rs index 00a35b7..78cfef9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -102,6 +102,7 @@ const DEFAULT_CONFIG: &str = r##" pub struct BibiConfig { pub general: General, pub colors: Colors, + pub citekey_formatter: CitekeyFormatter, } /// Substruct [general] in config.toml @@ -143,6 +144,11 @@ pub struct Colors { pub year_color: Color, } +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct CitekeyFormatter { + pub fields: Vec<String>, +} + impl Default for BibiConfig { fn default() -> Self { Self { @@ -161,6 +167,7 @@ impl Default for BibiConfig { custom_column: CustomField::Pubtype, }, colors: Self::dark_colors(), + citekey_formatter: CitekeyFormatter { fields: Vec::new() }, } } } @@ -187,6 +194,7 @@ impl BibiConfig { } else { Self::dark_colors() }, + citekey_formatter: CitekeyFormatter { fields: Vec::new() }, } } |
