Improve template syntax

Templates are still wip. Currently they can be used in metadata
by starting the value with %
pull/1/head
trivernis 5 years ago
parent 8498f4c66c
commit 792d991fb2

2
Cargo.lock generated

@ -566,7 +566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "snekdown" name = "snekdown"
version = "0.14.0" version = "0.15.0"
dependencies = [ dependencies = [
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "colored 1.9.3 (registry+https://github.com/rust-lang/crates.io-index)",

@ -1,6 +1,6 @@
[package] [package]
name = "snekdown" name = "snekdown"
version = "0.14.0" version = "0.15.0"
authors = ["trivernis <trivernis@protonmail.com>"] authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018" edition = "2018"
license-file = "LICENSE" license-file = "LICENSE"

@ -200,5 +200,5 @@ The end goal is to have a markup language with features similar to LaTeX.
- [ ] Cross References - [ ] Cross References
- [ ] Figures - [ ] Figures
- [ ] EPUB Rendering (PDF is too hard) - [ ] EPUB Rendering (PDF is too hard)
- [ ] Custom Elements via templates - [ ] Custom Elements via templates (50%)
- [ ] Custom Stylesheets - [ ] Custom Stylesheets

@ -1,5 +1,6 @@
use crate::format::Template; use crate::format::PlaceholderTemplate;
use crate::parsing::bibliography::{BibEntry, BibReference}; use crate::parsing::bibliography::{BibEntry, BibReference};
use crate::parsing::configuration::Value;
use crate::parsing::elements::*; use crate::parsing::elements::*;
use htmlescape::{encode_attribute, encode_minimal}; use htmlescape::{encode_attribute, encode_minimal};
use minify::html::minify; use minify::html::minify;
@ -61,6 +62,7 @@ impl ToHtml for Inline {
Inline::Emoji(emoji) => emoji.to_html(), Inline::Emoji(emoji) => emoji.to_html(),
Inline::Colored(colored) => colored.to_html(), Inline::Colored(colored) => colored.to_html(),
Inline::BibReference(bibref) => bibref.lock().unwrap().to_html(), Inline::BibReference(bibref) => bibref.lock().unwrap().to_html(),
Inline::TemplateVar(var) => var.lock().unwrap().to_html(),
} }
} }
} }
@ -88,6 +90,7 @@ impl ToHtml for MetadataValue {
MetadataValue::Placeholder(ph) => ph.lock().unwrap().to_html(), MetadataValue::Placeholder(ph) => ph.lock().unwrap().to_html(),
MetadataValue::Bool(b) => format!("{}", b), MetadataValue::Bool(b) => format!("{}", b),
MetadataValue::Float(f) => format!("{}", f), MetadataValue::Float(f) => format!("{}", f),
MetadataValue::Template(t) => t.to_html(),
} }
} }
} }
@ -416,7 +419,7 @@ impl ToHtml for Anchor {
impl ToHtml for InlineMetadata { impl ToHtml for InlineMetadata {
fn to_html(&self) -> String { fn to_html(&self) -> String {
if let Some(MetadataValue::String(format)) = self.data.get("display") { if let Some(MetadataValue::String(format)) = self.data.get("display") {
let mut template = Template::new(format.clone()); let mut template = PlaceholderTemplate::new(format.clone());
self.data self.data
.iter() .iter()
.for_each(|(k, v)| template.add_replacement(k, v.to_html().as_str())); .for_each(|(k, v)| template.add_replacement(k, v.to_html().as_str()));
@ -483,7 +486,25 @@ impl ToHtml for BibEntry {
} }
if let Some(display) = &self.display { if let Some(display) = &self.display {
let display = display.lock().unwrap(); let display = display.lock().unwrap();
let mut template = Template::new(display.get().as_string()); if let Value::Template(template) = display.get() {
let replacements = self
.as_map()
.iter()
.map(|(k, v)| {
(
k.clone(),
Element::Inline(Box::new(Inline::Plain(PlainText {
value: v.clone(),
}))),
)
})
.collect();
return template
.render(replacements)
.iter()
.fold("".to_string(), |a, b| format!("{}{}", a, b.to_html()));
}
let mut template = PlaceholderTemplate::new(display.get().as_string());
template.set_replacements(self.as_map()); template.set_replacements(self.as_map());
format!( format!(
"<span id='{}'>{}</span>", "<span id='{}'>{}</span>",
@ -506,3 +527,26 @@ impl ToHtml for BibEntry {
} }
} }
} }
impl ToHtml for Template {
fn to_html(&self) -> String {
self.text
.iter()
.fold("".to_string(), |a, b| format!("{}{}", a, b.to_html()))
}
}
impl ToHtml for TemplateVariable {
fn to_html(&self) -> String {
if let Some(value) = &self.value {
format!(
"{}{}{}",
encode_minimal(self.prefix.as_str()),
value.to_html(),
encode_minimal(self.suffix.as_str())
)
} else {
"".to_string()
}
}
}

@ -3,12 +3,12 @@ use std::collections::HashMap;
pub mod html; pub mod html;
pub struct Template { pub struct PlaceholderTemplate {
value: String, value: String,
replacements: HashMap<String, String>, replacements: HashMap<String, String>,
} }
impl Template { impl PlaceholderTemplate {
pub fn empty() -> Self { pub fn empty() -> Self {
Self::new(String::new()) Self::new(String::new())
} }

@ -1,4 +1,4 @@
use crate::format::Template; use crate::format::PlaceholderTemplate;
use crate::parsing::configuration::keys::{BIB_DISPLAY, BIB_HIDE_UNUSED}; use crate::parsing::configuration::keys::{BIB_DISPLAY, BIB_HIDE_UNUSED};
use crate::parsing::configuration::{ConfigRefEntry, Configuration, Value}; use crate::parsing::configuration::{ConfigRefEntry, Configuration, Value};
use crate::parsing::elements::Metadata; use crate::parsing::elements::Metadata;
@ -141,7 +141,7 @@ impl BibReference {
let entry = entry.lock().unwrap(); let entry = entry.lock().unwrap();
if let Some(display) = &self.display { if let Some(display) = &self.display {
let display = display.lock().unwrap(); let display = display.lock().unwrap();
let mut template = Template::new(display.get().as_string()); let mut template = PlaceholderTemplate::new(display.get().as_string());
template.set_replacements(entry.as_map()); template.set_replacements(entry.as_map());
return template.render(); return template.render();
} }

@ -2,7 +2,7 @@ use crate::parsing::configuration::config::RootConfig;
use crate::parsing::configuration::keys::{ use crate::parsing::configuration::keys::{
BIB_DISPLAY, BIB_HIDE_UNUSED, BIB_REF_DISPLAY, META_AUTHOR, META_DATE, META_TITLE, BIB_DISPLAY, BIB_HIDE_UNUSED, BIB_REF_DISPLAY, META_AUTHOR, META_DATE, META_TITLE,
}; };
use crate::parsing::elements::MetadataValue; use crate::parsing::elements::{MetadataValue, Template};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -15,6 +15,7 @@ pub enum Value {
Bool(bool), Bool(bool),
Float(f64), Float(f64),
Integer(i64), Integer(i64),
Template(Template),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -36,6 +37,7 @@ impl Value {
Value::Integer(int) => format!("{}", int), Value::Integer(int) => format!("{}", int),
Value::Float(f) => format!("{:02}", f), Value::Float(f) => format!("{:02}", f),
Value::Bool(b) => format!("{}", b), Value::Bool(b) => format!("{}", b),
_ => "".to_string(),
} }
} }
} }
@ -135,6 +137,7 @@ impl Configuration {
MetadataValue::Bool(bool) => self.set(key, Value::Bool(bool)), MetadataValue::Bool(bool) => self.set(key, Value::Bool(bool)),
MetadataValue::Float(f) => self.set(key, Value::Float(f)), MetadataValue::Float(f) => self.set(key, Value::Float(f)),
MetadataValue::Integer(i) => self.set(key, Value::Integer(i)), MetadataValue::Integer(i) => self.set(key, Value::Integer(i)),
MetadataValue::Template(t) => self.set(key, Value::Template(t)),
_ => {} _ => {}
} }
} }

@ -20,6 +20,7 @@ pub enum MetadataValue {
Float(f64), Float(f64),
Bool(bool), Bool(bool),
Placeholder(Arc<Mutex<Placeholder>>), Placeholder(Arc<Mutex<Placeholder>>),
Template(Template),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -161,6 +162,7 @@ pub enum Inline {
Emoji(Emoji), Emoji(Emoji),
Colored(Colored), Colored(Colored),
BibReference(Arc<Mutex<BibReference>>), BibReference(Arc<Mutex<BibReference>>),
TemplateVar(Arc<Mutex<TemplateVariable>>),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -245,6 +247,20 @@ pub struct Colored {
pub(crate) color: String, pub(crate) color: String,
} }
#[derive(Clone, Debug)]
pub struct Template {
pub(crate) text: Vec<Element>,
pub(crate) variables: HashMap<String, Arc<Mutex<TemplateVariable>>>,
}
#[derive(Clone, Debug)]
pub struct TemplateVariable {
pub(crate) prefix: String,
pub(crate) name: String,
pub(crate) suffix: String,
pub(crate) value: Option<Element>,
}
// implementations // implementations
impl Document { impl Document {
@ -592,3 +608,208 @@ impl Metadata for InlineMetadata {
} }
} }
} }
impl Element {
pub fn get_template_variables(&self) -> Vec<Arc<Mutex<TemplateVariable>>> {
match self {
Element::Block(block) => block
.get_template_variables()
.iter()
.filter_map(|e| e.clone())
.collect(),
Element::Inline(inline) => vec![inline.get_template_variable()]
.iter()
.filter_map(|e| e.clone())
.collect(),
Element::Line(line) => line
.get_template_variables()
.iter()
.filter_map(|e| e.clone())
.collect(),
}
}
pub fn freeze_variables(&mut self) -> Option<Arc<Mutex<TemplateVariable>>> {
match self {
Element::Block(b) => b.freeze_template_variables(),
Element::Line(l) => l.freeze_variables(),
Element::Inline(i) => return i.freeze_variables(),
}
None
}
}
impl Block {
pub fn get_template_variables(&self) -> Vec<Option<Arc<Mutex<TemplateVariable>>>> {
match self {
Block::Section(sec) => sec
.elements
.iter()
.map(|e| e.get_template_variables())
.flatten()
.collect(),
Block::Paragraph(par) => par
.elements
.iter()
.map(|l| l.get_template_variables())
.flatten()
.collect(),
Block::Quote(q) => q
.text
.iter()
.map(|t| t.subtext.iter().map(|i| i.get_template_variable()))
.flatten()
.collect(),
_ => Vec::new(),
}
}
pub fn freeze_template_variables(&mut self) {
match self {
Block::Section(s) => s
.elements
.iter_mut()
.for_each(|b| b.freeze_template_variables()),
Block::Paragraph(p) => p.elements.iter_mut().for_each(|l| l.freeze_variables()),
Block::Quote(q) => q.text.iter_mut().for_each(|t| {
t.subtext = t
.subtext
.iter_mut()
.map(|i| {
if let Some(t) = i.freeze_variables() {
Inline::TemplateVar(t)
} else {
(*i).clone()
}
})
.collect()
}),
_ => {}
}
}
}
impl Line {
pub fn get_template_variables(&self) -> Vec<Option<Arc<Mutex<TemplateVariable>>>> {
match self {
Line::Text(line) => line
.subtext
.iter()
.map(|s| s.get_template_variable())
.collect(),
_ => Vec::new(),
}
}
pub fn freeze_variables(&mut self) {
match self {
Line::Text(text) => {
text.subtext = text
.subtext
.iter_mut()
.map(|i| {
if let Some(t) = i.freeze_variables() {
Inline::TemplateVar(t)
} else {
(*i).clone()
}
})
.collect()
}
_ => {}
}
}
}
impl Inline {
pub fn get_template_variable(&self) -> Option<Arc<Mutex<TemplateVariable>>> {
match self {
Inline::TemplateVar(temp) => Some(Arc::clone(temp)),
Inline::Colored(col) => col.value.get_template_variable(),
Inline::Superscript(sup) => sup.value.get_template_variable(),
Inline::Striked(striked) => striked.value.get_template_variable(),
Inline::Underlined(under) => under.value.get_template_variable(),
Inline::Italic(it) => it.value.get_template_variable(),
Inline::Bold(bo) => bo.value.get_template_variable(),
_ => None,
}
}
pub fn freeze_variables(&mut self) -> Option<Arc<Mutex<TemplateVariable>>> {
match self {
Inline::TemplateVar(temp) => {
let temp = temp.lock().unwrap();
return Some(Arc::new(Mutex::new((*temp).clone())));
}
Inline::Colored(col) => {
if let Some(temp) = col.value.freeze_variables() {
col.value = Box::new(Inline::TemplateVar(temp))
}
}
Inline::Superscript(sup) => {
if let Some(temp) = sup.value.freeze_variables() {
sup.value = Box::new(Inline::TemplateVar(temp))
}
}
Inline::Striked(striked) => {
if let Some(temp) = striked.value.freeze_variables() {
striked.value = Box::new(Inline::TemplateVar(temp))
}
}
Inline::Underlined(under) => {
if let Some(temp) = under.value.freeze_variables() {
under.value = Box::new(Inline::TemplateVar(temp))
}
}
Inline::Italic(it) => {
if let Some(temp) = it.value.freeze_variables() {
it.value = Box::new(Inline::TemplateVar(temp))
}
}
Inline::Bold(bo) => {
if let Some(temp) = bo.value.freeze_variables() {
bo.value = Box::new(Inline::TemplateVar(temp))
}
}
_ => {}
}
None
}
}
impl Template {
pub fn render(&self, replacements: HashMap<String, Element>) -> Vec<Element> {
replacements.iter().for_each(|(k, r)| {
if let Some(v) = self.variables.get(k) {
v.lock().unwrap().set_value(r.clone())
}
});
let elements = self
.text
.iter()
.map(|e| {
let mut e = e.clone();
if let Some(template) = e.freeze_variables() {
Element::Inline(Box::new(Inline::TemplateVar(template)))
} else {
e
}
})
.collect();
self.variables
.iter()
.for_each(|(_, v)| v.lock().unwrap().reset());
elements
}
}
impl TemplateVariable {
pub fn set_value(&mut self, value: Element) {
self.value = Some(value)
}
pub fn reset(&mut self) {
self.value = None
}
}

@ -22,6 +22,7 @@ pub(crate) trait ParseInline {
fn parse_emoji(&mut self) -> ParseResult<Emoji>; fn parse_emoji(&mut self) -> ParseResult<Emoji>;
fn parse_colored(&mut self) -> ParseResult<Colored>; fn parse_colored(&mut self) -> ParseResult<Colored>;
fn parse_bibref(&mut self) -> ParseResult<Arc<Mutex<BibReference>>>; fn parse_bibref(&mut self) -> ParseResult<Arc<Mutex<BibReference>>>;
fn parse_template_variable(&mut self) -> ParseResult<Arc<Mutex<TemplateVariable>>>;
fn parse_plain(&mut self) -> ParseResult<PlainText>; fn parse_plain(&mut self) -> ParseResult<PlainText>;
} }
@ -40,6 +41,11 @@ impl ParseInline for Parser {
/// parses Inline, the formatting parts of a line (Text) /// parses Inline, the formatting parts of a line (Text)
fn parse_inline(&mut self) -> ParseResult<Inline> { fn parse_inline(&mut self) -> ParseResult<Inline> {
if self.parse_variables {
if let Ok(var) = self.parse_template_variable() {
return Ok(Inline::TemplateVar(var));
}
}
if self.check_special(&PIPE) || self.check_linebreak() { if self.check_special(&PIPE) || self.check_linebreak() {
Err(ParseError::new(self.index)) Err(ParseError::new(self.index))
} else if self.check_eof() { } else if self.check_eof() {
@ -249,6 +255,25 @@ impl ParseInline for Parser {
Ok(ref_entry) Ok(ref_entry)
} }
/// parses a template variable {prefix{name}suffix}
fn parse_template_variable(&mut self) -> ParseResult<Arc<Mutex<TemplateVariable>>> {
let start_index = self.index;
self.assert_special(&TEMP_VAR_OPEN, start_index)?;
self.skip_char();
let prefix = self.get_string_until_or_revert(&[TEMP_VAR_OPEN], &[LB], start_index)?;
self.skip_char();
let name = self.get_string_until_or_revert(&[TEMP_VAR_CLOSE], &[LB], start_index)?;
self.skip_char();
let suffix = self.get_string_until_or_revert(&[TEMP_VAR_CLOSE], &[LB], start_index)?;
self.skip_char();
Ok(Arc::new(Mutex::new(TemplateVariable {
value: None,
name,
prefix,
suffix,
})))
}
/// parses plain text as a string until it encounters an unescaped special inline char /// parses plain text as a string until it encounters an unescaped special inline char
fn parse_plain(&mut self) -> ParseResult<PlainText> { fn parse_plain(&mut self) -> ParseResult<PlainText> {
if self.check_linebreak() { if self.check_linebreak() {
@ -259,6 +284,7 @@ impl ParseInline for Parser {
while let Some(ch) = self.next_char() { while let Some(ch) = self.next_char() {
if self.check_special_group(&INLINE_SPECIAL_CHARS) if self.check_special_group(&INLINE_SPECIAL_CHARS)
|| self.check_special_group(&self.inline_break_at) || self.check_special_group(&self.inline_break_at)
|| (self.parse_variables && self.check_special(&TEMP_VAR_OPEN))
{ {
break; break;
} }

@ -25,10 +25,12 @@ pub struct Parser {
paths: Arc<Mutex<Vec<String>>>, paths: Arc<Mutex<Vec<String>>>,
wg: WaitGroup, wg: WaitGroup,
is_child: bool, is_child: bool,
pub(crate) block_break_at: Vec<char>,
pub(crate) inline_break_at: Vec<char>, pub(crate) inline_break_at: Vec<char>,
pub(crate) document: Document, pub(crate) document: Document,
pub(crate) previous_char: char, pub(crate) previous_char: char,
pub(crate) reader: Box<dyn BufRead>, pub(crate) reader: Box<dyn BufRead>,
pub(crate) parse_variables: bool,
} }
impl Parser { impl Parser {
@ -118,8 +120,10 @@ impl Parser {
is_child, is_child,
previous_char: ' ', previous_char: ' ',
inline_break_at: Vec::new(), inline_break_at: Vec::new(),
block_break_at: Vec::new(),
document: Document::new(!is_child), document: Document::new(!is_child),
reader, reader,
parse_variables: false,
} }
} }
@ -426,6 +430,8 @@ impl Parser {
self.seek_inline_whitespace(); self.seek_inline_whitespace();
if let Ok(ph) = self.parse_placeholder() { if let Ok(ph) = self.parse_placeholder() {
value = MetadataValue::Placeholder(ph); value = MetadataValue::Placeholder(ph);
} else if let Ok(template) = self.parse_template() {
value = MetadataValue::Template(template)
} else { } else {
let quoted_string = self.check_special_group(&QUOTES); let quoted_string = self.check_special_group(&QUOTES);
let parse_until = if quoted_string { let parse_until = if quoted_string {
@ -501,7 +507,9 @@ impl Parser {
while let Ok(token) = self.parse_line() { while let Ok(token) = self.parse_line() {
paragraph.add_element(token); paragraph.add_element(token);
let start_index = self.index; let start_index = self.index;
if self.check_special_sequence_group(&BLOCK_SPECIAL_CHARS) { if self.check_special_sequence_group(&BLOCK_SPECIAL_CHARS)
|| self.check_special_group(&self.block_break_at)
{
self.revert_to(start_index)?; self.revert_to(start_index)?;
break; break;
} }
@ -772,7 +780,7 @@ impl Parser {
let mut text = TextLine::new(); let mut text = TextLine::new();
while let Ok(subtext) = self.parse_inline() { while let Ok(subtext) = self.parse_inline() {
text.add_subtext(subtext); text.add_subtext(subtext);
if self.check_eof() { if self.check_eof() || self.check_special_group(&self.inline_break_at) {
break; break;
} }
} }
@ -787,4 +795,47 @@ impl Parser {
Err(ParseError::eof(self.index)) Err(ParseError::eof(self.index))
} }
} }
/// parses a template
fn parse_template(&mut self) -> ParseResult<Template> {
let start_index = self.index;
self.assert_special(&TEMPLATE, start_index)?;
self.skip_char();
if self.check_special(&TEMPLATE) {
return Err(self.revert_with_error(start_index));
}
let mut elements = Vec::new();
self.block_break_at.push(TEMPLATE);
self.inline_break_at.push(TEMPLATE);
self.parse_variables = true;
while let Ok(e) = self.parse_block() {
elements.push(Element::Block(Box::new(e)));
if self.check_special(&TEMPLATE) {
break;
}
}
self.parse_variables = false;
self.block_break_at.clear();
self.inline_break_at.clear();
self.assert_special(&TEMPLATE, start_index)?;
self.skip_char();
let vars: HashMap<String, Arc<Mutex<TemplateVariable>>> = elements
.iter()
.map(|e| e.get_template_variables())
.flatten()
.map(|e: Arc<Mutex<TemplateVariable>>| {
let name;
{
name = e.lock().unwrap().name.clone();
};
(name, e)
})
.collect();
Ok(Template {
text: elements,
variables: vars,
})
}
} }

@ -28,6 +28,9 @@ pub(crate) const UP: char = '^';
pub(crate) const COLON: char = ':'; pub(crate) const COLON: char = ':';
pub(crate) const PARAGRAPH: char = '§'; pub(crate) const PARAGRAPH: char = '§';
pub(crate) const SEMICOLON: char = ';'; pub(crate) const SEMICOLON: char = ';';
pub(crate) const R_BRACE: char = '{';
pub(crate) const L_BRACE: char = '}';
pub(crate) const PERCENT: char = '%';
// aliases // aliases
@ -57,6 +60,9 @@ pub(crate) const BIBREF_CLOSE: char = L_BRACKET;
pub(crate) const BIB_KEY_OPEN: char = R_BRACKET; pub(crate) const BIB_KEY_OPEN: char = R_BRACKET;
pub(crate) const BIB_KEY_CLOSE: char = L_BRACKET; pub(crate) const BIB_KEY_CLOSE: char = L_BRACKET;
pub(crate) const BIB_DATA_START: char = COLON; pub(crate) const BIB_DATA_START: char = COLON;
pub(crate) const TEMP_VAR_OPEN: char = R_BRACE;
pub(crate) const TEMP_VAR_CLOSE: char = L_BRACE;
pub(crate) const TEMPLATE: char = PERCENT;
pub(crate) const ITALIC: char = ASTERISK; pub(crate) const ITALIC: char = ASTERISK;
pub(crate) const MONOSPACE: char = BACKTICK; pub(crate) const MONOSPACE: char = BACKTICK;

Loading…
Cancel
Save