From a00db70bf64d7dc2d29b4ae1452247b45a2147d9 Mon Sep 17 00:00:00 2001
From: ef3d0c3e <ef3d0c3e@pundalik.org>
Date: Wed, 31 Jul 2024 17:10:26 +0200
Subject: [PATCH] Fix layout regex & add prop parser for centered

---
 src/elements/layout.rs | 171 ++++++++++++++++++++++++++---------------
 1 file changed, 111 insertions(+), 60 deletions(-)

diff --git a/src/elements/layout.rs b/src/elements/layout.rs
index d1a17d8..9c481ec 100644
--- a/src/elements/layout.rs
+++ b/src/elements/layout.rs
@@ -21,6 +21,7 @@ use mlua::Lua;
 use regex::Captures;
 use regex::Match;
 use regex::Regex;
+use regex::RegexBuilder;
 use std::any::Any;
 use std::cell::RefCell;
 use std::collections::HashMap;
@@ -64,46 +65,10 @@ mod default_layouts {
 
 	use super::*;
 
-	#[derive(Debug, Default)]
-	pub struct Centered;
-
-	impl LayoutType for Centered {
-		fn name(&self) -> &'static str { "Centered" }
-
-		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(
-			&self,
-			token: LayoutToken,
-			_id: usize,
-			_properties: &Option<Box<dyn Any>>,
-			compiler: &Compiler,
-			_document: &dyn Document,
-		) -> Result<String, String> {
-			match compiler.target() {
-				Target::HTML => match token {
-					LayoutToken::BEGIN => Ok(r#"<div class="centered">"#.to_string()),
-					LayoutToken::NEXT => panic!(),
-					LayoutToken::END => Ok(r#"</div>"#.to_string()),
-				},
-				_ => todo!(""),
-			}
-		}
-	}
-
 	#[derive(Debug)]
-	pub struct Split {
-		properties: PropertyParser,
-	}
+	pub struct Centered(PropertyParser);
 
-	impl Default for Split {
+	impl Default for Centered {
 		fn default() -> Self {
 			let mut properties = HashMap::new();
 			properties.insert(
@@ -115,22 +80,20 @@ mod default_layouts {
 				),
 			);
 
-			Self {
-				properties: PropertyParser { properties },
-			}
+			Self(PropertyParser { properties })
 		}
 	}
 
-	impl LayoutType for Split {
-		fn name(&self) -> &'static str { "Split" }
+	impl LayoutType for Centered {
+		fn name(&self) -> &'static str { "Centered" }
 
-		fn expects(&self) -> Range<usize> { 2..usize::MAX }
+		fn expects(&self) -> Range<usize> { 1..1 }
 
 		fn parse_properties(&self, properties: &str) -> Result<Option<Box<dyn Any>>, String> {
 			let props = if properties.is_empty() {
-				self.properties.default()
+				self.0.default()
 			} else {
-				self.properties.parse(properties)
+				self.0.parse(properties)
 			}
 			.map_err(|err| {
 				format!(
@@ -163,11 +126,90 @@ mod default_layouts {
 						.as_ref()
 						.unwrap()
 						.downcast_ref::<String>()
-						.unwrap().as_str()
-						{
-							"" => "".to_string(),
-							str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str))
-						};
+						.unwrap()
+						.as_str()
+					{
+						"" => "".to_string(),
+						str => format!(r#" style={}"#, Compiler::sanitize(compiler.target(), str)),
+					};
+					match token {
+						LayoutToken::BEGIN => Ok(format!(r#"<div class="centered"{style}>"#)),
+						LayoutToken::NEXT => panic!(),
+						LayoutToken::END => Ok(r#"</div>"#.to_string()),
+					}
+				}
+				_ => todo!(""),
+			}
+		}
+	}
+
+	#[derive(Debug)]
+	pub struct Split(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(PropertyParser { properties })
+		}
+	}
+
+	impl LayoutType for Split {
+		fn name(&self) -> &'static str { "Split" }
+
+		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.0.default()
+			} else {
+				self.0.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(
+			&self,
+			token: LayoutToken,
+			_id: usize,
+			properties: &Option<Box<dyn Any>>,
+			compiler: &Compiler,
+			_document: &dyn Document,
+		) -> Result<String, String> {
+			match compiler.target() {
+				Target::HTML => {
+					let style = match properties
+						.as_ref()
+						.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}>"#
@@ -225,12 +267,6 @@ impl State for LayoutState {
 			reports.push(
 				Report::build(ReportKind::Error, start.source(), start.start())
 					.with_message("Unterminated Layout")
-					//.with_label(
-					//	Label::new((document.source(), active_range.clone()))
-					//	.with_order(0)
-					//	.with_message(format!("Style {} is not terminated before the end of paragraph",
-					//	name.fg(parser.colors().info)))
-					//	.with_color(parser.colors().error))
 					.with_label(
 						Label::new((start.source(), start.range.start + 1..start.range.end))
 							.with_order(1)
@@ -271,9 +307,24 @@ impl LayoutRule {
 
 		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(),
+				RegexBuilder::new(
+					r"(?:^|\n)(?:[^\S\n]*)#\+LAYOUT_BEGIN(?:\[((?:\\.|[^\\\\])*?)\])?(.*)",
+				)
+				.multi_line(true)
+				.build()
+				.unwrap(),
+				RegexBuilder::new(
+					r"(?:^|\n)(?:[^\S\n]*)#\+LAYOUT_NEXT(?:\[((?:\\.|[^\\\\])*?)\])?$",
+				)
+				.multi_line(true)
+				.build()
+				.unwrap(),
+				RegexBuilder::new(
+					r"(?:^|\n)(?:[^\S\n]*)#\+LAYOUT_END(?:\[((?:\\.|[^\\\\])*?)\])?$",
+				)
+				.multi_line(true)
+				.build()
+				.unwrap(),
 			],
 			layouts,
 		}