From e2f0d88acf267627feb958851fbef3e9afa5e4d0 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 1 Jun 2020 19:34:21 +0200 Subject: [PATCH] Add bibliography, references and settings placeholders --- Cargo.lock | 40 ++++++++- Cargo.toml | 6 +- src/format/html.rs | 30 +++++++ src/parsing/elements.rs | 77 +++++++++++++++++ src/parsing/placeholders.rs | 161 ++++++++++++++++++++++++++++++++++++ 5 files changed, 311 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21c3f1e..3c7fb16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,6 +5,14 @@ name = "adler32" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -182,6 +190,11 @@ name = "linked-hash-map" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "minify" version = "1.1.1" @@ -308,6 +321,17 @@ dependencies = [ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "regex-syntax" version = "0.6.18" @@ -358,12 +382,14 @@ dependencies = [ [[package]] name = "snekdown" -version = "0.7.0" +version = "0.8.0" dependencies = [ "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "minify 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "syntect 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -456,6 +482,14 @@ dependencies = [ "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "time" version = "0.1.43" @@ -542,6 +576,7 @@ dependencies = [ [metadata] "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" +"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" @@ -567,6 +602,7 @@ dependencies = [ "checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" "checksum line-wrap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" "checksum linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" +"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" "checksum minify 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ef7c582bd7587da887914eaf294897e82f2f5c98b741f137f2a918cd26a885b" "checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5" "checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" @@ -582,6 +618,7 @@ dependencies = [ "checksum quote 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" "checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" "checksum ryu 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" "checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" @@ -597,6 +634,7 @@ dependencies = [ "checksum syntect 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "83b43a6ca1829ccb0c933b615c9ea83ffc8793ae240cecbd15119b13d741161d" "checksum termion 1.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" "checksum time 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" diff --git a/Cargo.toml b/Cargo.toml index 6a28e19..c7d506f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snekdown" -version = "0.7.0" +version = "0.8.0" authors = ["trivernis "] edition = "2018" license-file = "LICENSE" @@ -25,4 +25,6 @@ termion = "1.5.5" minify = "1.1.1" htmlescape = "0.3.1" syntect = "4.2.0" -chrono = "0.4.11" \ No newline at end of file +chrono = "0.4.11" +regex = "1.3.9" +lazy_static = "1.4.0" \ No newline at end of file diff --git a/src/format/html.rs b/src/format/html.rs index 2f9a820..37bfcaf 100644 --- a/src/format/html.rs +++ b/src/format/html.rs @@ -37,6 +37,7 @@ impl ToHtml for Line { Line::Ruler(ruler) => ruler.to_html(), Line::Anchor(anchor) => anchor.to_html(), Line::Centered(centered) => centered.to_html(), + Line::ReferenceEntry(ref_entry) => ref_entry.to_html(), } } } @@ -53,6 +54,7 @@ impl ToHtml for Inline { Inline::Bold(bold) => bold.to_html(), Inline::Image(img) => img.to_html(), Inline::Placeholder(placeholder) => placeholder.lock().unwrap().to_html(), + Inline::Reference(reference) => reference.to_html(), } } } @@ -421,3 +423,31 @@ impl ToHtml for Centered { format!("
{}
", self.line.to_html()) } } + +impl ToHtml for Reference { + fn to_html(&self) -> String { + if let Some(value) = &self.value { + value.to_html() + } else { + "Unknown reference".to_string() + } + } +} + +impl ToHtml for RefValue { + fn to_html(&self) -> String { + match self { + RefValue::BibEntry(bib) => encode_minimal(bib.lock().unwrap().get_formatted().as_str()), + } + } +} + +impl ToHtml for ReferenceEntry { + fn to_html(&self) -> String { + if let Some(val) = &self.value { + val.to_html() + } else { + "Unknown reference".to_string() + } + } +} diff --git a/src/parsing/elements.rs b/src/parsing/elements.rs index 66809ba..fdc91ad 100644 --- a/src/parsing/elements.rs +++ b/src/parsing/elements.rs @@ -1,3 +1,4 @@ +use crate::parsing::placeholders::BibEntry; use std::collections::HashMap; use std::sync::{Arc, Mutex}; @@ -58,6 +59,7 @@ pub enum Line { Ruler(Ruler), Anchor(Anchor), Centered(Centered), + ReferenceEntry(ReferenceEntry), } #[derive(Clone, Debug)] @@ -66,6 +68,8 @@ pub struct Document { pub(crate) is_root: bool, pub(crate) path: Option, pub(crate) placeholders: Vec>>, + pub bib_entries: HashMap>>, + pub config_settings: HashMap>>, } #[derive(Clone, Debug)] @@ -164,6 +168,7 @@ pub enum Inline { Url(Url), Image(Image), Placeholder(Arc>), + Reference(Reference), } #[derive(Clone, Debug)] @@ -226,15 +231,56 @@ pub struct Centered { pub(crate) line: TextLine, } +#[derive(Clone, Debug)] +pub struct Reference { + pub(crate) value: Option, + pub(crate) metadata: Option, +} + +#[derive(Clone, Debug)] +pub struct ReferenceEntry { + pub(crate) value: Option, +} + +#[derive(Clone, Debug)] +pub enum RefValue { + BibEntry(Arc>), +} + +#[derive(Clone, Debug)] +pub struct ConfigValue { + pub(crate) value: MetadataValue, +} + +impl ConfigValue { + fn set_value(&mut self, value: MetadataValue) { + self.value = value; + } +} + // implementations impl Document { + fn get_default_config() -> HashMap>> { + let mut config: HashMap>> = HashMap::new(); + config.insert( + "bib-display".to_string(), + Arc::new(Mutex::new(ConfigValue { + value: MetadataValue::String("title - author - year - (notes)".to_string()), + })), + ); + + config + } + pub fn new(is_root: bool) -> Self { Self { elements: Vec::new(), is_root, path: None, placeholders: Vec::new(), + bib_entries: HashMap::new(), + config_settings: Self::get_default_config(), } } @@ -289,6 +335,29 @@ impl Document { list } + + pub fn set_config_param( + &mut self, + key: String, + value: ConfigValue, + ) -> Option>> { + if let Some(arc_val) = self.config_settings.get(&key) { + arc_val.lock().unwrap().set_value(value.value); + + Some(Arc::clone(&arc_val)) + } else { + self.config_settings + .insert(key, Arc::new(Mutex::new(value))) + } + } + + pub fn get_config_param(&self, key: &str) -> Option>> { + if let Some(val) = self.config_settings.get(key) { + Some(Arc::clone(val)) + } else { + None + } + } } impl Section { @@ -497,4 +566,12 @@ impl InlineMetadata { false } } + + pub fn get_string(&self, key: &str) -> Option { + if let Some(MetadataValue::String(value)) = self.data.get(key) { + Some(value.clone()) + } else { + None + } + } } diff --git a/src/parsing/placeholders.rs b/src/parsing/placeholders.rs index f7600a5..1fb900b 100644 --- a/src/parsing/placeholders.rs +++ b/src/parsing/placeholders.rs @@ -1,5 +1,7 @@ use super::elements::*; use chrono::prelude::*; +use regex::Regex; +use std::sync::{Arc, Mutex, MutexGuard}; macro_rules! block { ($inner:expr) => { @@ -22,8 +24,67 @@ macro_rules! inline { pub(crate) trait ProcessPlaceholders { fn process_placeholders(&mut self); + fn process_definitions(&mut self); + fn add_bib_entry(&mut self, key: String, value: BibEntry) -> Arc>; + fn get_bib_entry(&self, ph: &MutexGuard, key: &str) -> BibEntry; } +const B_AUTHOR: &str = "author"; +const B_DATE: &str = "date"; +const B_URL: &str = "url"; +const B_TITLE: &str = "title"; +const B_PUBLISHER: &str = "publisher"; +const B_NOTES: &str = "notes"; + +#[derive(Clone, Debug)] +pub struct BibEntry { + key: String, + author: Option, + date: Option, + url: Option, + title: Option, + publisher: Option, + notes: Option, + display: Option>>, +} + +impl BibEntry { + pub fn get_formatted(&self) -> String { + if let Some(display) = &self.display { + let value = display.lock().unwrap(); + if let MetadataValue::String(format) = &value.value { + let mut format = format.clone(); + if let Some(author) = &self.author { + format = format.replace("author", author); + } + if let Some(date) = &self.date { + format = format.replace("date", date); + } + if let Some(url) = &self.url { + format = format.replace("url", url) + } + if let Some(title) = &self.title { + format = format.replace("title", title); + } + if let Some(publisher) = &self.publisher { + format = format.replace("publisher", publisher); + } + if let Some(notes) = &self.notes { + format = format.replace("notes", notes); + } + + format + } else { + format!("'Invalid formatter!' {:?}", self) + } + } else { + format!("{:?}", self) + } + } +} + +const S_VALUE: &str = "value"; + const P_TOC: &str = "toc"; const P_DATE: &str = "date"; const P_TIME: &str = "time"; @@ -32,8 +93,25 @@ const P_DATETIME: &str = "datetime"; impl ProcessPlaceholders for Document { /// parses all placeholders and assigns values to them fn process_placeholders(&mut self) { + self.process_definitions(); + lazy_static::lazy_static! {static ref RE_REF: Regex = Regex::new(r"^ref:(.*)$").unwrap();} self.placeholders.iter().for_each(|p| { let mut pholder = p.lock().unwrap(); + if let Some(cap) = RE_REF.captures(&pholder.name) { + if let Some(key) = cap.get(1) { + if let Some(entry) = self.bib_entries.get(key.as_str()) { + pholder.value = Some(inline!(Inline::Reference(Reference { + value: Some(RefValue::BibEntry(entry.clone())), + metadata: pholder.metadata.clone() + }))) + } else { + pholder.value = Some(inline!(Inline::Reference(Reference { + value: None, + metadata: pholder.metadata.clone() + }))) + } + } + } match pholder.name.to_ascii_lowercase().as_str() { P_TOC => { let ordered = if let Some(meta) = &pholder.metadata { @@ -56,6 +134,89 @@ impl ProcessPlaceholders for Document { } }) } + + fn process_definitions(&mut self) { + lazy_static::lazy_static! { + static ref RE_BIB: Regex = Regex::new(r"^bib:(.*)$").unwrap(); + } + + lazy_static::lazy_static! { + static ref RE_SET: Regex = Regex::new(r"^set:(.*)$").unwrap(); + } + + let placeholders = self.placeholders.clone(); + placeholders.iter().for_each(|p| { + let mut pholder = p.lock().unwrap(); + + let name = pholder.name.clone(); + if let Some(cap) = RE_BIB.captures(&name) { + if let Some(key) = cap.get(1) { + let key: &str = key.as_str(); + let entry = self.get_bib_entry(&pholder, key); + let entry = self.add_bib_entry(key.to_string(), entry); + pholder.value = Some(Element::Line(Box::new(Line::ReferenceEntry( + ReferenceEntry { + value: Some(RefValue::BibEntry(entry)), + }, + )))); + } + return; + } + + if let Some(cap) = RE_SET.captures(&name) { + if let Some(key) = cap.get(1) { + let key: &str = key.as_str(); + pholder.value = Some(inline!(Inline::Plain(PlainText { + value: "".to_string() + }))); + if let Some(meta) = &pholder.metadata { + if let Some(value) = meta.data.get(S_VALUE) { + self.set_config_param( + key.to_string(), + ConfigValue { + value: value.clone(), + }, + ); + } + } + } + return; + } + }); + } + + fn add_bib_entry(&mut self, key: String, value: BibEntry) -> Arc> { + let arc_entry = Arc::new(Mutex::new(value)); + self.bib_entries.insert(key, Arc::clone(&arc_entry)); + + arc_entry + } + + fn get_bib_entry(&self, ph: &MutexGuard, key: &str) -> BibEntry { + if let Some(meta) = &ph.metadata { + BibEntry { + key: key.to_string(), + author: meta.get_string(B_AUTHOR), + date: meta.get_string(B_DATE), + url: meta.get_string(B_URL), + title: meta.get_string(B_TITLE), + publisher: meta.get_string(B_PUBLISHER), + notes: meta.get_string(B_NOTES), + display: self.get_config_param("bib-display"), + } + } else { + BibEntry { + key: key.to_string(), + author: None, + date: None, + url: None, + title: None, + publisher: None, + notes: None, + display: self.get_config_param("bib-display"), + } + } + } } fn get_time_string() -> String {