diff --git a/src/elements/code.rs b/src/elements/code.rs index 0ebb050..06e5c66 100644 --- a/src/elements/code.rs +++ b/src/elements/code.rs @@ -325,7 +325,7 @@ impl CodeRule { ) .unwrap(), ], - properties: PropertyParser::new(props), + properties: PropertyParser{ properties: props }, } } } diff --git a/src/elements/graphviz.rs b/src/elements/graphviz.rs index f2cbdc9..b2d3586 100644 --- a/src/elements/graphviz.rs +++ b/src/elements/graphviz.rs @@ -179,7 +179,7 @@ impl GraphRule { r"\[graph\](?:\[((?:\\.|[^\[\]\\])*?)\])?(?:((?:\\.|[^\\\\])*?)\[/graph\])?", ) .unwrap()], - properties: PropertyParser::new(props), + properties: PropertyParser{ properties: props }, } } } diff --git a/src/elements/layout.rs b/src/elements/layout.rs index abfaa57..d1a17d8 100644 --- a/src/elements/layout.rs +++ b/src/elements/layout.rs @@ -4,11 +4,13 @@ use crate::document::document::Document; use crate::document::element::ElemKind; use crate::document::element::Element; use crate::parser::parser::Parser; +use crate::parser::parser::ReportColors; use crate::parser::rule::RegexRule; use crate::parser::source::Source; use crate::parser::source::Token; use crate::parser::state::Scope; use crate::parser::state::State; +use crate::parser::util::process_escaped; use ariadne::Fmt; use ariadne::Label; use ariadne::Report; @@ -17,7 +19,9 @@ use lazy_static::lazy_static; use mlua::Function; use mlua::Lua; use regex::Captures; +use regex::Match; use regex::Regex; +use std::any::Any; use std::cell::RefCell; use std::collections::HashMap; use std::ops::Range; @@ -35,6 +39,9 @@ pub trait LayoutType: core::fmt::Debug { /// Name of the layout fn name(&self) -> &'static str; + /// Parses layout properties + fn parse_properties(&self, properties: &str) -> Result>, String>; + /// Expected number of blocks fn expects(&self) -> Range; @@ -43,15 +50,21 @@ pub trait LayoutType: core::fmt::Debug { &self, token: LayoutToken, id: usize, + properties: &Option>, compiler: &Compiler, document: &dyn Document, ) -> Result; } mod default_layouts { + use std::any::Any; + + use crate::parser::util::Property; + use crate::parser::util::PropertyParser; + use super::*; - #[derive(Debug)] + #[derive(Debug, Default)] pub struct Centered; impl LayoutType for Centered { @@ -59,10 +72,18 @@ mod default_layouts { fn expects(&self) -> Range { 1..1 } + fn parse_properties(&self, properties: &str) -> Result>, String> { + if !properties.is_empty() { + return Err(format!("Layout {} excepts no properties", self.name())); + } + Ok(None) + } + fn compile( &self, token: LayoutToken, _id: usize, + _properties: &Option>, compiler: &Compiler, _document: &dyn Document, ) -> Result { @@ -78,26 +99,83 @@ mod default_layouts { } #[derive(Debug)] - pub struct Split; + pub struct Split { + properties: PropertyParser, + } + + impl Default for Split { + fn default() -> Self { + let mut properties = HashMap::new(); + properties.insert( + "style".to_string(), + Property::new( + true, + "Additional style for the split".to_string(), + Some("".to_string()), + ), + ); + + Self { + properties: PropertyParser { properties }, + } + } + } impl LayoutType for Split { fn name(&self) -> &'static str { "Split" } - fn expects(&self) -> Range { 2..usize::MAX } + fn expects(&self) -> Range { 2..usize::MAX } + + fn parse_properties(&self, properties: &str) -> Result>, String> { + let props = if properties.is_empty() { + self.properties.default() + } else { + self.properties.parse(properties) + } + .map_err(|err| { + format!( + "Failed to parse properties for layout {}: {err}", + self.name() + ) + })?; + + let style = props + .get("style", |_, value| -> Result { + Ok(value.clone()) + }) + .map_err(|err| format!("Failed to parse style: {err:#?}")) + .map(|(_, value)| value)?; + + Ok(Some(Box::new(style))) + } fn compile( &self, token: LayoutToken, _id: usize, + properties: &Option>, compiler: &Compiler, _document: &dyn Document, ) -> Result { match compiler.target() { - Target::HTML => match token { - LayoutToken::BEGIN => Ok(r#"
"#.to_string()), - LayoutToken::NEXT => Ok(r#"
"#.to_string()), - LayoutToken::END => Ok(r#"
"#.to_string()), - }, + Target::HTML => { + let style = match properties + .as_ref() + .unwrap() + .downcast_ref::() + .unwrap().as_str() + { + "" => "".to_string(), + str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)) + }; + match token { + LayoutToken::BEGIN => Ok(format!( + r#"
"# + )), + LayoutToken::NEXT => Ok(format!(r#"
"#)), + LayoutToken::END => Ok(r#"
"#.to_string()), + } + } _ => todo!(""), } } @@ -110,6 +188,7 @@ struct Layout { pub(self) layout: Rc, pub(self) id: usize, pub(self) token: LayoutToken, + pub(self) properties: Option>, } impl Element for Layout { @@ -118,7 +197,8 @@ impl Element for Layout { fn element_name(&self) -> &'static str { "Layout" } fn to_string(&self) -> String { format!("{self:#?}") } fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result { - self.layout.compile(self.token, self.id, compiler, document) + self.layout + .compile(self.token, self.id, &self.properties, compiler, document) } } @@ -182,20 +262,64 @@ pub struct LayoutRule { impl LayoutRule { pub fn new() -> Self { let mut layouts: HashMap> = HashMap::new(); - let layout_centered = default_layouts::Centered {}; + + let layout_centered = default_layouts::Centered::default(); layouts.insert(layout_centered.name().to_string(), Rc::new(layout_centered)); - let layout_split = default_layouts::Split {}; + + let layout_split = default_layouts::Split::default(); layouts.insert(layout_split.name().to_string(), Rc::new(layout_split)); Self { re: [ - Regex::new(r"(?:^|\n)#\+LAYOUT_BEGIN(.*)").unwrap(), - Regex::new(r"(?:^|\n)#\+LAYOUT_NEXT(?:$|\n)").unwrap(), - Regex::new(r"(?:^|\n)#\+LAYOUT_END(?:$|\n)").unwrap(), + Regex::new(r"(?:^|\n)#\+LAYOUT_BEGIN(?:\[((?:\\.|[^\\\\])*?)\])?(.*)").unwrap(), + Regex::new(r"(?:^|\n)#\+LAYOUT_NEXT(?:\[((?:\\.|[^\\\\])*?)\])?(?:$|\n)").unwrap(), + Regex::new(r"(?:^|\n)#\+LAYOUT_END(?:\[((?:\\.|[^\\\\])*?)\])?(?:$|\n)").unwrap(), ], layouts, } } + + pub fn parse_properties<'a>( + colors: &ReportColors, + token: &Token, + layout_type: Rc, + properties: Option, + ) -> Result>, Report<'a, (Rc, Range)>> { + match properties { + None => match layout_type.parse_properties("") { + Ok(props) => Ok(props), + Err(err) => Err( + Report::build(ReportKind::Error, token.source(), token.start()) + .with_message("Unable to parse layout properties") + .with_label( + Label::new((token.source(), token.range.clone())) + .with_message(err) + .with_color(colors.error), + ) + .finish(), + ), + }, + Some(props) => { + let trimmed = props.as_str().trim_start().trim_end(); + let content = process_escaped('\\', "]", trimmed); + match layout_type.parse_properties(content.as_str()) { + Ok(props) => Ok(props), + Err(err) => { + Err( + Report::build(ReportKind::Error, token.source(), props.start()) + .with_message("Unable to parse layout properties") + .with_label( + Label::new((token.source(), props.range())) + .with_message(err) + .with_color(colors.error), + ) + .finish(), + ) + } + } + } + } + } } lazy_static! { @@ -235,7 +359,7 @@ impl RegexRule for LayoutRule { if index == 0 // BEGIN_LAYOUT { - match matches.get(1) { + match matches.get(2) { None => { reports.push( Report::build(ReportKind::Error, token.source(), token.start()) @@ -276,11 +400,11 @@ impl RegexRule for LayoutRule { { reports.push( Report::build(ReportKind::Error, token.source(), name.start()) - .with_message("Empty Layout Name") + .with_message("Invalid Layout Name") .with_label( Label::new((token.source(), name.range())) .with_message(format!( - "Missing a space before layout `{}`", + "Missing a space before layout name `{}`", name.as_str().fg(parser.colors().highlight) )) .with_color(parser.colors().error), @@ -311,6 +435,20 @@ impl RegexRule for LayoutRule { Some(layout_type) => layout_type, }; + // Parse properties + let properties = match LayoutRule::parse_properties( + parser.colors(), + &token, + layout_type.clone(), + matches.get(1), + ) { + Ok(props) => props, + Err(rep) => { + reports.push(rep); + return reports; + } + }; + parser.push( document, Box::new(Layout { @@ -318,6 +456,7 @@ impl RegexRule for LayoutRule { layout: layout_type.clone(), id: 0, token: LayoutToken::BEGIN, + properties, }), ); @@ -333,7 +472,7 @@ impl RegexRule for LayoutRule { return reports; } - let (id, token_type, layout_type) = if index == 1 + let (id, token_type, layout_type, properties) = if index == 1 // LAYOUT_NEXT { let mut state_borrow = state.borrow_mut(); @@ -376,8 +515,27 @@ impl RegexRule for LayoutRule { return reports; } + // Parse properties + let properties = match LayoutRule::parse_properties( + parser.colors(), + &token, + layout_type.clone(), + matches.get(1), + ) { + Ok(props) => props, + Err(rep) => { + reports.push(rep); + return reports; + } + }; + tokens.push(token.clone()); - (tokens.len() - 1, LayoutToken::NEXT, layout_type.clone()) + ( + tokens.len() - 1, + LayoutToken::NEXT, + layout_type.clone(), + properties, + ) } else { // LAYOUT_END let mut state_borrow = state.borrow_mut(); @@ -420,10 +578,24 @@ impl RegexRule for LayoutRule { return reports; } + // Parse properties + let properties = match LayoutRule::parse_properties( + parser.colors(), + &token, + layout_type.clone(), + matches.get(1), + ) { + Ok(props) => props, + Err(rep) => { + reports.push(rep); + return reports; + } + }; + let layout_type = layout_type.clone(); let id = tokens.len(); state.stack.pop(); - (id, LayoutToken::END, layout_type) + (id, LayoutToken::END, layout_type, properties) }; parser.push( @@ -433,6 +605,7 @@ impl RegexRule for LayoutRule { layout: layout_type, id, token: token_type, + properties, }), ); diff --git a/src/elements/media.rs b/src/elements/media.rs index f7e4b23..ee03396 100644 --- a/src/elements/media.rs +++ b/src/elements/media.rs @@ -258,7 +258,7 @@ impl MediaRule { .multi_line(true) .build() .unwrap()], - properties: PropertyParser::new(props), + properties: PropertyParser{ properties: props }, } } diff --git a/src/elements/raw.rs b/src/elements/raw.rs index 32ba94f..d6f95d0 100644 --- a/src/elements/raw.rs +++ b/src/elements/raw.rs @@ -67,7 +67,7 @@ impl RawRule { Regex::new(r"\{\?(?:\[((?:\\.|[^\[\]\\])*?)\])?(?:((?:\\.|[^\\\\])*?)(\?\}))?") .unwrap(), ], - properties: PropertyParser::new(props), + properties: PropertyParser{ properties: props }, } } } diff --git a/src/elements/reference.rs b/src/elements/reference.rs index e467fc9..e5cf754 100644 --- a/src/elements/reference.rs +++ b/src/elements/reference.rs @@ -84,7 +84,7 @@ impl ReferenceRule { ); Self { re: [Regex::new(r"ยง\{(.*)\}(\[((?:\\.|[^\\\\])*?)\])?").unwrap()], - properties: PropertyParser::new(props), + properties: PropertyParser{ properties: props }, } } diff --git a/src/elements/tex.rs b/src/elements/tex.rs index e2a7708..58f58ae 100644 --- a/src/elements/tex.rs +++ b/src/elements/tex.rs @@ -253,7 +253,7 @@ impl TexRule { .unwrap(), Regex::new(r"\$(?:\[((?:\\.|[^\\\\])*?)\])?(?:((?:\\.|[^\\\\])*?)\$)?").unwrap(), ], - properties: PropertyParser::new(props), + properties: PropertyParser{ properties: props }, } } diff --git a/src/parser/util.rs b/src/parser/util.rs index b89bf70..845013b 100644 --- a/src/parser/util.rs +++ b/src/parser/util.rs @@ -229,13 +229,12 @@ impl<'a> PropertyMap<'a> { } } +#[derive(Debug)] pub struct PropertyParser { - properties: HashMap, + pub properties: HashMap, } impl PropertyParser { - pub fn new(properties: HashMap) -> Self { Self { properties } } - /// Attempts to build a default propertymap /// /// Returns an error if at least one [`Property`] is required and doesn't provide a default @@ -271,7 +270,7 @@ impl PropertyParser { /// properties.insert("width".to_string(), /// Property::new(true, "Width of the element in em".to_string(), None)); /// - /// let parser = PropertyParser::new(properties); + /// let parser = PropertyParser { properties }; /// let pm = parser.parse("width=15").unwrap(); /// /// assert_eq!(pm.get("width", |_, s| s.parse::()).unwrap().1, 15); @@ -491,7 +490,7 @@ mod tests { Property::new(false, "Weight in %".to_string(), Some("0.42".to_string())), ); - let parser = PropertyParser::new(properties); + let parser = PropertyParser { properties }; let pm = parser.parse("width=15,length=-10").unwrap(); // Ok