Add glossary implementation
Glossary entries can be defined with `~KEY` for the short form and `~~KEY` for the long form. If a glossary entry is referenced for the first time it will always be rendered as the long form. Glossary entries can be defined in a toml file (default is glossary.toml) similar to bibliography. Signed-off-by: trivernis <trivernis@protonmail.com>feature/epub-rendering
parent
9424d04c37
commit
e8cdbc3b06
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
pub mod bibliography;
|
pub mod bibliography;
|
||||||
pub mod configuration;
|
pub mod configuration;
|
||||||
|
pub mod glossary;
|
||||||
pub mod placeholders;
|
pub mod placeholders;
|
||||||
pub mod templates;
|
pub mod templates;
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
pub mod downloads;
|
pub mod downloads;
|
||||||
|
pub mod macros;
|
||||||
pub mod parsing;
|
pub mod parsing;
|
||||||
|
Loading…
Reference in New Issue