From 8498f4c66c09a7d4ee933995baf34490d3b005b3 Mon Sep 17 00:00:00 2001 From: trivernis Date: Sat, 6 Jun 2020 17:49:38 +0200 Subject: [PATCH] Change bibliography to an easier concept Definitions are similar to the markdown key-url definitions: [key]:url [key]:[metadata] References work like some markdown-footnote concepts: [^key] --- Cargo.lock | 20 ++- Cargo.toml | 7 +- README.md | 19 ++- src/format/html.rs | 91 ++++++----- src/parsing/bibliography.rs | 200 +++++++++++++++++++++++++ src/parsing/configuration/config.rs | 21 +++ src/parsing/configuration/default.toml | 4 + src/parsing/configuration/keys.rs | 7 + src/parsing/configuration/mod.rs | 141 +++++++++++++++++ src/parsing/elements.rs | 119 ++++----------- src/parsing/inline.rs | 99 +++++++----- src/parsing/mod.rs | 3 + src/parsing/parser.rs | 36 ++++- src/parsing/placeholders.rs | 155 ++----------------- src/parsing/tokens.rs | 7 + 15 files changed, 590 insertions(+), 339 deletions(-) create mode 100644 src/parsing/bibliography.rs create mode 100644 src/parsing/configuration/config.rs create mode 100644 src/parsing/configuration/default.toml create mode 100644 src/parsing/configuration/keys.rs create mode 100644 src/parsing/configuration/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 97fa2cd..4625950 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -566,7 +566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "snekdown" -version = "0.13.0" +version = "0.14.0" dependencies = [ "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -577,8 +577,11 @@ dependencies = [ "minify 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.111 (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)", + "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -646,7 +649,7 @@ dependencies = [ "serde_derive 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", "walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "yaml-rust 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -674,6 +677,14 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.111 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-segmentation" version = "1.6.0" @@ -762,7 +773,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "yaml-rust" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "linked-hash-map 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -848,6 +859,7 @@ dependencies = [ "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 toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" "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" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" @@ -862,4 +874,4 @@ dependencies = [ "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum xml-rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" -"checksum yaml-rust 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +"checksum yaml-rust 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" diff --git a/Cargo.toml b/Cargo.toml index ae3fc87..17d4c30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snekdown" -version = "0.13.0" +version = "0.14.0" authors = ["trivernis "] edition = "2018" license-file = "LICENSE" @@ -27,4 +27,7 @@ regex = "1.3.9" lazy_static = "1.4.0" colored = "1.9.3" gh-emoji = "1.0.3" -notify = "4.0.12" \ No newline at end of file +notify = "4.0.12" +toml = "0.5.6" +serde ="1.0.111" +serde_derive = "1.0.111" \ No newline at end of file diff --git a/README.md b/README.md index 75e63dd..8125247 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,23 @@ _Underlined_ §[#0C0]Colored text§[] §[red] red §[] ``` +## Bibliography + +Bibliography entries can be defined and referenced anywhere in the document. + +Definition: +```md +[book]:[author=Snek title = "Snekdown Book"] +[github]: https://github.com/trivernis/snekdown +``` + +Usage: +``` +There is a book about snekdown[^book] and a github repo[^github]. +``` + +Bibliography entries are only shown when used in the document. + ## Roadmap The end goal is to have a markup language with features similar to LaTeX. @@ -175,7 +192,7 @@ The end goal is to have a markup language with features similar to LaTeX. - [x] Colors - [x] Watching and rendering on change - [ ] Metadata files -- [ ] Bibliography +- [x] Bibliography - [ ] Math - [ ] Text sizes - [ ] Title pages diff --git a/src/format/html.rs b/src/format/html.rs index 910fcdc..5c265f1 100644 --- a/src/format/html.rs +++ b/src/format/html.rs @@ -1,4 +1,5 @@ use crate::format::Template; +use crate::parsing::bibliography::{BibEntry, BibReference}; use crate::parsing::elements::*; use htmlescape::{encode_attribute, encode_minimal}; use minify::html::minify; @@ -38,7 +39,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(), + Line::BibEntry(bib) => bib.lock().unwrap().to_html(), } } } @@ -55,11 +56,11 @@ 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(), Inline::Superscript(superscript) => superscript.to_html(), Inline::Checkbox(checkbox) => checkbox.to_html(), Inline::Emoji(emoji) => emoji.to_html(), Inline::Colored(colored) => colored.to_html(), + Inline::BibReference(bibref) => bibref.lock().unwrap().to_html(), } } } @@ -435,51 +436,6 @@ impl ToHtml for Centered { } } -impl ToHtml for Reference { - fn to_html(&self) -> String { - if let Some(value) = &self.value { - let ref_id = value.get_ref_id(); - if let Some(display) = &self.display { - match value { - RefValue::BibEntry(bib) => { - let bib = bib.lock().unwrap(); - let mut template = bib.get_template(); - template.set_value(display.lock().unwrap().value.to_html()); - - format!("{}", ref_id, template.render()) - } - } - } else { - format!("{}", ref_id, 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 { - format!( - "
{}
", - encode_attribute(val.get_ref_id().as_str()), - val.to_html() - ) - } else { - "Unknown reference".to_string() - } - } -} - impl ToHtml for Checkbox { fn to_html(&self) -> String { if self.value { @@ -509,3 +465,44 @@ impl ToHtml for Colored { ) } } + +impl ToHtml for BibReference { + fn to_html(&self) -> String { + format!( + "{}", + self.key.clone(), + self.get_formatted() + ) + } +} + +impl ToHtml for BibEntry { + fn to_html(&self) -> String { + if !self.is_visible() { + return "".to_string(); + } + if let Some(display) = &self.display { + let display = display.lock().unwrap(); + let mut template = Template::new(display.get().as_string()); + template.set_replacements(self.as_map()); + format!( + "{}", + encode_attribute(self.key.as_str()), + encode_minimal(template.render().as_str()) + ) + } else { + if let Some(url) = &self.url { + format!( + "{1}", + encode_attribute(url.as_str()), + encode_minimal(self.key.as_str()) + ) + } else { + format!( + "{0}", + encode_attribute(self.key.as_str()) + ) + } + } + } +} diff --git a/src/parsing/bibliography.rs b/src/parsing/bibliography.rs new file mode 100644 index 0000000..8d85064 --- /dev/null +++ b/src/parsing/bibliography.rs @@ -0,0 +1,200 @@ +use crate::format::Template; +use crate::parsing::configuration::keys::{BIB_DISPLAY, BIB_HIDE_UNUSED}; +use crate::parsing::configuration::{ConfigRefEntry, Configuration, Value}; +use crate::parsing::elements::Metadata; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +const B_NUMBER: &str = "number"; +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 { + pub(crate) number: usize, + pub(crate) ref_count: usize, + pub key: String, + pub author: Option, + pub date: Option, + pub url: Option, + pub title: Option, + pub publisher: Option, + pub notes: Option, + pub display: Option, + pub hide_unused: Option, +} + +#[derive(Clone, Debug)] +pub struct BibReference { + pub(crate) key: String, + pub(crate) reference_entry: Option>>, + pub(crate) display: Option, +} + +#[derive(Clone, Debug)] +pub struct Bibliography { + entries: HashMap>>, + references: Vec>>, +} + +impl BibEntry { + pub fn as_map(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert(B_NUMBER.to_string(), format!("{}", self.number)); + map.insert("key".to_string(), self.key.clone()); + if let Some(author) = &self.author { + map.insert(B_AUTHOR.to_string(), author.clone()); + } + if let Some(date) = &self.date { + map.insert(B_DATE.to_string(), date.clone()); + } + if let Some(url) = &self.url { + map.insert(B_URL.to_string(), url.clone()); + } + if let Some(title) = &self.title { + map.insert(B_TITLE.to_string(), title.clone()); + } + if let Some(publisher) = &self.publisher { + map.insert(B_PUBLISHER.to_string(), publisher.clone()); + } + if let Some(notes) = &self.notes { + map.insert(B_NOTES.to_string(), notes.clone()); + } + + map + } + + pub fn from_metadata(key: String, data: Box, config: &Configuration) -> Self { + BibEntry { + number: 0, + ref_count: 0, + key, + author: data.get_string(B_AUTHOR), + date: data.get_string(B_DATE), + url: data.get_string(B_URL), + title: data.get_string(B_TITLE), + publisher: data.get_string(B_PUBLISHER), + notes: data.get_string(B_NOTES), + display: config.get_ref_entry(BIB_DISPLAY), + hide_unused: config.get_ref_entry(BIB_HIDE_UNUSED), + } + } + + pub fn from_url(key: String, url: String, config: &Configuration) -> Self { + BibEntry { + number: 0, + ref_count: 0, + key, + author: None, + date: None, + url: Some(url), + title: None, + publisher: None, + notes: None, + display: config.get_ref_entry(BIB_DISPLAY), + hide_unused: config.get_ref_entry(BIB_HIDE_UNUSED), + } + } + + pub fn set_number(&mut self, number: usize) { + self.number = number + } + + pub fn set_ref_count(&mut self, number: usize) { + self.ref_count = number + } + + pub fn is_visible(&self) -> bool { + if let Some(hide_cfg) = &self.hide_unused { + let hide_cfg = hide_cfg.lock().unwrap(); + if let Value::Bool(b) = hide_cfg.get() { + if *b && self.ref_count == 0 { + return false; + } + } + } + + true + } +} + +impl BibReference { + pub fn new(key: String, display: Option) -> Self { + Self { + key: key.to_string(), + display, + reference_entry: None, + } + } + + /// sets the reference to the bib entry + pub(crate) fn set_entry(&mut self, entry: Arc>) { + self.reference_entry = Some(entry) + } + + pub(crate) fn get_formatted(&self) -> String { + if let Some(entry) = &self.reference_entry { + let entry = entry.lock().unwrap(); + if let Some(display) = &self.display { + let display = display.lock().unwrap(); + let mut template = Template::new(display.get().as_string()); + template.set_replacements(entry.as_map()); + return template.render(); + } + return format!("{}", entry.number); + } + + return "citation needed".to_string(); + } +} + +impl Bibliography { + pub fn new() -> Self { + Self { + entries: HashMap::new(), + references: Vec::new(), + } + } + + pub(crate) fn assign_entry_data(&mut self) { + let mut count = 0; + self.references.iter().for_each(|e| { + let mut reference = e.lock().unwrap(); + if let Some(entry) = self.entries.get(&reference.key) { + { + let mut entry_raw = entry.lock().unwrap(); + let ref_count = entry_raw.ref_count; + entry_raw.set_ref_count(ref_count + 1); + } + reference.set_entry(Arc::clone(entry)); + } + }); + self.entries.iter().for_each(|(_, e)| { + let mut entry = e.lock().unwrap(); + if entry.is_visible() { + count += 1; + entry.set_number(count) + } + }); + } + + pub fn add_ref_entry(&mut self, entry: Arc>) { + self.references.push(entry) + } + + pub fn add_bib_entry(&mut self, entry: Arc>) { + let key = entry.lock().unwrap().key.clone(); + self.entries.insert(key, entry); + } + + pub fn combine(&mut self, other: &mut Bibliography) { + let other_entries = other.entries.clone(); + other.entries = HashMap::new(); + self.entries.extend(other_entries.into_iter()); + self.references.append(&mut other.references); + } +} diff --git a/src/parsing/configuration/config.rs b/src/parsing/configuration/config.rs new file mode 100644 index 0000000..4fe8c31 --- /dev/null +++ b/src/parsing/configuration/config.rs @@ -0,0 +1,21 @@ +use serde_derive::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RootConfig { + pub(crate) bibliography: Option, + pub(crate) metadata: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct BibConfig { + pub(crate) entry_display: Option, + pub(crate) reference_display: Option, + pub(crate) hide_unused: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MetaConfig { + pub(crate) author: Option, + pub(crate) date: Option, + pub(crate) title: Option, +} diff --git a/src/parsing/configuration/default.toml b/src/parsing/configuration/default.toml new file mode 100644 index 0000000..f6257e6 --- /dev/null +++ b/src/parsing/configuration/default.toml @@ -0,0 +1,4 @@ +[bibliography] +entry_display = "{{number}}: {{author}} - {{title}} - {{date}} - {{url}}" +reference_display = "{{number}}" +hide_unused = true \ No newline at end of file diff --git a/src/parsing/configuration/keys.rs b/src/parsing/configuration/keys.rs new file mode 100644 index 0000000..495761b --- /dev/null +++ b/src/parsing/configuration/keys.rs @@ -0,0 +1,7 @@ +pub const BIB_DISPLAY: &str = "bib-entry-display"; +pub const BIB_REF_DISPLAY: &str = "bib-ref-display"; +pub const BIB_HIDE_UNUSED: &str = "bib-hide-unused"; + +pub const META_AUTHOR: &str = "author"; +pub const META_TITLE: &str = "title"; +pub const META_DATE: &str = "date"; diff --git a/src/parsing/configuration/mod.rs b/src/parsing/configuration/mod.rs new file mode 100644 index 0000000..414a323 --- /dev/null +++ b/src/parsing/configuration/mod.rs @@ -0,0 +1,141 @@ +use crate::parsing::configuration::config::RootConfig; +use crate::parsing::configuration::keys::{ + BIB_DISPLAY, BIB_HIDE_UNUSED, BIB_REF_DISPLAY, META_AUTHOR, META_DATE, META_TITLE, +}; +use crate::parsing::elements::MetadataValue; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +pub mod config; +pub(crate) mod keys; + +#[derive(Clone, Debug)] +pub enum Value { + String(String), + Bool(bool), + Float(f64), + Integer(i64), +} + +#[derive(Clone, Debug)] +pub struct ConfigEntry { + inner: Value, +} + +pub type ConfigRefEntry = Arc>; + +#[derive(Clone, Debug)] +pub struct Configuration { + config: Arc>>, +} + +impl Value { + pub fn as_string(&self) -> String { + match self { + Value::String(string) => string.clone(), + Value::Integer(int) => format!("{}", int), + Value::Float(f) => format!("{:02}", f), + Value::Bool(b) => format!("{}", b), + } + } +} + +impl ConfigEntry { + pub fn new(value: Value) -> Self { + Self { inner: value } + } + + pub fn set(&mut self, value: Value) { + self.inner = value; + } + + pub fn get(&self) -> &Value { + &self.inner + } +} + +impl Configuration { + pub fn new() -> Self { + Self { + config: Arc::new(Mutex::new(HashMap::new())), + } + } + + pub fn default() -> Self { + let mut self_config = Self::new(); + lazy_static::lazy_static! { static ref CONFIG: RootConfig = toml::from_str(std::include_str!("default.toml")).unwrap();} + self_config.assign_config(&CONFIG); + + self_config + } + + pub fn assign_config(&mut self, config: &RootConfig) { + if let Some(bib) = &config.bibliography { + if let Some(cfg) = &bib.entry_display { + self.set(BIB_DISPLAY, Value::String(cfg.clone())) + } + if let Some(cfg) = &bib.reference_display { + self.set(BIB_REF_DISPLAY, Value::String(cfg.clone())) + } + if let Some(cfg) = &bib.hide_unused { + self.set(BIB_HIDE_UNUSED, Value::Bool(*cfg)); + } + } + if let Some(meta) = &config.metadata { + if let Some(cfg) = &meta.author { + self.set(META_AUTHOR, Value::String(cfg.clone())) + } + if let Some(cfg) = &meta.date { + self.set(META_DATE, Value::String(cfg.clone())) + } + if let Some(cfg) = &meta.title { + self.set(META_TITLE, Value::String(cfg.clone())) + } + } + } + + /// returns the value of a config entry + pub fn get_entry(&self, key: &str) -> Option { + let config = self.config.lock().unwrap(); + if let Some(entry) = config.get(key) { + let value = entry.lock().unwrap(); + Some(value.clone()) + } else { + None + } + } + + /// returns a config entry that is a reference to a value + pub fn get_ref_entry(&self, key: &str) -> Option { + let config = self.config.lock().unwrap(); + if let Some(entry) = config.get(&key.to_string()) { + Some(Arc::clone(entry)) + } else { + None + } + } + + /// Sets a config parameter + pub fn set(&mut self, key: &str, value: Value) { + let mut config = self.config.lock().unwrap(); + if let Some(entry) = config.get(&key.to_string()) { + entry.lock().unwrap().set(value) + } else { + config.insert( + key.to_string(), + Arc::new(Mutex::new(ConfigEntry::new(value))), + ); + } + } + + /// Sets a config value based on a metadata value + pub fn set_from_meta(&mut self, key: &str, value: MetadataValue) { + match value { + MetadataValue::String(string) => self.set(key, Value::String(string)), + MetadataValue::Bool(bool) => self.set(key, Value::Bool(bool)), + MetadataValue::Float(f) => self.set(key, Value::Float(f)), + MetadataValue::Integer(i) => self.set(key, Value::Integer(i)), + _ => {} + } + } +} diff --git a/src/parsing/elements.rs b/src/parsing/elements.rs index aef72e7..5d919de 100644 --- a/src/parsing/elements.rs +++ b/src/parsing/elements.rs @@ -1,4 +1,6 @@ -use crate::parsing::placeholders::BibEntry; +use crate::parsing::bibliography::{BibEntry, BibReference, Bibliography}; +use crate::parsing::configuration::Configuration; +use crate::parsing::placeholders::ProcessPlaceholders; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; @@ -45,7 +47,7 @@ pub enum Line { Ruler(Ruler), Anchor(Anchor), Centered(Centered), - ReferenceEntry(ReferenceEntry), + BibEntry(Arc>), } #[derive(Clone, Debug)] @@ -54,8 +56,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>>, + pub config: Configuration, + pub bibliography: Bibliography, } #[derive(Clone, Debug)] @@ -155,10 +157,10 @@ pub enum Inline { Url(Url), Image(Image), Placeholder(Arc>), - Reference(Reference), Checkbox(Checkbox), Emoji(Emoji), Colored(Colored), + BibReference(Arc>), } #[derive(Clone, Debug)] @@ -231,29 +233,6 @@ pub struct Centered { pub(crate) line: TextLine, } -#[derive(Clone, Debug)] -pub struct Reference { - pub(crate) value: Option, - pub(crate) metadata: Option, - pub(crate) display: Option>>, -} - -#[derive(Clone, Debug)] -pub struct ReferenceEntry { - pub(crate) value: Option, - pub(crate) reference_count: usize, -} - -#[derive(Clone, Debug)] -pub enum RefValue { - BibEntry(Arc>), -} - -#[derive(Clone, Debug)] -pub struct ConfigValue { - pub(crate) value: MetadataValue, -} - #[derive(Clone, Debug)] pub struct Emoji { pub(crate) value: char, @@ -269,34 +248,14 @@ pub struct Colored { // 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}} - {{date}} - {{url}} - ({{notes}})".to_string(), - ), - })), - ); - config.insert( - "ref-display".to_string(), - Arc::new(Mutex::new(ConfigValue { - value: MetadataValue::String("[{{key}}]".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(), + config: Configuration::default(), + bibliography: Bibliography::new(), } } @@ -331,29 +290,6 @@ 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 - } - } - /// Processes section and import elements /// /// if it encounters a section it checks if the sections is of smaller order than the previous one @@ -385,6 +321,7 @@ impl Document { let anchor = &mut arc_anchor.lock().unwrap(); if let Some(doc) = &mut anchor.document { self.placeholders.append(&mut doc.placeholders); + self.bibliography.combine(&mut doc.bibliography); doc.elements.reverse(); self.elements.append(&mut doc.elements); anchor.document = None; @@ -408,11 +345,14 @@ impl Document { } self.elements = new_order; } -} -impl ConfigValue { - fn set_value(&mut self, value: MetadataValue) { - self.value = value; + pub fn post_process(&mut self) { + self.postprocess_imports(); + if self.is_root { + self.process_definitions(); + self.bibliography.assign_entry_data(); + self.process_placeholders(); + } } } @@ -630,8 +570,13 @@ impl Placeholder { } } -impl InlineMetadata { - pub fn get_bool(&self, key: &str) -> bool { +pub trait Metadata { + fn get_bool(&self, key: &str) -> bool; + fn get_string(&self, key: &str) -> Option; +} + +impl Metadata for InlineMetadata { + fn get_bool(&self, key: &str) -> bool { if let Some(MetadataValue::Bool(value)) = self.data.get(key) { *value } else { @@ -639,7 +584,7 @@ impl InlineMetadata { } } - pub fn get_string(&self, key: &str) -> Option { + fn get_string(&self, key: &str) -> Option { if let Some(MetadataValue::String(value)) = self.data.get(key) { Some(value.clone()) } else { @@ -647,17 +592,3 @@ impl InlineMetadata { } } } - -impl RefValue { - pub fn get_ref_id(&self) -> String { - match self { - RefValue::BibEntry(bib) => { - let bib = bib.lock().unwrap(); - let mut key = bib.key.clone(); - key.retain(|c| !c.is_whitespace()); - - key - } - } - } -} diff --git a/src/parsing/inline.rs b/src/parsing/inline.rs index 77ceba9..6b26982 100644 --- a/src/parsing/inline.rs +++ b/src/parsing/inline.rs @@ -1,8 +1,11 @@ use super::charstate::CharStateMachine; use super::elements::*; use super::tokens::*; +use crate::parsing::bibliography::BibReference; +use crate::parsing::configuration::keys::BIB_REF_DISPLAY; use crate::parsing::utils::{ParseError, ParseResult}; use crate::Parser; +use std::sync::{Arc, Mutex}; pub(crate) trait ParseInline { fn parse_surrounded(&mut self, surrounding: &char) -> ParseResult; @@ -18,10 +21,23 @@ pub(crate) trait ParseInline { fn parse_superscript(&mut self) -> ParseResult; fn parse_emoji(&mut self) -> ParseResult; fn parse_colored(&mut self) -> ParseResult; + fn parse_bibref(&mut self) -> ParseResult>>; fn parse_plain(&mut self) -> ParseResult; } impl ParseInline for Parser { + /// parses Inline surrounded by characters + fn parse_surrounded(&mut self, surrounding: &char) -> ParseResult<Inline> { + let start_index = self.index; + self.assert_special(surrounding, start_index)?; + self.skip_char(); + let inline = self.parse_inline()?; + self.assert_special(surrounding, start_index)?; + self.skip_char(); + + Ok(inline) + } + /// parses Inline, the formatting parts of a line (Text) fn parse_inline(&mut self) -> ParseResult<Inline> { if self.check_special(&PIPE) || self.check_linebreak() { @@ -52,6 +68,8 @@ impl ParseInline for Parser { Ok(Inline::Emoji(emoji)) } else if let Ok(colored) = self.parse_colored() { Ok(Inline::Colored(colored)) + } else if let Ok(bibref) = self.parse_bibref() { + Ok(Inline::BibReference(bibref)) } else { Ok(Inline::Plain(self.parse_plain()?)) } @@ -180,44 +198,6 @@ impl ParseInline for Parser { }) } - /// parses plain text as a string until it encounters an unescaped special inline char - fn parse_plain(&mut self) -> ParseResult<PlainText> { - if self.check_linebreak() { - return Err(ParseError::new(self.index)); - } - let mut characters = String::new(); - characters.push(self.current_char); - while let Some(ch) = self.next_char() { - if self.check_special_group(&INLINE_SPECIAL_CHARS) - || self.check_special_group(&self.inline_break_at) - { - break; - } - characters.push(ch) - } - - if characters.len() > 0 { - Ok(PlainText { value: characters }) - } else { - Err(ParseError::new_with_message( - self.index, - "no plaintext characters parsed", - )) - } - } - - /// parses Inline surrounded by characters - fn parse_surrounded(&mut self, surrounding: &char) -> ParseResult<Inline> { - let start_index = self.index; - self.assert_special(surrounding, start_index)?; - self.skip_char(); - let inline = self.parse_inline()?; - self.assert_special(surrounding, start_index)?; - self.skip_char(); - - Ok(inline) - } - fn parse_emoji(&mut self) -> ParseResult<Emoji> { let start_index = self.index; self.assert_special(&EMOJI, start_index)?; @@ -251,4 +231,47 @@ impl ParseInline for Parser { color, }) } + + fn parse_bibref(&mut self) -> ParseResult<Arc<Mutex<BibReference>>> { + let start_index = self.index; + self.assert_special_sequence(&SQ_BIBREF_START, start_index)?; + self.skip_char(); + let key = self.get_string_until_or_revert(&[BIBREF_CLOSE], &[SPACE, LB], start_index)?; + self.skip_char(); + let ref_entry = Arc::new(Mutex::new(BibReference::new( + key, + self.document.config.get_ref_entry(BIB_REF_DISPLAY), + ))); + self.document + .bibliography + .add_ref_entry(Arc::clone(&ref_entry)); + + Ok(ref_entry) + } + + /// parses plain text as a string until it encounters an unescaped special inline char + fn parse_plain(&mut self) -> ParseResult<PlainText> { + if self.check_linebreak() { + return Err(ParseError::new(self.index)); + } + let mut characters = String::new(); + characters.push(self.current_char); + while let Some(ch) = self.next_char() { + if self.check_special_group(&INLINE_SPECIAL_CHARS) + || self.check_special_group(&self.inline_break_at) + { + break; + } + characters.push(ch) + } + + if characters.len() > 0 { + Ok(PlainText { value: characters }) + } else { + Err(ParseError::new_with_message( + self.index, + "no plaintext characters parsed", + )) + } + } } diff --git a/src/parsing/mod.rs b/src/parsing/mod.rs index f43efbf..cba2112 100644 --- a/src/parsing/mod.rs +++ b/src/parsing/mod.rs @@ -1,8 +1,11 @@ +pub mod bibliography; pub mod charstate; +pub mod configuration; pub mod elements; pub mod inline; pub mod parser; pub mod placeholders; pub mod tokens; + #[macro_use] pub mod utils; diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index a429503..f5f0760 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -1,8 +1,8 @@ use super::elements::*; use super::tokens::*; +use crate::parsing::bibliography::BibEntry; use crate::parsing::charstate::CharStateMachine; use crate::parsing::inline::ParseInline; -use crate::parsing::placeholders::ProcessPlaceholders; use crate::parsing::utils::{ParseError, ParseResult}; use colored::*; use crossbeam_utils::sync::WaitGroup; @@ -26,7 +26,7 @@ pub struct Parser { wg: WaitGroup, is_child: bool, pub(crate) inline_break_at: Vec<char>, - document: Document, + pub(crate) document: Document, pub(crate) previous_char: char, pub(crate) reader: Box<dyn BufRead>, } @@ -230,11 +230,7 @@ impl Parser { let wg = self.wg.clone(); self.wg = WaitGroup::new(); wg.wait(); - - self.document.postprocess_imports(); - if !self.is_child { - self.document.process_placeholders(); - } + self.document.post_process(); let document = self.document.clone(); self.document = Document::new(!self.is_child); @@ -693,6 +689,8 @@ impl Parser { Ok(Line::Ruler(ruler)) } else if let Ok(centered) = self.parse_centered() { Ok(Line::Centered(centered)) + } else if let Ok(bib) = self.parse_bib_entry() { + Ok(Line::BibEntry(bib)) } else if let Ok(text) = self.parse_text_line() { Ok(Line::Text(text)) } else { @@ -701,6 +699,30 @@ impl Parser { } } + fn parse_bib_entry(&mut self) -> ParseResult<Arc<Mutex<BibEntry>>> { + let start_index = self.index; + self.seek_inline_whitespace(); + self.assert_special(&BIB_KEY_OPEN, start_index)?; + self.skip_char(); + let key = self.get_string_until_or_revert(&[BIB_KEY_CLOSE], &[LB, SPACE], start_index)?; + self.skip_char(); + self.assert_special(&BIB_DATA_START, start_index)?; + self.skip_char(); + self.seek_inline_whitespace(); + let entry = if let Ok(meta) = self.parse_inline_metadata() { + BibEntry::from_metadata(key, Box::new(meta), &self.document.config) + } else { + let url = self.get_string_until_or_revert(&[LB], &[], start_index)?; + BibEntry::from_url(key, url, &self.document.config) + }; + let entry_ref = Arc::new(Mutex::new(entry)); + self.document + .bibliography + .add_bib_entry(Arc::clone(&entry_ref)); + + Ok(entry_ref) + } + /// parses centered text fn parse_centered(&mut self) -> Result<Centered, ParseError> { let start_index = self.index; diff --git a/src/parsing/placeholders.rs b/src/parsing/placeholders.rs index 4a11124..259546e 100644 --- a/src/parsing/placeholders.rs +++ b/src/parsing/placeholders.rs @@ -1,8 +1,6 @@ use super::elements::*; -use crate::format::Template; use chrono::prelude::*; use regex::Regex; -use std::sync::{Arc, Mutex, MutexGuard}; macro_rules! block { ($inner:expr) => { @@ -26,71 +24,6 @@ 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<Mutex<BibEntry>>; - fn get_bib_entry(&self, ph: &MutexGuard<Placeholder>, 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 { - pub(crate) key: String, - author: Option<String>, - date: Option<String>, - url: Option<String>, - title: Option<String>, - publisher: Option<String>, - notes: Option<String>, - display: Option<Arc<Mutex<ConfigValue>>>, -} - -impl BibEntry { - pub fn get_template(&self) -> Template { - let mut template = Template::empty(); - template.add_replacement("key", &self.key); - - if let Some(author) = &self.author { - template.add_replacement(B_AUTHOR, author.as_str()); - } - if let Some(date) = &self.date { - template.add_replacement(B_DATE, date.as_str()); - } - if let Some(url) = &self.url { - template.add_replacement(B_URL, url.as_str()); - } - if let Some(title) = &self.title { - template.add_replacement(B_TITLE, title.as_str()); - } - if let Some(publisher) = &self.publisher { - template.add_replacement(B_PUBLISHER, publisher.as_str()); - } - if let Some(notes) = &self.notes { - template.add_replacement(B_NOTES, notes.as_str()); - } - - template - } - - 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 template = self.get_template(); - template.set_value(format.clone()); - - template.render() - } else { - format!("'Invalid formatter!' {:?}", self) - } - } else { - format!("{:?}", self) - } - } } const S_VALUE: &str = "value"; @@ -103,28 +36,9 @@ 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(), - display: self.get_config_param("ref-display") - }))) - } else { - pholder.value = Some(inline!(Inline::Reference(Reference { - value: None, - metadata: pholder.metadata.clone(), - display: self.get_config_param("ref-display") - }))) - } - } - } - match pholder.name.to_ascii_lowercase().as_str() { + match pholder.name.to_lowercase().as_str() { P_TOC => { let ordered = if let Some(meta) = &pholder.metadata { meta.get_bool("ordered") @@ -142,16 +56,18 @@ impl ProcessPlaceholders for Document { P_DATETIME => pholder.set_value(inline!(Inline::Plain(PlainText { value: format!("{} {}", get_date_string(), get_time_string()) }))), - _ => {} + _ => { + if let Some(entry) = self.config.get_entry(pholder.name.to_lowercase().as_str()) + { + let value = entry.get().as_string(); + pholder.set_value(inline!(Inline::Plain(PlainText { value }))) + } + } } }) } 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(); } @@ -159,22 +75,7 @@ impl ProcessPlaceholders for Document { 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)), - reference_count: 0, - }, - )))); - } - return; - } if let Some(cap) = RE_SET.captures(&name) { if let Some(key) = cap.get(1) { @@ -184,12 +85,7 @@ impl ProcessPlaceholders for Document { }))); 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(), - }, - ); + self.config.set_from_meta(key, value.clone()) } } } @@ -197,39 +93,6 @@ impl ProcessPlaceholders for Document { } }); } - - fn add_bib_entry(&mut self, key: String, value: BibEntry) -> Arc<Mutex<BibEntry>> { - 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<Placeholder>, 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 { diff --git a/src/parsing/tokens.rs b/src/parsing/tokens.rs index ffd1dc8..123245a 100644 --- a/src/parsing/tokens.rs +++ b/src/parsing/tokens.rs @@ -51,6 +51,12 @@ pub(crate) const CHECK_CHECKED: char = X; pub(crate) const COLOR_START: char = PARAGRAPH; pub(crate) const COLOR_OPEN: char = R_BRACKET; pub(crate) const COLOR_CLOSE: char = L_BRACKET; +pub(crate) const BIBREF_OPEN: char = R_BRACKET; +pub(crate) const BIBREF_REF: char = UP; +pub(crate) const BIBREF_CLOSE: char = L_BRACKET; +pub(crate) const BIB_KEY_OPEN: char = R_BRACKET; +pub(crate) const BIB_KEY_CLOSE: char = L_BRACKET; +pub(crate) const BIB_DATA_START: char = COLON; pub(crate) const ITALIC: char = ASTERISK; pub(crate) const MONOSPACE: char = BACKTICK; @@ -102,6 +108,7 @@ pub(crate) const SQ_PHOLDER_START: [char; 2] = [PHOLDER_OPEN, PHOLDER_OPEN]; pub(crate) const SQ_PHOLDER_STOP: [char; 2] = [PHOLDER_CLOSE, PHOLDER_CLOSE]; pub(crate) const SQ_CENTERED_START: [char; 2] = [PIPE, PIPE]; pub(crate) const SQ_COLOR_START: [char; 2] = [COLOR_START, COLOR_OPEN]; +pub(crate) const SQ_BIBREF_START: [char; 2] = [BIBREF_OPEN, BIBREF_REF]; // expressions