Added layout properties

This commit is contained in:
ef3d0c3e 2024-07-31 13:59:51 +02:00
parent 12e15321a4
commit 57756d8048
8 changed files with 203 additions and 31 deletions

View file

@ -325,7 +325,7 @@ impl CodeRule {
) )
.unwrap(), .unwrap(),
], ],
properties: PropertyParser::new(props), properties: PropertyParser{ properties: props },
} }
} }
} }

View file

@ -179,7 +179,7 @@ impl GraphRule {
r"\[graph\](?:\[((?:\\.|[^\[\]\\])*?)\])?(?:((?:\\.|[^\\\\])*?)\[/graph\])?", r"\[graph\](?:\[((?:\\.|[^\[\]\\])*?)\])?(?:((?:\\.|[^\\\\])*?)\[/graph\])?",
) )
.unwrap()], .unwrap()],
properties: PropertyParser::new(props), properties: PropertyParser{ properties: props },
} }
} }
} }

View file

@ -4,11 +4,13 @@ use crate::document::document::Document;
use crate::document::element::ElemKind; use crate::document::element::ElemKind;
use crate::document::element::Element; use crate::document::element::Element;
use crate::parser::parser::Parser; use crate::parser::parser::Parser;
use crate::parser::parser::ReportColors;
use crate::parser::rule::RegexRule; use crate::parser::rule::RegexRule;
use crate::parser::source::Source; use crate::parser::source::Source;
use crate::parser::source::Token; use crate::parser::source::Token;
use crate::parser::state::Scope; use crate::parser::state::Scope;
use crate::parser::state::State; use crate::parser::state::State;
use crate::parser::util::process_escaped;
use ariadne::Fmt; use ariadne::Fmt;
use ariadne::Label; use ariadne::Label;
use ariadne::Report; use ariadne::Report;
@ -17,7 +19,9 @@ use lazy_static::lazy_static;
use mlua::Function; use mlua::Function;
use mlua::Lua; use mlua::Lua;
use regex::Captures; use regex::Captures;
use regex::Match;
use regex::Regex; use regex::Regex;
use std::any::Any;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Range; use std::ops::Range;
@ -35,6 +39,9 @@ pub trait LayoutType: core::fmt::Debug {
/// Name of the layout /// Name of the layout
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
/// Parses layout properties
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String>;
/// Expected number of blocks /// Expected number of blocks
fn expects(&self) -> Range<usize>; fn expects(&self) -> Range<usize>;
@ -43,15 +50,21 @@ pub trait LayoutType: core::fmt::Debug {
&self, &self,
token: LayoutToken, token: LayoutToken,
id: usize, id: usize,
properties: &Option<Box<dyn Any>>,
compiler: &Compiler, compiler: &Compiler,
document: &dyn Document, document: &dyn Document,
) -> Result<String, String>; ) -> Result<String, String>;
} }
mod default_layouts { mod default_layouts {
use std::any::Any;
use crate::parser::util::Property;
use crate::parser::util::PropertyParser;
use super::*; use super::*;
#[derive(Debug)] #[derive(Debug, Default)]
pub struct Centered; pub struct Centered;
impl LayoutType for Centered { impl LayoutType for Centered {
@ -59,10 +72,18 @@ mod default_layouts {
fn expects(&self) -> Range<usize> { 1..1 } fn expects(&self) -> Range<usize> { 1..1 }
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String> {
if !properties.is_empty() {
return Err(format!("Layout {} excepts no properties", self.name()));
}
Ok(None)
}
fn compile( fn compile(
&self, &self,
token: LayoutToken, token: LayoutToken,
_id: usize, _id: usize,
_properties: &Option<Box<dyn Any>>,
compiler: &Compiler, compiler: &Compiler,
_document: &dyn Document, _document: &dyn Document,
) -> Result<String, String> { ) -> Result<String, String> {
@ -78,26 +99,83 @@ mod default_layouts {
} }
#[derive(Debug)] #[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 { impl LayoutType for Split {
fn name(&self) -> &'static str { "Split" } fn name(&self) -> &'static str { "Split" }
fn expects(&self) -> Range<usize> { 2..usize::MAX } fn expects(&self) -> Range<usize> { 2..usize::MAX }
fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, 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<String, ()> {
Ok(value.clone())
})
.map_err(|err| format!("Failed to parse style: {err:#?}"))
.map(|(_, value)| value)?;
Ok(Some(Box::new(style)))
}
fn compile( fn compile(
&self, &self,
token: LayoutToken, token: LayoutToken,
_id: usize, _id: usize,
properties: &Option<Box<dyn Any>>,
compiler: &Compiler, compiler: &Compiler,
_document: &dyn Document, _document: &dyn Document,
) -> Result<String, String> { ) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => match token { Target::HTML => {
LayoutToken::BEGIN => Ok(r#"<div class="split-container"><div class="split">"#.to_string()), let style = match properties
LayoutToken::NEXT => Ok(r#"</div><div class="split">"#.to_string()), .as_ref()
LayoutToken::END => Ok(r#"</div></div>"#.to_string()), .unwrap()
}, .downcast_ref::<String>()
.unwrap().as_str()
{
"" => "".to_string(),
str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str))
};
match token {
LayoutToken::BEGIN => Ok(format!(
r#"<div class="split-container"><div class="split"{style}>"#
)),
LayoutToken::NEXT => Ok(format!(r#"</div><div class="split"{style}>"#)),
LayoutToken::END => Ok(r#"</div></div>"#.to_string()),
}
}
_ => todo!(""), _ => todo!(""),
} }
} }
@ -110,6 +188,7 @@ struct Layout {
pub(self) layout: Rc<dyn LayoutType>, pub(self) layout: Rc<dyn LayoutType>,
pub(self) id: usize, pub(self) id: usize,
pub(self) token: LayoutToken, pub(self) token: LayoutToken,
pub(self) properties: Option<Box<dyn Any>>,
} }
impl Element for Layout { impl Element for Layout {
@ -118,7 +197,8 @@ impl Element for Layout {
fn element_name(&self) -> &'static str { "Layout" } fn element_name(&self) -> &'static str { "Layout" }
fn to_string(&self) -> String { format!("{self:#?}") } fn to_string(&self) -> String { format!("{self:#?}") }
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
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 { impl LayoutRule {
pub fn new() -> Self { pub fn new() -> Self {
let mut layouts: HashMap<String, Rc<dyn LayoutType>> = HashMap::new(); let mut layouts: HashMap<String, Rc<dyn LayoutType>> = 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)); 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)); layouts.insert(layout_split.name().to_string(), Rc::new(layout_split));
Self { Self {
re: [ re: [
Regex::new(r"(?:^|\n)#\+LAYOUT_BEGIN(.*)").unwrap(), Regex::new(r"(?:^|\n)#\+LAYOUT_BEGIN(?:\[((?:\\.|[^\\\\])*?)\])?(.*)").unwrap(),
Regex::new(r"(?:^|\n)#\+LAYOUT_NEXT(?:$|\n)").unwrap(), Regex::new(r"(?:^|\n)#\+LAYOUT_NEXT(?:\[((?:\\.|[^\\\\])*?)\])?(?:$|\n)").unwrap(),
Regex::new(r"(?:^|\n)#\+LAYOUT_END(?:$|\n)").unwrap(), Regex::new(r"(?:^|\n)#\+LAYOUT_END(?:\[((?:\\.|[^\\\\])*?)\])?(?:$|\n)").unwrap(),
], ],
layouts, layouts,
} }
} }
pub fn parse_properties<'a>(
colors: &ReportColors,
token: &Token,
layout_type: Rc<dyn LayoutType>,
properties: Option<Match>,
) -> Result<Option<Box<dyn Any>>, Report<'a, (Rc<dyn Source>, Range<usize>)>> {
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! { lazy_static! {
@ -235,7 +359,7 @@ impl RegexRule for LayoutRule {
if index == 0 if index == 0
// BEGIN_LAYOUT // BEGIN_LAYOUT
{ {
match matches.get(1) { match matches.get(2) {
None => { None => {
reports.push( reports.push(
Report::build(ReportKind::Error, token.source(), token.start()) Report::build(ReportKind::Error, token.source(), token.start())
@ -276,11 +400,11 @@ impl RegexRule for LayoutRule {
{ {
reports.push( reports.push(
Report::build(ReportKind::Error, token.source(), name.start()) Report::build(ReportKind::Error, token.source(), name.start())
.with_message("Empty Layout Name") .with_message("Invalid Layout Name")
.with_label( .with_label(
Label::new((token.source(), name.range())) Label::new((token.source(), name.range()))
.with_message(format!( .with_message(format!(
"Missing a space before layout `{}`", "Missing a space before layout name `{}`",
name.as_str().fg(parser.colors().highlight) name.as_str().fg(parser.colors().highlight)
)) ))
.with_color(parser.colors().error), .with_color(parser.colors().error),
@ -311,6 +435,20 @@ impl RegexRule for LayoutRule {
Some(layout_type) => layout_type, 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( parser.push(
document, document,
Box::new(Layout { Box::new(Layout {
@ -318,6 +456,7 @@ impl RegexRule for LayoutRule {
layout: layout_type.clone(), layout: layout_type.clone(),
id: 0, id: 0,
token: LayoutToken::BEGIN, token: LayoutToken::BEGIN,
properties,
}), }),
); );
@ -333,7 +472,7 @@ impl RegexRule for LayoutRule {
return reports; return reports;
} }
let (id, token_type, layout_type) = if index == 1 let (id, token_type, layout_type, properties) = if index == 1
// LAYOUT_NEXT // LAYOUT_NEXT
{ {
let mut state_borrow = state.borrow_mut(); let mut state_borrow = state.borrow_mut();
@ -376,8 +515,27 @@ impl RegexRule for LayoutRule {
return reports; 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.push(token.clone());
(tokens.len() - 1, LayoutToken::NEXT, layout_type.clone()) (
tokens.len() - 1,
LayoutToken::NEXT,
layout_type.clone(),
properties,
)
} else { } else {
// LAYOUT_END // LAYOUT_END
let mut state_borrow = state.borrow_mut(); let mut state_borrow = state.borrow_mut();
@ -420,10 +578,24 @@ impl RegexRule for LayoutRule {
return reports; 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 layout_type = layout_type.clone();
let id = tokens.len(); let id = tokens.len();
state.stack.pop(); state.stack.pop();
(id, LayoutToken::END, layout_type) (id, LayoutToken::END, layout_type, properties)
}; };
parser.push( parser.push(
@ -433,6 +605,7 @@ impl RegexRule for LayoutRule {
layout: layout_type, layout: layout_type,
id, id,
token: token_type, token: token_type,
properties,
}), }),
); );

View file

@ -258,7 +258,7 @@ impl MediaRule {
.multi_line(true) .multi_line(true)
.build() .build()
.unwrap()], .unwrap()],
properties: PropertyParser::new(props), properties: PropertyParser{ properties: props },
} }
} }

View file

@ -67,7 +67,7 @@ impl RawRule {
Regex::new(r"\{\?(?:\[((?:\\.|[^\[\]\\])*?)\])?(?:((?:\\.|[^\\\\])*?)(\?\}))?") Regex::new(r"\{\?(?:\[((?:\\.|[^\[\]\\])*?)\])?(?:((?:\\.|[^\\\\])*?)(\?\}))?")
.unwrap(), .unwrap(),
], ],
properties: PropertyParser::new(props), properties: PropertyParser{ properties: props },
} }
} }
} }

View file

@ -84,7 +84,7 @@ impl ReferenceRule {
); );
Self { Self {
re: [Regex::new(r"§\{(.*)\}(\[((?:\\.|[^\\\\])*?)\])?").unwrap()], re: [Regex::new(r"§\{(.*)\}(\[((?:\\.|[^\\\\])*?)\])?").unwrap()],
properties: PropertyParser::new(props), properties: PropertyParser{ properties: props },
} }
} }

View file

@ -253,7 +253,7 @@ impl TexRule {
.unwrap(), .unwrap(),
Regex::new(r"\$(?:\[((?:\\.|[^\\\\])*?)\])?(?:((?:\\.|[^\\\\])*?)\$)?").unwrap(), Regex::new(r"\$(?:\[((?:\\.|[^\\\\])*?)\])?(?:((?:\\.|[^\\\\])*?)\$)?").unwrap(),
], ],
properties: PropertyParser::new(props), properties: PropertyParser{ properties: props },
} }
} }

View file

@ -229,13 +229,12 @@ impl<'a> PropertyMap<'a> {
} }
} }
#[derive(Debug)]
pub struct PropertyParser { pub struct PropertyParser {
properties: HashMap<String, Property>, pub properties: HashMap<String, Property>,
} }
impl PropertyParser { impl PropertyParser {
pub fn new(properties: HashMap<String, Property>) -> Self { Self { properties } }
/// Attempts to build a default propertymap /// Attempts to build a default propertymap
/// ///
/// Returns an error if at least one [`Property`] is required and doesn't provide a default /// 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(), /// properties.insert("width".to_string(),
/// Property::new(true, "Width of the element in em".to_string(), None)); /// 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(); /// let pm = parser.parse("width=15").unwrap();
/// ///
/// assert_eq!(pm.get("width", |_, s| s.parse::<i32>()).unwrap().1, 15); /// assert_eq!(pm.get("width", |_, s| s.parse::<i32>()).unwrap().1, 15);
@ -491,7 +490,7 @@ mod tests {
Property::new(false, "Weight in %".to_string(), Some("0.42".to_string())), 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(); let pm = parser.parse("width=15,length=-10").unwrap();
// Ok // Ok