aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlukeflo2025-10-07 15:05:47 +0200
committerlukeflo2025-10-07 15:05:47 +0200
commit67afd67d4d51a00079269d431a7058fc50750886 (patch)
treed8d259a74fcc52b0272f004fa39a4be95cdb7d6b
parent34170cfa62df5443a0d8675106c553efec035687 (diff)
downloadbibiman-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.toml2
-rw-r--r--src/bibiman.rs4
-rw-r--r--src/bibiman/bibisetup.rs10
-rw-r--r--src/bibiman/citekeys.rs167
-rw-r--r--src/config.rs8
5 files changed, 187 insertions, 4 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 66627b8..098848e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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() },
}
}