You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
snekdown/src/elements/mod.rs

912 lines
24 KiB
Rust

/*
* Snekdown - Custom Markdown flavour and parser
* Copyright (C) 2021 Trivernis
* See LICENSE for more information.
*/
pub mod tokens;
use crate::format::PlaceholderTemplate;
use crate::references::glossary::{GlossaryManager, GlossaryReference};
use crate::references::placeholders::ProcessPlaceholders;
use crate::references::templates::{Template, TemplateVariable};
use crate::settings::Settings;
use crate::utils::downloads::{DownloadManager, PendingDownload};
use crate::utils::image_converting::{ImageConverter, PendingImage};
use asciimath_rs::elements::special::Expression;
use bibliographix::bib_manager::BibManager;
use bibliographix::bibliography::bibliography_entry::BibliographyEntryReference;
use bibliographix::references::bib_reference::BibRefAnchor;
use image::ImageFormat;
use mime::Mime;
use parking_lot::Mutex;
use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
pub const SECTION: &str = "section";
pub const PARAGRAPH: &str = "paragraph";
pub const LIST: &str = "list";
pub const TABLE: &str = "table";
pub const CODE_BLOCK: &str = "code_block";
pub const QUOTE: &str = "quote";
pub const IMPORT: &str = "import";
#[derive(Clone, Debug)]
pub enum MetadataValue {
String(String),
Integer(i64),
Float(f64),
Bool(bool),
Placeholder(Arc<RwLock<Placeholder>>),
Template(Template),
}
#[derive(Clone, Debug)]
pub enum Element {
Block(Box<Block>),
Line(Box<Line>),
Inline(Box<Inline>),
}
#[derive(Clone, Debug)]
pub enum Block {
Section(Section),
Paragraph(Paragraph),
List(List),
Table(Table),
CodeBlock(CodeBlock),
MathBlock(MathBlock),
Quote(Quote),
Import(Import),
Placeholder(Arc<RwLock<Placeholder>>),
Null,
}
#[derive(Clone, Debug)]
pub enum Line {
Text(TextLine),
Ruler(Ruler),
RefLink(RefLink),
Anchor(Anchor),
Centered(Centered),
BibEntry(BibEntry),
}
#[derive(Clone, Debug)]
pub struct Document {
pub elements: Vec<Block>,
pub(crate) is_root: bool,
pub(crate) path: Option<String>,
pub(crate) placeholders: Vec<Arc<RwLock<Placeholder>>>,
pub config: Arc<Mutex<Settings>>,
pub bibliography: BibManager,
pub downloads: Arc<Mutex<DownloadManager>>,
pub images: Arc<Mutex<ImageConverter>>,
pub stylesheets: Vec<Arc<Mutex<PendingDownload>>>,
pub glossary: Arc<Mutex<GlossaryManager>>,
}
#[derive(Clone, Debug)]
pub struct Section {
pub(crate) header: Header,
pub(crate) elements: Vec<Block>,
pub(crate) metadata: Option<InlineMetadata>,
}
#[derive(Clone, Debug)]
pub struct Header {
pub(crate) size: u8,
pub(crate) line: Line,
pub(crate) anchor: String,
}
#[derive(Clone, Debug)]
pub struct Paragraph {
pub(crate) elements: Vec<Line>,
}
#[derive(Clone, Debug)]
pub struct List {
pub(crate) ordered: bool,
pub items: Vec<ListItem>,
}
#[derive(Clone, Debug)]
pub struct ListItem {
pub(crate) text: Line,
pub(crate) level: u16,
pub(crate) ordered: bool,
pub(crate) children: Vec<ListItem>,
}
#[derive(Clone, Debug)]
pub struct Table {
pub(crate) header: Row,
pub(crate) rows: Vec<Row>,
}
#[derive(Clone, Debug)]
pub struct Row {
pub(crate) cells: Vec<Cell>,
}
#[derive(Clone, Debug)]
pub struct Cell {
pub(crate) text: Line,
}
#[derive(Clone, Debug)]
pub struct CodeBlock {
pub(crate) language: String,
pub(crate) code: String,
}
#[derive(Clone, Debug)]
pub struct Quote {
pub(crate) metadata: Option<InlineMetadata>,
pub(crate) text: Vec<TextLine>,
}
#[derive(Clone, Debug)]
pub struct Import {
pub(crate) path: String,
pub(crate) anchor: Arc<RwLock<ImportAnchor>>,
}
#[derive(Clone, Debug)]
pub struct ImportAnchor {
pub(crate) document: Option<Document>,
}
#[derive(Clone, Debug)]
pub struct InlineMetadata {
pub(crate) data: HashMap<String, MetadataValue>,
}
#[derive(Clone, Debug)]
pub struct Ruler {}
#[derive(Clone, Debug)]
pub struct TextLine {
pub subtext: Vec<Inline>,
}
#[derive(Clone, Debug)]
pub enum Inline {
Plain(PlainText),
Bold(BoldText),
Italic(ItalicText),
Underlined(UnderlinedText),
Striked(StrikedText),
Monospace(MonospaceText),
Superscript(SuperscriptText),
Url(Url),
Image(Image),
Placeholder(Arc<RwLock<Placeholder>>),
Checkbox(Checkbox),
Emoji(Emoji),
Colored(Colored),
Math(Math),
BibReference(Arc<RwLock<BibReference>>),
GlossaryReference(Arc<Mutex<GlossaryReference>>),
TemplateVar(Arc<RwLock<TemplateVariable>>),
CharacterCode(CharacterCode),
LineBreak,
Arrow(Arrow),
Anchor(Anchor),
}
#[derive(Clone, Debug)]
pub struct PlainText {
pub(crate) value: String,
}
#[derive(Clone, Debug)]
pub struct BoldText {
pub(crate) value: Vec<Inline>,
}
#[derive(Clone, Debug)]
pub struct ItalicText {
pub(crate) value: Vec<Inline>,
}
#[derive(Clone, Debug)]
pub struct UnderlinedText {
pub(crate) value: Vec<Inline>,
}
#[derive(Clone, Debug)]
pub struct StrikedText {
pub(crate) value: Vec<Inline>,
}
#[derive(Clone, Debug)]
pub struct MonospaceText {
pub(crate) value: String,
}
#[derive(Clone, Debug)]
pub struct SuperscriptText {
pub(crate) value: Vec<Inline>,
}
#[derive(Clone, Debug)]
pub struct Checkbox {
pub(crate) value: bool,
}
#[derive(Clone, Debug)]
pub struct Url {
pub description: Option<Vec<Inline>>,
pub url: String,
}
#[derive(Clone, Debug)]
pub struct Image {
pub(crate) url: Url,
pub(crate) metadata: Option<InlineMetadata>,
pub(crate) image_data: Arc<Mutex<PendingImage>>,
}
#[derive(Clone, Debug)]
pub struct Placeholder {
pub(crate) name: String,
pub(crate) value: Option<Element>,
pub(crate) metadata: Option<InlineMetadata>,
}
#[derive(Clone, Debug)]
pub struct RefLink {
pub(crate) description: TextLine,
pub(crate) reference: String,
}
#[derive(Clone, Debug)]
pub struct Anchor {
pub(crate) inner: Box<Line>,
pub(crate) key: String,
}
#[derive(Clone, Debug)]
pub struct Centered {
pub(crate) line: TextLine,
}
#[derive(Clone, Debug)]
pub struct Emoji {
pub(crate) value: char,
pub(crate) name: String,
}
#[derive(Clone, Debug)]
pub struct Colored {
pub(crate) value: Box<Inline>,
pub(crate) color: String,
}
#[derive(Clone, Debug)]
pub struct Math {
pub(crate) expression: Expression,
}
#[derive(Clone, Debug)]
pub struct MathBlock {
pub(crate) expression: Expression,
}
#[derive(Clone, Debug)]
pub struct CharacterCode {
pub(crate) code: String,
}
#[derive(Clone, Debug)]
pub enum Arrow {
RightArrow,
LeftArrow,
LeftRightArrow,
BigRightArrow,
BigLeftArrow,
BigLeftRightArrow,
}
// implementations
impl Document {
/// Creates a new parent document
pub fn new() -> Self {
Self {
elements: Vec::new(),
is_root: true,
path: None,
placeholders: Vec::new(),
config: Arc::new(Mutex::new(Settings::default())),
bibliography: BibManager::new(),
stylesheets: Vec::new(),
downloads: Arc::new(Mutex::new(DownloadManager::new())),
images: Arc::new(Mutex::new(ImageConverter::new())),
glossary: Arc::new(Mutex::new(GlossaryManager::new())),
}
}
/// Creates a new child document
pub fn create_child(&self) -> Self {
Self {
elements: Vec::new(),
is_root: false,
path: None,
placeholders: Vec::new(),
config: self.config.clone(),
bibliography: self.bibliography.create_child(),
stylesheets: Vec::new(),
downloads: Arc::clone(&self.downloads),
images: Arc::clone(&self.images),
glossary: Arc::clone(&self.glossary),
}
}
pub fn add_element(&mut self, element: Block) {
self.elements.push(element)
}
pub fn add_placeholder(&mut self, placeholder: Arc<RwLock<Placeholder>>) {
self.placeholders.push(placeholder);
}
pub fn create_toc(&self, ordered: bool) -> List {
let mut list = List::new();
list.ordered = ordered;
self.elements.iter().for_each(|e| match e {
Block::Section(sec) => {
if !sec.is_hidden_in_toc() {
let mut item =
ListItem::new(Line::RefLink(sec.header.get_anchor()), 1, ordered);
item.children.append(&mut sec.get_toc_list(ordered).items);
list.add_item(item);
}
}
Block::Import(imp) => {
let anchor = imp.anchor.read().unwrap();
if let Some(doc) = &anchor.document {
list.items.append(&mut doc.create_toc(ordered).items)
}
}
_ => {}
});
list
}
/// Processes section and import elements
///
/// if it encounters a section it checks if the sections is of smaller order than the previous one
/// if thats the case it grabs the previous one and adds the section to its children
///
/// if it encounters an import, it loads the imports top elements to its own
pub fn postprocess_imports(&mut self) {
let mut new_order: Vec<Block> = Vec::with_capacity(self.elements.len());
self.elements.reverse();
let mut count: usize = 0;
let mut last_section: Option<(u8, usize)> = None;
while let Some(element) = self.elements.pop() {
match element {
Block::Section(sec) => {
if let Some((last_size, last_pos)) = last_section {
if sec.header.size > last_size {
let last = new_order.get_mut(last_pos).unwrap();
if let Block::Section(p_sec) = last {
p_sec.add_section(sec);
continue;
}
}
}
last_section = Some((sec.header.size, count));
new_order.push(Block::Section(sec));
}
Block::Import(imp) => {
let arc_anchor = Arc::clone(&imp.anchor);
let anchor = &mut arc_anchor.write().unwrap();
if let Some(doc) = &mut anchor.document {
self.placeholders.append(&mut doc.placeholders);
doc.elements.reverse();
self.elements.append(&mut doc.elements);
anchor.document = None;
continue;
} else {
new_order.push(Block::Import(imp));
}
}
_ => {
if let Some((_, last_pos)) = last_section {
let last = new_order.get_mut(last_pos).unwrap();
if let Block::Section(p_sec) = last {
p_sec.add_element(element);
continue;
}
}
new_order.push(element)
}
}
count += 1;
}
self.elements = new_order;
}
pub fn post_process(&mut self) {
self.postprocess_imports();
if self.is_root {
self.process_definitions();
self.bibliography.assign_entries_to_references();
self.glossary.lock().assign_entries_to_references();
self.process_placeholders();
self.process_media();
}
}
fn process_media(&self) {
let downloads = Arc::clone(&self.downloads);
if self.config.lock().features.embed_external {
downloads.lock().download_all();
}
if let Some(s) = &self.config.lock().images.format {
if let Some(format) = ImageFormat::from_extension(s) {
self.images.lock().set_target_format(format);
}
}
let mut image_width = 0;
let mut image_height = 0;
if let Some(i) = self.config.lock().images.max_width {
image_width = i;
image_height = i;
}
if let Some(i) = self.config.lock().images.max_height {
image_height = i;
if image_width <= 0 {
image_width = i;
}
}
if image_width > 0 && image_height > 0 {
self.images
.lock()
.set_target_size((image_width as u32, image_height as u32));
}
self.images.lock().convert_all();
}
}
impl Section {
pub fn new(header: Header) -> Self {
Self {
header,
elements: Vec::new(),
metadata: None,
}
}
pub fn add_element(&mut self, element: Block) {
self.elements.push(element)
}
pub fn get_toc_list(&self, ordered: bool) -> List {
let mut list = List::new();
self.elements.iter().for_each(|e| {
if let Block::Section(sec) = e {
if !sec.is_hidden_in_toc() {
let mut item =
ListItem::new(Line::RefLink(sec.header.get_anchor()), 1, ordered);
item.children.append(&mut sec.get_toc_list(ordered).items);
list.add_item(item);
}
}
});
list
}
pub(crate) fn is_hidden_in_toc(&self) -> bool {
if let Some(meta) = &self.metadata {
meta.get_bool("toc-hidden")
} else {
false
}
}
/// adds a child section to the section
/// It either adds it directly to its elements or iterates through its children to
/// add it to the fitting one
pub(crate) fn add_section(&mut self, section: Section) {
if section.header.size == self.header.size + 1 {
self.elements.push(Block::Section(section))
} else {
let has_parent = RwLock::new(AtomicBool::new(true));
let iterator = self.elements.iter_mut().rev().filter(|e| {
if let Block::Section(sec) = e {
if sec.header.size > section.header.size {
has_parent.write().unwrap().store(true, Ordering::Relaxed);
true
} else {
false
}
} else {
false
}
});
if has_parent.read().unwrap().load(Ordering::Relaxed) {
for sec in iterator {
if let Block::Section(sec) = sec {
if sec.header.size < section.header.size {
sec.add_section(section);
break;
}
}
}
} else {
self.elements.push(Block::Section(section));
}
}
}
}
impl Header {
pub fn new(content: Line, anchor: String) -> Self {
Self {
size: 0,
anchor,
line: content,
}
}
pub fn get_anchor(&self) -> RefLink {
RefLink {
description: self.line.as_raw_text().as_plain_line(),
reference: self.anchor.clone(),
}
}
}
impl Paragraph {
pub fn new() -> Self {
Self {
elements: Vec::new(),
}
}
pub fn add_element(&mut self, element: Line) {
self.elements.push(element)
}
}
impl List {
pub fn new() -> Self {
Self {
ordered: false,
items: Vec::new(),
}
}
pub fn add_item(&mut self, item: ListItem) {
self.items.push(item)
}
}
impl ListItem {
pub fn new(text: Line, level: u16, ordered: bool) -> Self {
Self {
text,
level,
ordered,
children: Vec::new(),
}
}
pub fn add_child(&mut self, child: ListItem) {
self.children.push(child)
}
}
impl TextLine {
pub fn new() -> Self {
Self {
subtext: Vec::new(),
}
}
pub fn add_subtext(&mut self, subtext: Inline) {
self.subtext.push(subtext)
}
pub fn as_plain_line(&self) -> TextLine {
TextLine {
subtext: self
.subtext
.iter()
.map(|s| Inline::Plain(s.as_plain_text()))
.collect(),
}
}
}
impl Table {
pub fn new(header: Row) -> Self {
Self {
header,
rows: Vec::new(),
}
}
pub fn add_row(&mut self, row: Row) {
self.rows.push(row)
}
}
impl Row {
pub fn new() -> Self {
Self { cells: Vec::new() }
}
pub fn add_cell(&mut self, cell: Cell) {
self.cells.push(cell)
}
}
impl Url {
pub fn new(description: Option<Vec<Inline>>, url: String) -> Self {
Self { description, url }
}
}
impl Quote {
pub fn new(metadata: Option<InlineMetadata>) -> Self {
Self {
metadata,
text: Vec::new(),
}
}
pub fn add_text(&mut self, text: TextLine) {
self.text.push(text)
}
/// Strips a single linebreak from the end of the quote
pub fn strip_linebreak(&mut self) {
if let Some(last) = self.text.last_mut() {
if let Some(Inline::LineBreak) = last.subtext.last() {
last.subtext.pop();
}
}
}
}
impl ImportAnchor {
pub fn new() -> Self {
Self { document: None }
}
pub fn set_document(&mut self, document: Document) {
self.document = Some(document);
}
}
impl PartialEq for Import {
fn eq(&self, other: &Self) -> bool {
self.path == other.path
}
}
impl Placeholder {
pub fn new(name: String, metadata: Option<InlineMetadata>) -> Self {
Self {
name,
value: None,
metadata,
}
}
pub fn set_value(&mut self, value: Element) {
self.value = Some(value);
}
}
pub trait Metadata {
fn get_bool(&self, key: &str) -> bool;
fn get_string(&self, key: &str) -> Option<String>;
fn get_float(&self, key: &str) -> Option<f64>;
fn get_integer(&self, key: &str) -> Option<i64>;
fn get_string_map(&self) -> HashMap<String, String>;
}
impl Metadata for InlineMetadata {
fn get_bool(&self, key: &str) -> bool {
if let Some(MetadataValue::Bool(value)) = self.data.get(key) {
*value
} else {
false
}
}
fn get_string(&self, key: &str) -> Option<String> {
if let Some(MetadataValue::String(value)) = self.data.get(key) {
Some(value.clone())
} else {
None
}
}
fn get_float(&self, key: &str) -> Option<f64> {
if let Some(MetadataValue::Float(f)) = self.data.get(key) {
Some(*f)
} else if let Some(MetadataValue::Integer(i)) = self.data.get(key) {
Some(*i as f64)
} else {
None
}
}
fn get_integer(&self, key: &str) -> Option<i64> {
if let Some(MetadataValue::Integer(i)) = self.data.get(key) {
Some(*i)
} else {
None
}
}
fn get_string_map(&self) -> HashMap<String, String> {
let mut string_map = HashMap::new();
for (k, v) in &self.data {
match v {
MetadataValue::String(s) => string_map.insert(k.clone(), s.clone()),
MetadataValue::Bool(b) => string_map.insert(k.clone(), b.to_string()),
MetadataValue::Float(f) => string_map.insert(k.clone(), f.to_string()),
MetadataValue::Integer(i) => string_map.insert(k.clone(), i.to_string()),
_ => None,
};
}
string_map
}
}
impl Image {
pub fn get_content(&self) -> Option<Vec<u8>> {
let mut data = None;
std::mem::swap(&mut data, &mut self.image_data.lock().data);
data
}
pub fn get_mime_type(&self) -> Mime {
self.image_data.lock().mime.clone()
}
}
#[derive(Clone, Debug)]
pub struct BibEntry {
pub key: String,
pub entry: BibliographyEntryReference,
}
#[derive(Clone, Debug)]
pub struct BibReference {
pub(crate) key: String,
pub(crate) entry_anchor: Arc<Mutex<BibRefAnchor>>,
pub(crate) display: Option<String>,
}
impl BibReference {
pub fn new(key: String, display: Option<String>, anchor: Arc<Mutex<BibRefAnchor>>) -> Self {
Self {
key: key.to_string(),
display,
entry_anchor: anchor,
}
}
pub(crate) fn get_formatted(&self) -> String {
if let Some(entry) = &self.entry_anchor.lock().entry {
let entry = entry.lock();
if let Some(display) = &self.display {
let mut template = PlaceholderTemplate::new(display.clone());
let mut value_map = HashMap::new();
value_map.insert("key".to_string(), entry.key());
if let Some(ord) = entry.raw_fields.get("ord") {
value_map.insert("number".to_string(), ord.clone());
}
template.set_replacements(value_map);
return template.render();
}
return format!("{}", entry.key());
}
return "citation needed".to_string();
}
}
impl MetadataValue {
pub fn to_string(&self) -> String {
match self {
MetadataValue::String(s) => s.clone(),
MetadataValue::Placeholder(_) => "".to_string(),
MetadataValue::Integer(i) => i.to_string(),
MetadataValue::Float(f) => f.to_string(),
MetadataValue::Bool(b) => b.to_string(),
MetadataValue::Template(_) => "".to_string(),
}
}
}
impl Line {
pub fn as_raw_text(&self) -> TextLine {
match self {
Line::Text(t) => t.clone(),
Line::Ruler(_) => TextLine::new(),
Line::RefLink(r) => r.description.clone(),
Line::Anchor(a) => a.inner.as_raw_text().as_plain_line(),
Line::Centered(c) => c.line.clone(),
Line::BibEntry(_) => TextLine::new(),
}
}
}
impl Inline {
pub fn as_plain_text(&self) -> PlainText {
match self {
Inline::Plain(p) => p.clone(),
Inline::Bold(b) => b.value.iter().fold(
PlainText {
value: String::new(),
},
|a, b| PlainText {
value: format!("{} {}", a.value, b.as_plain_text().value),
},
),
Inline::Italic(i) => i.value.iter().fold(
PlainText {
value: String::new(),
},
|a, b| PlainText {
value: format!("{} {}", a.value, b.as_plain_text().value),
},
),
Inline::Underlined(u) => u.value.iter().fold(
PlainText {
value: String::new(),
},
|a, b| PlainText {
value: format!("{} {}", a.value, b.as_plain_text().value),
},
),
Inline::Striked(s) => s.value.iter().fold(
PlainText {
value: String::new(),
},
|a, b| PlainText {
value: format!("{} {}", a.value, b.as_plain_text().value),
},
),
Inline::Monospace(m) => PlainText {
value: m.value.clone(),
},
Inline::Superscript(s) => s.value.iter().fold(
PlainText {
value: String::new(),
},
|a, b| PlainText {
value: format!("{} {}", a.value, b.as_plain_text().value),
},
),
Inline::Colored(c) => c.value.as_plain_text(),
_ => PlainText {
value: String::new(),
},
}
}
}