From 48d2064d0c8ccb012a0cb6bf9fe560701a2f99ef Mon Sep 17 00:00:00 2001 From: ef3d0c3e Date: Sat, 3 Aug 2024 09:33:21 +0200 Subject: [PATCH] Added element styling --- src/document/element.rs | 10 ++- src/document/mod.rs | 1 + src/document/style.rs | 66 +++++++++++++++++++ src/elements/elemstyle.rs | 134 ++++++++++++++++++++++++++++++++++++++ src/elements/mod.rs | 1 + src/elements/registrar.rs | 2 + src/elements/section.rs | 132 +++++++++++++++++++++++++++++++------ src/elements/variable.rs | 6 +- src/parser/langparser.rs | 21 ++++++ src/parser/parser.rs | 3 +- src/parser/rule.rs | 12 +++- 11 files changed, 359 insertions(+), 29 deletions(-) create mode 100644 src/document/style.rs create mode 100644 src/elements/elemstyle.rs diff --git a/src/document/element.rs b/src/document/element.rs index 24eec1c..59ac9a6 100644 --- a/src/document/element.rs +++ b/src/document/element.rs @@ -3,6 +3,7 @@ use std::str::FromStr; use crate::compiler::compiler::Compiler; use crate::elements::reference::Reference; use crate::parser::source::Token; +use crate::parser::util::PropertyParser; use downcast_rs::impl_downcast; use downcast_rs::Downcast; @@ -62,10 +63,15 @@ pub trait ReferenceableElement: Element { fn refcount_key(&self) -> &'static str; /// Creates the reference element - fn compile_reference(&self, compiler: &Compiler, document: &dyn Document, reference: &Reference, refid: usize) -> Result; + fn compile_reference( + &self, + compiler: &Compiler, + document: &dyn Document, + reference: &Reference, + refid: usize, + ) -> Result; } - pub trait ContainerElement: Element { /// Gets the contained elements fn contained(&self) -> &Vec>; diff --git a/src/document/mod.rs b/src/document/mod.rs index bff3340..8fb316f 100644 --- a/src/document/mod.rs +++ b/src/document/mod.rs @@ -3,3 +3,4 @@ pub mod references; pub mod langdocument; pub mod element; pub mod variable; +pub mod style; diff --git a/src/document/style.rs b/src/document/style.rs new file mode 100644 index 0000000..fcf8334 --- /dev/null +++ b/src/document/style.rs @@ -0,0 +1,66 @@ +use std::cell::Ref; +use std::cell::RefMut; +use std::collections::HashMap; +use std::rc::Rc; + +use downcast_rs::impl_downcast; +use downcast_rs::Downcast; + +/// Styling for an element +pub trait ElementStyle: Downcast + core::fmt::Debug { + /// The style key + fn key(&self) -> &'static str; + + /// Attempts to create a new style from a [`json`] string + /// + /// # Errors + /// + /// Will fail if deserialization fails + fn from_json(&self, json: &str) -> Result, String>; + + /// Serializes sytle into json string + fn to_json(&self) -> String; +} +impl_downcast!(ElementStyle); + +pub trait StyleHolder { + /// gets a reference to all defined styles + fn styles(&self) -> Ref<'_, HashMap>>; + + /// gets a (mutable) reference to all defined styles + fn styles_mut(&self) -> RefMut<'_, HashMap>>; + + /// Checks if a given style key is registered + fn is_registered(&self, style_key: &str) -> bool { + self.styles().contains_key(style_key) + } + + /// Gets the current active style for an element + /// NOTE: Will panic if a style is not defined for a given element + /// If you need to process user input, use [`is_registered`] + fn current_style(&self, style_key: &str) -> Rc { + self.styles().get(style_key).map(|rc| rc.clone()).unwrap() + } + + /// Sets the [`style`] + fn set_style(&self, style: Rc) { + self.styles_mut().insert(style.key().to_string(), style); + } +} + +#[macro_export] +macro_rules! impl_elementstyle { + ($t:ty, $key:expr) => { + impl ElementStyle for $t { + fn key(&self) -> &'static str { $key } + + fn from_json(&self, json: &str) -> Result, String> { + serde_json::from_str::<$t>(json) + .map_err(|e| e.to_string()) + .map(|obj| std::rc::Rc::new(obj) as std::rc::Rc) + } + + fn to_json(&self) -> String { serde_json::to_string(self).unwrap() } + } + }; +} diff --git a/src/elements/elemstyle.rs b/src/elements/elemstyle.rs new file mode 100644 index 0000000..3277332 --- /dev/null +++ b/src/elements/elemstyle.rs @@ -0,0 +1,134 @@ +use std::ops::Range; +use std::rc::Rc; + +use ariadne::{Fmt, Label, Report, ReportKind}; +use regex::{Captures, Regex}; + +use crate::document::document::Document; +use crate::document::{self}; +use crate::parser::parser::Parser; +use crate::parser::rule::RegexRule; +use crate::parser::source::Source; +use crate::parser::source::Token; + +use super::variable::VariableRule; + +pub struct ElemStyleRule { + re: [Regex; 1], +} + +impl ElemStyleRule { + pub fn new() -> Self { + Self { + re: [Regex::new(r"(?:^|\n)@@(.*?)=((?:\\\n|.)*)").unwrap()], + } + } +} + +impl RegexRule for ElemStyleRule { + fn name(&self) -> &'static str { "Element Style" } + + fn regexes(&self) -> &[regex::Regex] { &self.re } + + fn on_regex_match<'a>( + &self, + _: usize, + parser: &dyn Parser, + _document: &'a dyn Document, + token: Token, + matches: Captures, + ) -> Vec, Range)>> { + let mut reports = vec![]; + + let style = if let Some(key) = matches.get(1) + { + let trimmed = key.as_str().trim_start().trim_end(); + + // Check if empty + if trimmed.is_empty() + { + reports.push( + Report::build(ReportKind::Error, token.source(), key.start()) + .with_message("Empty Style Key") + .with_label( + Label::new((token.source(), key.range())) + .with_message(format!( + "Expected a non-empty style key", + )) + .with_color(parser.colors().error), + ) + .finish()); + return reports; + } + + // Check if key exists + if !parser.is_registered(trimmed) + { + reports.push( + Report::build(ReportKind::Error, token.source(), key.start()) + .with_message("Unknown Style Key") + .with_label( + Label::new((token.source(), key.range())) + .with_message(format!( + "Could not find a style with key: {}", + trimmed.fg(parser.colors().info) + )) + .with_color(parser.colors().error), + ) + .finish()); + + return reports; + } + + parser.current_style(trimmed) + } else { panic!("Unknown error") }; + + // Get value + let new_style = if let Some(value) = matches.get(2) { + let value_str = match VariableRule::validate_value(value.as_str()) { + Err(err) => { + reports.push( + Report::build(ReportKind::Error, token.source(), value.start()) + .with_message("Invalid Style Value") + .with_label( + Label::new((token.source(), value.range())) + .with_message(format!( + "Value `{}` is not allowed: {err}", + value.as_str().fg(parser.colors().highlight) + )) + .with_color(parser.colors().error), + ) + .finish()); + return reports; + } + Ok(value) => value, + }; + + // Attempt to serialize + match style.from_json(value_str.as_str()) + { + Err(err) => { + reports.push( + Report::build(ReportKind::Error, token.source(), value.start()) + .with_message("Invalid Style Value") + .with_label( + Label::new((token.source(), value.range())) + .with_message(format!( + "Failed to serialize `{}` into style with key `{}`: {err}", + value_str.fg(parser.colors().highlight), + style.key().fg(parser.colors().info) + )) + .with_color(parser.colors().error), + ) + .finish()); + return reports; + }, + Ok(style) => style, + } + } else { panic!("Unknown error") }; + + parser.set_style(new_style); + + reports + } +} diff --git a/src/elements/mod.rs b/src/elements/mod.rs index 9128fc6..066b14c 100644 --- a/src/elements/mod.rs +++ b/src/elements/mod.rs @@ -16,3 +16,4 @@ pub mod style; pub mod tex; pub mod text; pub mod variable; +pub mod elemstyle; diff --git a/src/elements/registrar.rs b/src/elements/registrar.rs index 5b08177..13ed156 100644 --- a/src/elements/registrar.rs +++ b/src/elements/registrar.rs @@ -2,6 +2,7 @@ use crate::parser::parser::Parser; use super::code::CodeRule; use super::comment::CommentRule; +use super::elemstyle::ElemStyleRule; use super::graphviz::GraphRule; use super::import::ImportRule; use super::layout::LayoutRule; @@ -24,6 +25,7 @@ pub fn register(parser: &mut P) { parser.add_rule(Box::new(ParagraphRule::new()), None).unwrap(); parser.add_rule(Box::new(ImportRule::new()), None).unwrap(); parser.add_rule(Box::new(ScriptRule::new()), None).unwrap(); + parser.add_rule(Box::new(ElemStyleRule::new()), None).unwrap(); parser.add_rule(Box::new(VariableRule::new()), None).unwrap(); parser.add_rule(Box::new(VariableSubstitutionRule::new()), None).unwrap(); parser.add_rule(Box::new(RawRule::new()), None).unwrap(); diff --git a/src/elements/section.rs b/src/elements/section.rs index 6d2be6b..ae9e77c 100644 --- a/src/elements/section.rs +++ b/src/elements/section.rs @@ -17,7 +17,8 @@ use mlua::Error::BadArgument; use mlua::Function; use mlua::Lua; use regex::Regex; -use section_kind::NO_NUMBER; +use section_style::SectionLinkPos; +use section_style::SectionStyle; use std::ops::Range; use std::rc::Rc; use std::sync::Arc; @@ -25,42 +26,78 @@ use std::sync::Arc; #[derive(Debug)] pub struct Section { pub(self) location: Token, - pub(self) title: String, // Section title - pub(self) depth: usize, // Section depth - pub(self) kind: u8, // Section kind, e.g numbered, unnumbred, ... - pub(self) reference: Option, // Section reference name + /// Title of the section + pub(self) title: String, + /// Depth i.e number of '#' + pub(self) depth: usize, + /// [`section_kind`] + pub(self) kind: u8, + /// Section reference name + pub(self) reference: Option, + /// Style of the section + pub(self) style: Rc, } impl Element for Section { fn location(&self) -> &Token { &self.location } fn kind(&self) -> ElemKind { ElemKind::Block } fn element_name(&self) -> &'static str { "Section" } - fn as_referenceable(&self) -> Option<&dyn ReferenceableElement> { Some(self) } fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result { match compiler.target() { Target::HTML => { - let mut number = String::new(); - - if (self.kind & NO_NUMBER) != NO_NUMBER { + // Section numbering + let number = if (self.kind & section_kind::NO_NUMBER) == section_kind::NO_NUMBER { let numbering = compiler.section_counter(self.depth); - number = numbering - .iter() - .map(|n| n.to_string()) - .collect::>() - .join("."); - number += " "; + let number = " ".to_string() + + numbering + .iter() + .map(|n| n.to_string()) + .collect::>() + .join(".") + .as_str(); + number + } else { + String::new() + }; + + if self.style.link_pos == SectionLinkPos::None { + return Ok(format!( + r#"{number}{2}"#, + self.depth, + Compiler::refname(compiler.target(), self.title.as_str()), + Compiler::sanitize(compiler.target(), self.title.as_str()) + )); } - Ok(format!( - r#"{number}{2}"#, - self.depth, - Compiler::refname(compiler.target(), self.title.as_str()), - Compiler::sanitize(compiler.target(), self.title.as_str()) - )) + let refname = Compiler::refname(compiler.target(), self.title.as_str()); + let link = format!( + "{}", + Compiler::sanitize(compiler.target(), self.style.link.as_str()) + ); + + if self.style.link_pos == SectionLinkPos::After { + Ok(format!( + r#"{number}{2}{link}"#, + self.depth, + Compiler::refname(compiler.target(), self.title.as_str()), + Compiler::sanitize(compiler.target(), self.title.as_str()) + )) + } else + // Before + { + Ok(format!( + r#"{link}{number}{2}"#, + self.depth, + Compiler::refname(compiler.target(), self.title.as_str()), + Compiler::sanitize(compiler.target(), self.title.as_str()) + )) + } } Target::LATEX => Err("Unimplemented compiler".to_string()), } } + + fn as_referenceable(&self) -> Option<&dyn ReferenceableElement> { Some(self) } } impl ReferenceableElement for Section { @@ -252,6 +289,12 @@ impl RegexRule for SectionRule { _ => panic!("Empty section name"), }; + // Get style + let style = parser + .current_style(section_style::STYLE_KEY) + .downcast_rc::() + .unwrap(); + parser.push( document, Box::new(Section { @@ -260,6 +303,7 @@ impl RegexRule for SectionRule { depth: section_depth, kind: section_kind, reference: section_refname, + style, }), ); @@ -292,6 +336,13 @@ impl RegexRule for SectionRule { CTX.with_borrow(|ctx| { ctx.as_ref().map(|ctx| { + // Get style + let style = ctx + .parser + .current_style(section_style::STYLE_KEY) + .downcast_rc::() + .unwrap(); + ctx.parser.push( ctx.document, Box::new(Section { @@ -300,6 +351,7 @@ impl RegexRule for SectionRule { depth, kind, reference, + style, }), ); }) @@ -313,4 +365,42 @@ impl RegexRule for SectionRule { Some(bindings) } + + fn register_styles(&self, parser: &dyn Parser) { + parser.set_style(Rc::new(SectionStyle::default())); + } +} + +mod section_style { + use serde::Deserialize; + use serde::Serialize; + + use crate::document::style::ElementStyle; + use crate::impl_elementstyle; + + pub static STYLE_KEY: &'static str = "style.section"; + + #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] + pub enum SectionLinkPos { + Before, + After, + None, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct SectionStyle { + pub link_pos: SectionLinkPos, + pub link: String, + } + + impl Default for SectionStyle { + fn default() -> Self { + Self { + link_pos: SectionLinkPos::After, + link: "đŸ”—".to_string(), + } + } + } + + impl_elementstyle!(SectionStyle, STYLE_KEY); } diff --git a/src/elements/variable.rs b/src/elements/variable.rs index dd21a31..e8607f2 100644 --- a/src/elements/variable.rs +++ b/src/elements/variable.rs @@ -85,7 +85,7 @@ impl VariableRule { return Ok(name); } - pub fn validate_value(_colors: &ReportColors, original_value: &str) -> Result { + pub fn validate_value(original_value: &str) -> Result { let mut escaped = 0usize; let mut result = String::new(); for c in original_value.trim_start().trim_end().chars() { @@ -93,7 +93,7 @@ impl VariableRule { escaped += 1 } else if c == '\n' { match escaped { - 0 => return Err("Unknown error wile capturing variable".to_string()), + 0 => return Err("Unknown error wile capturing value".to_string()), // Remove '\n' 1 => {} // Insert '\n' @@ -202,7 +202,7 @@ impl RegexRule for VariableRule { }; let var_value = match matches.get(3) { - Some(value) => match VariableRule::validate_value(&parser.colors(), value.as_str()) { + Some(value) => match VariableRule::validate_value(value.as_str()) { Ok(var_value) => var_value, Err(msg) => { result.push( diff --git a/src/parser/langparser.rs b/src/parser/langparser.rs index 0cc569d..3b70bcf 100644 --- a/src/parser/langparser.rs +++ b/src/parser/langparser.rs @@ -1,3 +1,4 @@ +use std::cell::Ref; use std::cell::RefCell; use std::cell::RefMut; use std::collections::HashMap; @@ -15,6 +16,8 @@ use crate::document::element::DocumentEnd; use crate::document::element::ElemKind; use crate::document::element::Element; use crate::document::langdocument::LangDocument; +use crate::document::style::ElementStyle; +use crate::document::style::StyleHolder; use crate::elements::paragraph::Paragraph; use crate::elements::registrar::register; use crate::elements::text::Text; @@ -42,6 +45,7 @@ pub struct LangParser { pub err_flag: RefCell, pub state: RefCell, pub kernels: RefCell>, + pub styles: RefCell>>, } impl LangParser { @@ -52,12 +56,21 @@ impl LangParser { err_flag: RefCell::new(false), state: RefCell::new(StateHolder::new()), kernels: RefCell::new(HashMap::new()), + styles: RefCell::new(HashMap::new()), }; + // Register rules register(&mut s); + + // Register default kernel s.kernels .borrow_mut() .insert("main".to_string(), Kernel::new(&s)); + + // Register default styles + for rule in &s.rules { + rule.register_styles(&s); + } s } @@ -292,3 +305,11 @@ impl KernelHolder for LangParser { self.get_kernel(name.as_str()).unwrap() } } + +impl StyleHolder for LangParser { + fn styles(&self) -> Ref<'_, HashMap>> { self.styles.borrow() } + + fn styles_mut(&self) -> RefMut<'_, HashMap>> { + self.styles.borrow_mut() + } +} diff --git a/src/parser/parser.rs b/src/parser/parser.rs index f41bf31..4d28018 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -10,6 +10,7 @@ use super::source::Source; use super::state::StateHolder; use crate::document::document::Document; use crate::document::element::Element; +use crate::document::style::StyleHolder; use crate::lua::kernel::KernelHolder; use ariadne::Color; @@ -41,7 +42,7 @@ impl ReportColors { } } -pub trait Parser: KernelHolder { +pub trait Parser: KernelHolder + StyleHolder { /// Gets the colors for formatting errors /// /// When colors are disabled, all colors should resolve to empty string diff --git a/src/parser/rule.rs b/src/parser/rule.rs index cdb7c5a..55be741 100644 --- a/src/parser/rule.rs +++ b/src/parser/rule.rs @@ -25,7 +25,10 @@ pub trait Rule { match_data: Option>, ) -> (Cursor, Vec, Range)>>); /// Export bindings to lua - fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option)>>; + fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option)>> { None } + + /// Registers default styles + fn register_styles(&self, _parser: &dyn Parser) {} } impl core::fmt::Debug for dyn Rule { @@ -89,7 +92,8 @@ pub trait RegexRule { matches: regex::Captures, ) -> Vec, Range)>>; - fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option)>>; + fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option)>> { None } + fn register_styles(&self, _parser: &dyn Parser) {} } impl Rule for T { @@ -147,4 +151,8 @@ impl Rule for T { fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option)>> { self.lua_bindings(lua) } + + fn register_styles(&self, parser: &dyn Parser) { + self.register_styles(parser); + } }