diff --git a/Cargo.lock b/Cargo.lock index afcdbaa..74c7ca4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1277,7 +1277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "snekdown" -version = "0.27.2" +version = "0.28.0" dependencies = [ "asciimath-rs 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 56f9a20..0543f84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "snekdown" -version = "0.27.2" +version = "0.28.0" authors = ["trivernis "] edition = "2018" license-file = "LICENSE" diff --git a/README.md b/README.md index c75320a..d05a6c1 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,8 @@ test-key = ["test value", "test value 2"] ignored-imports = ["style.css"] # those files won't get imported included-stylesheets = ["style2.css"] # stylesheets that should be included included-configs = [] # other metadata files that should be included -included-bibliography = ["nextbib.toml"]# bibliography that should be included +included-bibliography = ["mybib.toml"] # bibliography that should be included +included-glossary = ["myglossary.toml"] #glossary that sould be included ``` The `[Section]` keys are not relevant as the structure gets flattened before the values are read. @@ -239,6 +240,36 @@ Bibliography entries are not rendered. To render a list of used bibliography ins `bib` placeholder at the place you want it to be rendered. +## Glossary + +Glossary entries are to be defined in a `glossary.toml` file or any other toml file +that is imported as type `glossary`. +The definition of glossary entries has to follow the following structure + +```toml +[SHORT] +long = "Long Form" +description = "The description of the entry" + +# Example +[HTML] +long = "Hypertext Markup Language" +description = "The markup language of the web" +``` + +Those glossary entries can be referenced in the snekdown file as follows: + +```md +~HTML is widely used for websites. +The format ~HTML is not considered a programming language by some definitions. + +~~HTML +``` + +The first occurence of the glossary entry (`~HTML`) always uses the long form. +The second will always be the short form. The long form can be enforced by using two +(`~~HTML`) tildes. + ## Math Snekdown allows the embedding of [AsciiMath](http://asciimath.org/): diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 8a22fc9..49cb9fe 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -2,6 +2,7 @@ pub mod tokens; use crate::format::PlaceholderTemplate; use crate::references::configuration::{ConfigRefEntry, Configuration, Value}; +use crate::references::glossary::{GlossaryManager, GlossaryReference}; use crate::references::placeholders::ProcessPlaceholders; use crate::references::templates::{Template, TemplateVariable}; use crate::utils::downloads::{DownloadManager, PendingDownload}; @@ -73,6 +74,7 @@ pub struct Document { pub bibliography: BibManager, pub downloads: Arc>, pub stylesheets: Vec>>, + pub glossary: Arc>, } #[derive(Clone, Debug)] @@ -177,6 +179,7 @@ pub enum Inline { Colored(Colored), Math(Math), BibReference(Arc>), + GlossaryReference(Arc>), TemplateVar(Arc>), CharacterCode(CharacterCode), LineBreak, @@ -300,6 +303,7 @@ impl Document { bibliography: BibManager::new(), stylesheets: Vec::new(), downloads: Arc::new(Mutex::new(DownloadManager::new())), + glossary: Arc::new(Mutex::new(GlossaryManager::new())), } } @@ -314,6 +318,7 @@ impl Document { bibliography: self.bibliography.create_child(), stylesheets: Vec::new(), downloads: Arc::clone(&self.downloads), + glossary: Arc::clone(&self.glossary), } } @@ -411,6 +416,7 @@ impl Document { if self.is_root { self.process_definitions(); self.bibliography.assign_entries_to_references(); + self.glossary.lock().unwrap().assign_entries_to_references(); self.process_placeholders(); } } diff --git a/src/elements/tokens.rs b/src/elements/tokens.rs index 4572326..4625be1 100644 --- a/src/elements/tokens.rs +++ b/src/elements/tokens.rs @@ -69,16 +69,18 @@ pub(crate) const TEMPLATE: char = PERCENT; pub(crate) const ITALIC: char = ASTERISK; pub(crate) const MONOSPACE: char = BACKTICK; -pub(crate) const STRIKED: char = TILDE; +pub(crate) const STRIKED: &'static [char] = &[TILDE, TILDE]; pub(crate) const UNDERLINED: char = UNDERSCR; pub(crate) const SUPER: char = UP; pub(crate) const EMOJI: char = COLON; pub(crate) const MATH_INLINE: &'static [char] = &[MATH, MATH]; -pub(crate) const BOLD: [char; 2] = [ASTERISK, ASTERISK]; +pub(crate) const BOLD: &'static [char] = &[ASTERISK, ASTERISK]; pub(crate) const CHARACTER_START: char = AMPERSAND; pub(crate) const CHARACTER_STOP: char = SEMICOLON; +pub(crate) const GLOSSARY_REF_START: char = TILDE; + // groups pub(crate) const QUOTES: [char; 2] = [SINGLE_QUOTE, DOUBLE_QUOTE]; diff --git a/src/format/html/assets/style.css b/src/format/html/assets/style.css index a445a39..4722cca 100644 --- a/src/format/html/assets/style.css +++ b/src/format/html/assets/style.css @@ -127,4 +127,10 @@ blockquote { .centered { text-align: center; +} + +.glossaryReference { + text-decoration: none; + color: inherit; + border-bottom: 1px dotted #000; } \ No newline at end of file diff --git a/src/format/html/to_html.rs b/src/format/html/to_html.rs index ac506cd..de4493f 100644 --- a/src/format/html/to_html.rs +++ b/src/format/html/to_html.rs @@ -3,6 +3,7 @@ use crate::format::html::html_writer::HTMLWriter; use crate::format::PlaceholderTemplate; use crate::references::configuration::keys::{EMBED_EXTERNAL, META_LANG}; use crate::references::configuration::Value; +use crate::references::glossary::{GlossaryDisplay, GlossaryReference}; use crate::references::templates::{Template, TemplateVariable}; use asciimath_rs::format::mathml::ToMathML; use htmlescape::encode_attribute; @@ -63,6 +64,7 @@ impl ToHtml for Inline { Inline::Math(m) => m.to_html(writer), Inline::LineBreak => writer.write("
".to_string()), Inline::CharacterCode(code) => code.to_html(writer), + Inline::GlossaryReference(gloss) => gloss.lock().unwrap().to_html(writer), } } } @@ -665,3 +667,23 @@ impl ToHtml for Anchor { writer.write("".to_string()) } } + +impl ToHtml for GlossaryReference { + fn to_html(&self, writer: &mut HTMLWriter) -> io::Result<()> { + if let Some(entry) = &self.entry { + let entry = entry.lock().unwrap(); + writer.write("".to_string())?; + match self.display { + GlossaryDisplay::Short => writer.write_escaped(entry.short.clone())?, + GlossaryDisplay::Long => writer.write_escaped(entry.long.clone())?, + } + writer.write("".to_string())?; + } else { + writer.write_escaped(format!("~{}", self.short.clone()))?; + } + + Ok(()) + } +} diff --git a/src/parser/inline.rs b/src/parser/inline.rs index ccbed68..1b0012c 100644 --- a/src/parser/inline.rs +++ b/src/parser/inline.rs @@ -4,11 +4,13 @@ use crate::elements::BibReference; use crate::elements::*; use crate::parser::block::ParseBlock; use crate::references::configuration::keys::BIB_REF_DISPLAY; +use crate::references::glossary::GlossaryDisplay; +use crate::references::glossary::GlossaryReference; use crate::references::templates::{GetTemplateVariables, Template, TemplateVariable}; use crate::Parser; use bibliographix::references::bib_reference::BibRef; use std::collections::HashMap; -use std::sync::{Arc, RwLock}; +use std::sync::{Arc, Mutex, RwLock}; pub(crate) trait ParseInline { fn parse_surrounded(&mut self, surrounding: &char) -> ParseResult>; @@ -27,6 +29,7 @@ pub(crate) trait ParseInline { fn parse_colored(&mut self) -> ParseResult; fn parse_bibref(&mut self) -> ParseResult>>; fn parse_template_variable(&mut self) -> ParseResult>>; + fn parse_glossary_reference(&mut self) -> ParseResult>>; fn parse_plain(&mut self) -> ParseResult; fn parse_inline_metadata(&mut self) -> ParseResult<InlineMetadata>; fn parse_metadata_pair(&mut self) -> ParseResult<(String, MetadataValue)>; @@ -70,13 +73,13 @@ impl ParseInline for Parser { log::trace!("EOF"); Err(self.ctm.err()) } else if let Ok(image) = self.parse_image() { - log::trace!("Inline::Image"); + log::trace!("Inline::Image {:?}", image); Ok(Inline::Image(image)) } else if let Ok(url) = self.parse_url(false) { - log::trace!("Inline::Url"); + log::trace!("Inline::Url {:?}", url); Ok(Inline::Url(url)) } else if let Ok(pholder) = self.parse_placeholder() { - log::trace!("Inline::Placeholder"); + log::trace!("Inline::Placeholder {:?}", pholder); Ok(Inline::Placeholder(pholder)) } else if let Ok(bold) = self.parse_bold() { log::trace!("Inline::Bold"); @@ -88,8 +91,11 @@ impl ParseInline for Parser { log::trace!("Inline::Underlined"); Ok(Inline::Underlined(under)) } else if let Ok(mono) = self.parse_monospace() { - log::trace!("Inline::Monospace"); + log::trace!("Inline::Monospace {}", mono.value); Ok(Inline::Monospace(mono)) + } else if let Ok(gloss) = self.parse_glossary_reference() { + log::trace!("Inline::GlossaryReference {}", gloss.lock().unwrap().short); + Ok(Inline::GlossaryReference(gloss)) } else if let Ok(striked) = self.parse_striked() { log::trace!("Inline::Striked"); Ok(Inline::Striked(striked)) @@ -97,26 +103,27 @@ impl ParseInline for Parser { log::trace!("Inline::Superscript"); Ok(Inline::Superscript(superscript)) } else if let Ok(checkbox) = self.parse_checkbox() { - log::trace!("Inline::Checkbox"); + log::trace!("Inline::Checkbox {}", checkbox.value); Ok(Inline::Checkbox(checkbox)) } else if let Ok(emoji) = self.parse_emoji() { - log::trace!("Inline::Emoji"); + log::trace!("Inline::Emoji {} -> {}", emoji.name, emoji.value); Ok(Inline::Emoji(emoji)) } else if let Ok(colored) = self.parse_colored() { log::trace!("Inline::Colored"); Ok(Inline::Colored(colored)) } else if let Ok(bibref) = self.parse_bibref() { - log::trace!("Inline::BibReference"); + log::trace!("Inline::BibReference {:?}", bibref); Ok(Inline::BibReference(bibref)) } else if let Ok(math) = self.parse_math() { log::trace!("Inline::Math"); Ok(Inline::Math(math)) } else if let Ok(char_code) = self.parse_character_code() { - log::trace!("Inline::Character Code"); + log::trace!("Inline::CharacterCode {}", char_code.code); Ok(Inline::CharacterCode(char_code)) } else { - log::trace!("Inline::Plain"); - Ok(Inline::Plain(self.parse_plain()?)) + let plain = self.parse_plain()?; + log::trace!("Inline::Plain {}", plain.value); + Ok(Inline::Plain(plain)) } } @@ -237,9 +244,19 @@ impl ParseInline for Parser { } fn parse_striked(&mut self) -> ParseResult<StrikedText> { - Ok(StrikedText { - value: self.parse_surrounded(&STRIKED)?, - }) + let start_index = self.ctm.get_index(); + self.ctm.assert_sequence(&STRIKED, Some(start_index))?; + self.ctm.seek_one()?; + let mut inline = vec![self.parse_inline()?]; + while !self.ctm.check_sequence(&STRIKED) { + if let Ok(result) = self.parse_inline() { + inline.push(result); + } else { + return Err(self.ctm.rewind_with_error(start_index)); + } + } + self.ctm.seek_one()?; + Ok(StrikedText { value: inline }) } fn parse_math(&mut self) -> ParseResult<Math> { @@ -373,6 +390,39 @@ impl ParseInline for Parser { }))) } + /// Parses a reference to a glossary entry + fn parse_glossary_reference(&mut self) -> ParseResult<Arc<Mutex<GlossaryReference>>> { + self.ctm.assert_char(&GLOSSARY_REF_START, None)?; + let start_index = self.ctm.get_index(); + self.ctm.seek_one()?; + + let display = if self.ctm.check_char(&GLOSSARY_REF_START) { + self.ctm.seek_one()?; + GlossaryDisplay::Long + } else { + GlossaryDisplay::Short + }; + let mut key = + self.ctm + .get_string_until_any_or_rewind(&WHITESPACE, &[TILDE], start_index)?; + if key.len() == 0 { + return Err(self.ctm.rewind_with_error(start_index)); + } + if !key.chars().last().unwrap().is_alphabetic() { + self.ctm.rewind(self.ctm.get_index() - 1); + key = key[..key.len() - 1].to_string(); + } + let reference = GlossaryReference::with_display(key, display); + + Ok(self + .options + .document + .glossary + .lock() + .unwrap() + .add_reference(reference)) + } + /// parses plain text as a string until it encounters an unescaped special inline char fn parse_plain(&mut self) -> ParseResult<PlainText> { if self.ctm.check_char(&LB) { @@ -499,13 +549,11 @@ impl ParseInline for Parser { } else { return Err(self.ctm.rewind_with_error(start_index)); }; - self.ctm.seek_one()?; + if !self.ctm.check_eof() { + self.ctm.seek_one()?; + } - let metadata = if let Ok(meta) = self.parse_inline_metadata() { - Some(meta) - } else { - None - }; + let metadata = self.parse_inline_metadata().ok(); let placeholder = Arc::new(RwLock::new(Placeholder::new(name, metadata))); self.options diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e35a7ab..c9d5d11 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -6,7 +6,7 @@ use self::block::ParseBlock; use crate::elements::tokens::LB; use crate::elements::{Document, ImportAnchor}; use crate::references::configuration::keys::{ - IMP_BIBLIOGRAPHY, IMP_CONFIGS, IMP_IGNORE, IMP_STYLESHEETS, + IMP_BIBLIOGRAPHY, IMP_CONFIGS, IMP_GLOSSARY, IMP_IGNORE, IMP_STYLESHEETS, }; use crate::references::configuration::Value; use charred::tapemachine::{CharTapeMachine, TapeError, TapeResult}; @@ -27,6 +27,7 @@ const DEFAULT_IMPORTS: &'static [(&str, &str)] = &[ ("manifest.toml", "manifest"), ("bibliography.toml", "bibliography"), ("bibliography2.bib.toml", "bibliography"), + ("glossary.toml", "glossary"), ("style.css", "stylesheet"), ]; @@ -225,6 +226,23 @@ impl Parser { Ok(()) } + /// Imports a glossary + fn import_glossary(&self, path: PathBuf) -> ParseResult<()> { + let contents = self.import_text_file(path)?; + let value = contents + .parse::<toml::Value>() + .map_err(|_| self.ctm.err())?; + self.options + .document + .glossary + .lock() + .unwrap() + .assign_from_toml(value) + .unwrap_or_else(|e| log::error!("{}", e)); + + Ok(()) + } + /// Imports a path fn import(&mut self, path: String, args: &HashMap<String, Value>) -> ImportType { log::debug!( @@ -274,6 +292,9 @@ impl Parser { Some(s) if s == "manifest".to_string() || s == "config" => { ImportType::Manifest(self.import_manifest(path)) } + Some(s) if s == "glossary".to_string() => { + ImportType::Glossary(self.import_glossary(path)) + } _ => { lazy_static::lazy_static! { static ref BIB_NAME: Regex = Regex::new(r".*\.bib\.toml$").unwrap(); @@ -379,6 +400,20 @@ impl Parser { self.import(s, &args); } } + + if let Some(Value::Array(mut imp)) = self + .options + .document + .config + .get_entry(IMP_GLOSSARY) + .and_then(|e| Some(e.get().clone())) + { + let args = + maplit::hashmap! {"type".to_string() => Value::String("glossary".to_string())}; + while let Some(Value::String(s)) = imp.pop() { + self.import(s, &args); + } + } } } @@ -387,5 +422,6 @@ pub(crate) enum ImportType { Stylesheet(ParseResult<()>), Bibliography(ParseResult<()>), Manifest(ParseResult<()>), + Glossary(ParseResult<()>), None, } diff --git a/src/references/bibliography.rs b/src/references/bibliography.rs index 4f0b17c..923990b 100644 --- a/src/references/bibliography.rs +++ b/src/references/bibliography.rs @@ -17,51 +17,12 @@ use bibliographix::bibliography::bibliography_entry::{ BibliographyEntry, BibliographyEntryReference, }; -macro_rules! plain_text { - ($e:expr) => { - Inline::Plain(PlainText { value: $e }) - }; -} - -macro_rules! bold_text { - ($e:expr) => { - Inline::Bold(BoldText { - value: vec![Inline::Plain(PlainText { value: $e })], - }) - }; -} - -macro_rules! italic_text { - ($e:expr) => { - Inline::Italic(ItalicText { - value: vec![Inline::Plain(PlainText { value: $e })], - }) - }; -} - -macro_rules! url_text { - ($e:expr) => { - Inline::Url(Url { - url: $e, - description: None, - }) - }; -} - -macro_rules! list_item { - ($e:expr, $k:expr) => { - ListItem::new( - Line::Anchor(Anchor { - inner: Box::new(Line::Text($e)), - key: $k, - }), - 0, - true, - ) - }; -} - const DATE_FORMAT: &str = "%d.%m.%Y"; +use crate::bold_text; +use crate::italic_text; +use crate::list_item; +use crate::plain_text; +use crate::url_text; /// Creates a list from a list of bib items pub fn create_bib_list(entries: Vec<BibliographyEntryReference>) -> List { diff --git a/src/references/configuration/keys.rs b/src/references/configuration/keys.rs index 0c934ce..89f33b5 100644 --- a/src/references/configuration/keys.rs +++ b/src/references/configuration/keys.rs @@ -5,4 +5,5 @@ pub const IMP_IGNORE: &str = "ignored-imports"; pub const IMP_STYLESHEETS: &str = "included-stylesheets"; pub const IMP_CONFIGS: &str = "included-configs"; pub const IMP_BIBLIOGRAPHY: &str = "included-bibliography"; +pub const IMP_GLOSSARY: &str = "included-glossary"; pub const EMBED_EXTERNAL: &str = "embed-external"; diff --git a/src/references/glossary.rs b/src/references/glossary.rs new file mode 100644 index 0000000..0751cbc --- /dev/null +++ b/src/references/glossary.rs @@ -0,0 +1,188 @@ +use crate::elements::{ + Anchor, BoldText, Inline, ItalicText, Line, List, ListItem, PlainText, TextLine, +}; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +use crate::bold_text; +use crate::italic_text; +use crate::plain_text; + +const K_LONG: &str = "long"; +const K_DESCRIPTION: &str = "description"; + +/// A glossary manager responsible for handling glossary entries and references to those entries +#[derive(Clone, Debug)] +pub struct GlossaryManager { + entries: HashMap<String, Arc<Mutex<GlossaryEntry>>>, + references: Vec<Arc<Mutex<GlossaryReference>>>, +} + +/// A single glossary entry +#[derive(Clone, Debug)] +pub struct GlossaryEntry { + pub short: String, + pub long: String, + pub description: String, + pub is_assigned: bool, +} + +/// A single glossary reference +#[derive(Clone, Debug)] +pub struct GlossaryReference { + pub short: String, + pub display: GlossaryDisplay, + pub entry: Option<Arc<Mutex<GlossaryEntry>>>, +} + +/// A glossary display value that determines which value +/// of a glossary entry will be rendered +#[derive(Clone, Debug)] +pub enum GlossaryDisplay { + Short, + Long, +} + +impl GlossaryManager { + /// Creates a new glossary manager + pub fn new() -> Self { + Self { + entries: HashMap::new(), + references: Vec::new(), + } + } + + /// Adds a new glossary entry to the manager + pub fn add_entry(&mut self, entry: GlossaryEntry) -> Arc<Mutex<GlossaryEntry>> { + let key = entry.short.clone(); + let entry = Arc::new(Mutex::new(entry)); + self.entries.insert(key.clone(), Arc::clone(&entry)); + log::debug!("Added glossary entry {}", key); + + entry + } + + /// Adds a new glossary reference to the manager + pub fn add_reference(&mut self, reference: GlossaryReference) -> Arc<Mutex<GlossaryReference>> { + let reference = Arc::new(Mutex::new(reference)); + self.references.push(Arc::clone(&reference)); + + reference + } + + /// Assignes bibliography entries from toml + pub fn assign_from_toml(&mut self, value: toml::Value) -> Result<(), String> { + let table = value.as_table().ok_or("Failed to parse toml".to_string())?; + + log::debug!("Assigning glossary entries from toml..."); + for (key, value) in table { + let long = value.get(K_LONG).and_then(|l| l.as_str()); + let description = value.get(K_DESCRIPTION).and_then(|d| d.as_str()); + if let Some(long) = long { + if let Some(description) = description { + let entry = GlossaryEntry { + description: description.to_string(), + long: long.to_string(), + short: key.clone(), + is_assigned: false, + }; + self.add_entry(entry); + } else { + log::warn!( + "Failed to parse glossary entry {}: Missing field '{}'", + key, + K_DESCRIPTION + ); + } + } else { + log::warn!( + "Failed to parse glossary entry {}: Missing field '{}'", + key, + K_LONG + ); + } + } + + Ok(()) + } + + /// Assignes entries to references + pub fn assign_entries_to_references(&self) { + for reference in &self.references { + let mut reference = reference.lock().unwrap(); + + if let Some(entry) = self.entries.get(&reference.short) { + reference.entry = Some(Arc::clone(entry)); + let mut entry = entry.lock().unwrap(); + + if !entry.is_assigned { + entry.is_assigned = true; + reference.display = GlossaryDisplay::Long; + } + } + } + } + + /// Creates a sorted glossary list from the glossary entries + pub fn create_glossary_list(&self) -> List { + let mut list = List::new(); + let mut entries = self + .entries + .values() + .filter(|e| e.lock().unwrap().is_assigned) + .cloned() + .collect::<Vec<Arc<Mutex<GlossaryEntry>>>>(); + + entries.sort_by(|a, b| { + let a = a.lock().unwrap(); + let b = b.lock().unwrap(); + if a.short > b.short { + Ordering::Greater + } else if a.short < b.short { + Ordering::Less + } else { + Ordering::Equal + } + }); + for entry in &entries { + let entry = entry.lock().unwrap(); + let mut line = TextLine::new(); + line.subtext.push(bold_text!(entry.short.clone())); + line.subtext.push(plain_text!(" - ".to_string())); + line.subtext.push(italic_text!(entry.long.clone())); + line.subtext.push(plain_text!(" - ".to_string())); + line.subtext.push(plain_text!(entry.description.clone())); + list.add_item(ListItem::new( + Line::Anchor(Anchor { + inner: Box::new(Line::Text(line)), + key: entry.short.clone(), + }), + 0, + false, + )); + } + + list + } +} + +impl GlossaryReference { + /// Creates a new glossary reference + pub fn new(key: String) -> Self { + Self { + short: key, + display: GlossaryDisplay::Short, + entry: None, + } + } + + /// Creates a new glossary reference with a given display parameter + pub fn with_display(key: String, display: GlossaryDisplay) -> Self { + Self { + short: key, + display, + entry: None, + } + } +} diff --git a/src/references/mod.rs b/src/references/mod.rs index f5f5f64..2ebfb66 100644 --- a/src/references/mod.rs +++ b/src/references/mod.rs @@ -1,4 +1,5 @@ pub mod bibliography; pub mod configuration; +pub mod glossary; pub mod placeholders; pub mod templates; diff --git a/src/references/placeholders.rs b/src/references/placeholders.rs index cfe5275..4aa19e7 100644 --- a/src/references/placeholders.rs +++ b/src/references/placeholders.rs @@ -31,6 +31,7 @@ const S_VALUE: &str = "value"; const P_TOC: &str = "toc"; const P_BIB: &str = "bib"; +const P_GLS: &str = "gls"; const P_DATE: &str = "date"; const P_TIME: &str = "time"; const P_DATETIME: &str = "datetime"; @@ -52,6 +53,9 @@ impl ProcessPlaceholders for Document { P_BIB => pholder.set_value(block!(Block::List(create_bib_list( self.bibliography.get_entry_list_by_occurrence() )))), + P_GLS => pholder.set_value(block!(Block::List( + self.glossary.lock().unwrap().create_glossary_list() + ))), P_DATE => pholder.set_value(inline!(Inline::Plain(PlainText { value: get_date_string() }))), diff --git a/src/utils/macros.rs b/src/utils/macros.rs new file mode 100644 index 0000000..f21ecb4 --- /dev/null +++ b/src/utils/macros.rs @@ -0,0 +1,48 @@ +#[macro_export] +macro_rules! plain_text { + ($e:expr) => { + Inline::Plain(PlainText { value: $e }) + }; +} + +#[macro_export] +macro_rules! bold_text { + ($e:expr) => { + Inline::Bold(BoldText { + value: vec![Inline::Plain(PlainText { value: $e })], + }) + }; +} + +#[macro_export] +macro_rules! italic_text { + ($e:expr) => { + Inline::Italic(ItalicText { + value: vec![Inline::Plain(PlainText { value: $e })], + }) + }; +} + +#[macro_export] +macro_rules! url_text { + ($e:expr) => { + Inline::Url(Url { + url: $e, + description: None, + }) + }; +} + +#[macro_export] +macro_rules! list_item { + ($e:expr, $k:expr) => { + ListItem::new( + Line::Anchor(Anchor { + inner: Box::new(Line::Text($e)), + key: $k, + }), + 0, + true, + ) + }; +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 0c2f117..20780f3 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,2 +1,3 @@ pub mod downloads; +pub mod macros; pub mod parsing;