Add metadata syntax with formatting

pull/1/head
trivernis 4 years ago
parent e1f1140aeb
commit 0d6eb1d1a7

2
Cargo.lock generated

@ -358,7 +358,7 @@ dependencies = [
[[package]]
name = "snekdown"
version = "0.4.0"
version = "0.5.0"
dependencies = [
"chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",

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

@ -78,3 +78,33 @@ Header with rows
|--------|--------|-------
| row | row | row
```
### Metadata
Additional metadata can be provided for some elements.
```md
String value
[key = value]
String value
[key = "String value"]
Integer value
[key = 123]
Float value
[key = 1.23]
Boolean
[key]
Boolean
[key = false]
Placeholder
[key = [[placeholder]]]
Formatting
[author = "The Great snek" date = [[date]] time = [[time]] display = "author - date at time"]
```

@ -71,6 +71,18 @@ impl ToHtml for Block {
}
}
impl ToHtml for MetadataValue {
fn to_html(&self) -> String {
match self {
MetadataValue::String(string) => encode_minimal(string),
MetadataValue::Integer(num) => format!("{}", num),
MetadataValue::Placeholder(ph) => ph.lock().unwrap().to_html(),
MetadataValue::Bool(b) => format!("{}", b),
MetadataValue::Float(f) => format!("{}", f),
}
}
}
impl ToHtml for Document {
fn to_html(&self) -> String {
let inner = self
@ -251,7 +263,7 @@ impl ToHtml for Quote {
if let Some(meta) = self.metadata.clone() {
format!(
"<div class='quote'><blockquote>{}</blockquote><span class='metadata'>{}</span></div>",
text, encode_minimal(meta.data.as_str())
text, meta.to_html()
)
} else {
format!("<div class='quote'><blockquote>{}</blockquote></div>", text)
@ -368,3 +380,20 @@ impl ToHtml for Anchor {
)
}
}
impl ToHtml for InlineMetadata {
fn to_html(&self) -> String {
if let Some(MetadataValue::String(format)) = self.data.get("display") {
let mut format = format.clone();
self.data
.iter()
.for_each(|(k, v)| format = format.replace(k, v.to_html().as_str()));
format
} else {
self.data.iter().fold("".to_string(), |s, (k, v)| {
format!("{} {}={},", s, k, v.to_html())
})
}
}
}

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
pub const SECTION: &str = "section";
@ -23,6 +24,15 @@ macro_rules! test_block {
};
}
#[derive(Clone, Debug)]
pub enum MetadataValue {
String(String),
Integer(i64),
Float(f64),
Bool(bool),
Placeholder(Arc<Mutex<Placeholder>>),
}
#[derive(Clone, Debug)]
pub enum Element {
Block(Box<Block>),
@ -130,7 +140,7 @@ pub struct ImportAnchor {
#[derive(Clone, Debug)]
pub struct InlineMetadata {
pub(crate) data: String,
pub(crate) data: HashMap<String, MetadataValue>,
}
#[derive(Clone, Debug)]

@ -2,6 +2,7 @@ use super::elements::*;
use super::tokens::*;
use crate::parsing::placeholders::ProcessPlaceholders;
use crossbeam_utils::sync::WaitGroup;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::fmt::{Display, Formatter};
@ -532,21 +533,76 @@ impl Parser {
// if no meta open tag is present, the parsing has failed
return Err(ParseError::new(self.index));
}
let mut text = String::new();
while let Some(character) = self.next_char() {
let mut values = HashMap::new();
let _ = self.next_char();
while let Ok((key, value)) = self.parse_metadata_pair() {
values.insert(key, value);
if self.check_special(&META_CLOSE) || self.check_linebreak() {
// abort the parsing of the inner content when encountering a closing tag or linebreak
break;
}
text.push(character);
}
if self.check_linebreak() || text.len() == 0 {
if self.check_linebreak() || values.len() == 0 {
// if there was a linebreak (the metadata wasn't closed) or there is no inner data
// return an error
return Err(self.revert_with_error(start_index));
}
Ok(InlineMetadata { data: text })
Ok(InlineMetadata { data: values })
}
fn parse_metadata_pair(&mut self) -> Result<(String, MetadataValue), ParseError> {
self.seek_inline_whitespace();
let mut name = String::new();
name.push(self.current_char);
while let Some(char) = self.next_char() {
if self.check_special_group(&[META_CLOSE, EQ, SPACE, LB]) {
break;
}
name.push(char);
}
self.seek_inline_whitespace();
let mut value = MetadataValue::Bool(true);
if self.check_special(&EQ) {
let _ = self.next_char();
self.seek_inline_whitespace();
if let Ok(ph) = self.parse_placeholder() {
value = MetadataValue::Placeholder(ph);
} else {
let quoted_string = self.check_special_group(&QUOTES);
let mut raw_value = String::new();
if !quoted_string {
raw_value.push(self.current_char);
}
while let Some(char) = self.next_char() {
if self.check_special_group(&[META_CLOSE, LB])
|| (quoted_string && self.check_special_group(&QUOTES))
|| (!quoted_string && self.check_special(&SPACE))
{
break;
}
raw_value.push(char);
}
if self.check_special_group(&QUOTES) {
let _ = self.next_char();
}
value = if quoted_string {
MetadataValue::String(raw_value)
} else if raw_value.to_lowercase().as_str() == "true" {
MetadataValue::Bool(true)
} else if raw_value.to_lowercase().as_str() == "false" {
MetadataValue::Bool(false)
} else if let Ok(num) = raw_value.parse::<i64>() {
MetadataValue::Integer(num)
} else if let Ok(num) = raw_value.parse::<f64>() {
MetadataValue::Float(num)
} else {
MetadataValue::String(raw_value)
}
}
}
Ok((name, value))
}
/// parses an import and starts a new task to parse the document of the import

@ -20,6 +20,9 @@ pub(crate) const GT: char = '>';
pub(crate) const LT: char = '<';
pub(crate) const BANG: char = '!';
pub(crate) const SPACE: char = ' ';
pub(crate) const EQ: char = '=';
pub(crate) const DOUBLE_QUOTE: char = '"';
pub(crate) const SINGLE_QUOTE: char = '\'';
// aliases
@ -40,6 +43,8 @@ pub(crate) const PHOLDER_CLOSE: char = L_BRACKET;
// groups
pub(crate) const QUOTES: [char; 2] = [SINGLE_QUOTE, DOUBLE_QUOTE];
pub(crate) const BLOCK_SPECIAL_CHARS: [char; 7] = [
HASH,
MINUS,

Loading…
Cancel
Save