Compare commits

...

2 commits

Author SHA1 Message Date
48d2064d0c Added element styling 2024-08-03 09:33:21 +02:00
7b081be97b Section numbering 2024-08-02 14:10:17 +02:00
13 changed files with 404 additions and 33 deletions

View file

@ -1,3 +1,4 @@
use std::cell::Ref;
use std::cell::RefCell; use std::cell::RefCell;
use std::cell::RefMut; use std::cell::RefMut;
use std::collections::HashMap; use std::collections::HashMap;
@ -20,6 +21,8 @@ pub struct Compiler {
cache: Option<RefCell<Connection>>, cache: Option<RefCell<Connection>>,
reference_count: RefCell<HashMap<String, HashMap<String, usize>>>, reference_count: RefCell<HashMap<String, HashMap<String, usize>>>,
// TODO: External references, i.e resolved later // TODO: External references, i.e resolved later
sections_counter: RefCell<Vec<usize>>,
} }
impl Compiler { impl Compiler {
@ -35,9 +38,34 @@ impl Compiler {
target, target,
cache: cache.map(|con| RefCell::new(con)), cache: cache.map(|con| RefCell::new(con)),
reference_count: RefCell::new(HashMap::new()), reference_count: RefCell::new(HashMap::new()),
sections_counter: RefCell::new(vec![]),
} }
} }
/// Gets the section counter for a given depth
/// This function modifies the section counter
pub fn section_counter(&self, depth: usize) -> Ref<'_, Vec<usize>>
{
// Increment current counter
if self.sections_counter.borrow().len() == depth {
self.sections_counter.borrow_mut().last_mut()
.map(|id| *id += 1);
return Ref::map(self.sections_counter.borrow(), |b| &*b);
}
// Close
while self.sections_counter.borrow().len() > depth {
self.sections_counter.borrow_mut().pop();
}
// Open
while self.sections_counter.borrow().len() < depth {
self.sections_counter.borrow_mut().push(1);
}
Ref::map(self.sections_counter.borrow(), |b| &*b)
}
/// Sanitizes text for a [`Target`] /// Sanitizes text for a [`Target`]
pub fn sanitize<S: AsRef<str>>(target: Target, str: S) -> String { pub fn sanitize<S: AsRef<str>>(target: Target, str: S) -> String {
match target { match target {

View file

@ -3,6 +3,7 @@ use std::str::FromStr;
use crate::compiler::compiler::Compiler; use crate::compiler::compiler::Compiler;
use crate::elements::reference::Reference; use crate::elements::reference::Reference;
use crate::parser::source::Token; use crate::parser::source::Token;
use crate::parser::util::PropertyParser;
use downcast_rs::impl_downcast; use downcast_rs::impl_downcast;
use downcast_rs::Downcast; use downcast_rs::Downcast;
@ -62,10 +63,15 @@ pub trait ReferenceableElement: Element {
fn refcount_key(&self) -> &'static str; fn refcount_key(&self) -> &'static str;
/// Creates the reference element /// Creates the reference element
fn compile_reference(&self, compiler: &Compiler, document: &dyn Document, reference: &Reference, refid: usize) -> Result<String, String>; fn compile_reference(
&self,
compiler: &Compiler,
document: &dyn Document,
reference: &Reference,
refid: usize,
) -> Result<String, String>;
} }
pub trait ContainerElement: Element { pub trait ContainerElement: Element {
/// Gets the contained elements /// Gets the contained elements
fn contained(&self) -> &Vec<Box<dyn Element>>; fn contained(&self) -> &Vec<Box<dyn Element>>;

View file

@ -3,3 +3,4 @@ pub mod references;
pub mod langdocument; pub mod langdocument;
pub mod element; pub mod element;
pub mod variable; pub mod variable;
pub mod style;

66
src/document/style.rs Normal file
View file

@ -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<Rc<dyn ElementStyle>, 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<String, Rc<dyn ElementStyle>>>;
/// gets a (mutable) reference to all defined styles
fn styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn ElementStyle>>>;
/// 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<dyn ElementStyle> {
self.styles().get(style_key).map(|rc| rc.clone()).unwrap()
}
/// Sets the [`style`]
fn set_style(&self, style: Rc<dyn ElementStyle>) {
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<std::rc::Rc<dyn ElementStyle>, String> {
serde_json::from_str::<$t>(json)
.map_err(|e| e.to_string())
.map(|obj| std::rc::Rc::new(obj) as std::rc::Rc<dyn ElementStyle>)
}
fn to_json(&self) -> String { serde_json::to_string(self).unwrap() }
}
};
}

134
src/elements/elemstyle.rs Normal file
View file

@ -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<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
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
}
}

View file

@ -16,3 +16,4 @@ pub mod style;
pub mod tex; pub mod tex;
pub mod text; pub mod text;
pub mod variable; pub mod variable;
pub mod elemstyle;

View file

@ -2,6 +2,7 @@ use crate::parser::parser::Parser;
use super::code::CodeRule; use super::code::CodeRule;
use super::comment::CommentRule; use super::comment::CommentRule;
use super::elemstyle::ElemStyleRule;
use super::graphviz::GraphRule; use super::graphviz::GraphRule;
use super::import::ImportRule; use super::import::ImportRule;
use super::layout::LayoutRule; use super::layout::LayoutRule;
@ -24,6 +25,7 @@ pub fn register<P: Parser>(parser: &mut P) {
parser.add_rule(Box::new(ParagraphRule::new()), None).unwrap(); parser.add_rule(Box::new(ParagraphRule::new()), None).unwrap();
parser.add_rule(Box::new(ImportRule::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(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(VariableRule::new()), None).unwrap();
parser.add_rule(Box::new(VariableSubstitutionRule::new()), None).unwrap(); parser.add_rule(Box::new(VariableSubstitutionRule::new()), None).unwrap();
parser.add_rule(Box::new(RawRule::new()), None).unwrap(); parser.add_rule(Box::new(RawRule::new()), None).unwrap();

View file

@ -282,7 +282,7 @@ impl RegexRule for ScriptRule {
mod tests { mod tests {
use super::*; use super::*;
use crate::elements::link::Link; use crate::elements::link::Link;
use crate::elements::list::ListEntry; use crate::elements::list::ListEntry;
use crate::elements::list::ListMarker; use crate::elements::list::ListMarker;
use crate::elements::paragraph::Paragraph; use crate::elements::paragraph::Paragraph;
use crate::elements::style::Style; use crate::elements::style::Style;

View file

@ -17,6 +17,8 @@ use mlua::Error::BadArgument;
use mlua::Function; use mlua::Function;
use mlua::Lua; use mlua::Lua;
use regex::Regex; use regex::Regex;
use section_style::SectionLinkPos;
use section_style::SectionStyle;
use std::ops::Range; use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
@ -24,30 +26,78 @@ use std::sync::Arc;
#[derive(Debug)] #[derive(Debug)]
pub struct Section { pub struct Section {
pub(self) location: Token, pub(self) location: Token,
pub(self) title: String, // Section title /// Title of the section
pub(self) depth: usize, // Section depth pub(self) title: String,
pub(self) kind: u8, // Section kind, e.g numbered, unnumbred, ... /// Depth i.e number of '#'
pub(self) reference: Option<String>, // Section reference name pub(self) depth: usize,
/// [`section_kind`]
pub(self) kind: u8,
/// Section reference name
pub(self) reference: Option<String>,
/// Style of the section
pub(self) style: Rc<section_style::SectionStyle>,
} }
impl Element for Section { impl Element for Section {
fn location(&self) -> &Token { &self.location } fn location(&self) -> &Token { &self.location }
fn kind(&self) -> ElemKind { ElemKind::Block } fn kind(&self) -> ElemKind { ElemKind::Block }
fn element_name(&self) -> &'static str { "Section" } 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<String, String> { fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
// Section numbering
let number = if (self.kind & section_kind::NO_NUMBER) == section_kind::NO_NUMBER {
let numbering = compiler.section_counter(self.depth);
let number = " ".to_string()
+ numbering
.iter()
.map(|n| n.to_string())
.collect::<Vec<_>>()
.join(".")
.as_str();
number
} else {
String::new()
};
if self.style.link_pos == SectionLinkPos::None {
return Ok(format!(
r#"<h{0} id="{1}">{number}{2}</h{0}>"#,
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!(
"<a class=\"section-link\" href=\"#{refname}\">{}</a>",
Compiler::sanitize(compiler.target(), self.style.link.as_str())
);
if self.style.link_pos == SectionLinkPos::After {
Ok(format!( Ok(format!(
r#"<h{0} id="{1}">{2}</h{0}>"#, r#"<h{0} id="{1}">{number}{2}{link}</h{0}>"#,
self.depth, self.depth,
Compiler::refname(compiler.target(), self.title.as_str()), Compiler::refname(compiler.target(), self.title.as_str()),
Compiler::sanitize(compiler.target(), self.title.as_str()) Compiler::sanitize(compiler.target(), self.title.as_str())
)) ))
}, } else
// Before
{
Ok(format!(
r#"<h{0} id="{1}">{link}{number}{2}</h{0}>"#,
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()), Target::LATEX => Err("Unimplemented compiler".to_string()),
} }
} }
fn as_referenceable(&self) -> Option<&dyn ReferenceableElement> { Some(self) }
} }
impl ReferenceableElement for Section { impl ReferenceableElement for Section {
@ -136,12 +186,12 @@ impl RegexRule for SectionRule {
}; };
// [Optional] Reference name // [Optional] Reference name
let section_refname = matches.get(2).map_or_else( let section_refname =
matches.get(2).map_or_else(
|| None, || None,
|refname| { |refname| {
// Check for duplicate reference // Check for duplicate reference
if let Some(elem_reference) = document.get_reference(refname.as_str()) if let Some(elem_reference) = document.get_reference(refname.as_str()) {
{
let elem = document.get_from_reference(&elem_reference).unwrap(); let elem = document.get_from_reference(&elem_reference).unwrap();
result.push( result.push(
@ -239,6 +289,12 @@ impl RegexRule for SectionRule {
_ => panic!("Empty section name"), _ => panic!("Empty section name"),
}; };
// Get style
let style = parser
.current_style(section_style::STYLE_KEY)
.downcast_rc::<SectionStyle>()
.unwrap();
parser.push( parser.push(
document, document,
Box::new(Section { Box::new(Section {
@ -247,6 +303,7 @@ impl RegexRule for SectionRule {
depth: section_depth, depth: section_depth,
kind: section_kind, kind: section_kind,
reference: section_refname, reference: section_refname,
style,
}), }),
); );
@ -279,6 +336,13 @@ impl RegexRule for SectionRule {
CTX.with_borrow(|ctx| { CTX.with_borrow(|ctx| {
ctx.as_ref().map(|ctx| { ctx.as_ref().map(|ctx| {
// Get style
let style = ctx
.parser
.current_style(section_style::STYLE_KEY)
.downcast_rc::<SectionStyle>()
.unwrap();
ctx.parser.push( ctx.parser.push(
ctx.document, ctx.document,
Box::new(Section { Box::new(Section {
@ -287,6 +351,7 @@ impl RegexRule for SectionRule {
depth, depth,
kind, kind,
reference, reference,
style,
}), }),
); );
}) })
@ -300,4 +365,42 @@ impl RegexRule for SectionRule {
Some(bindings) 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);
} }

View file

@ -85,7 +85,7 @@ impl VariableRule {
return Ok(name); return Ok(name);
} }
pub fn validate_value(_colors: &ReportColors, original_value: &str) -> Result<String, String> { pub fn validate_value(original_value: &str) -> Result<String, String> {
let mut escaped = 0usize; let mut escaped = 0usize;
let mut result = String::new(); let mut result = String::new();
for c in original_value.trim_start().trim_end().chars() { for c in original_value.trim_start().trim_end().chars() {
@ -93,7 +93,7 @@ impl VariableRule {
escaped += 1 escaped += 1
} else if c == '\n' { } else if c == '\n' {
match escaped { match escaped {
0 => return Err("Unknown error wile capturing variable".to_string()), 0 => return Err("Unknown error wile capturing value".to_string()),
// Remove '\n' // Remove '\n'
1 => {} 1 => {}
// Insert '\n' // Insert '\n'
@ -202,7 +202,7 @@ impl RegexRule for VariableRule {
}; };
let var_value = match matches.get(3) { 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, Ok(var_value) => var_value,
Err(msg) => { Err(msg) => {
result.push( result.push(

View file

@ -1,3 +1,4 @@
use std::cell::Ref;
use std::cell::RefCell; use std::cell::RefCell;
use std::cell::RefMut; use std::cell::RefMut;
use std::collections::HashMap; use std::collections::HashMap;
@ -15,6 +16,8 @@ use crate::document::element::DocumentEnd;
use crate::document::element::ElemKind; use crate::document::element::ElemKind;
use crate::document::element::Element; use crate::document::element::Element;
use crate::document::langdocument::LangDocument; use crate::document::langdocument::LangDocument;
use crate::document::style::ElementStyle;
use crate::document::style::StyleHolder;
use crate::elements::paragraph::Paragraph; use crate::elements::paragraph::Paragraph;
use crate::elements::registrar::register; use crate::elements::registrar::register;
use crate::elements::text::Text; use crate::elements::text::Text;
@ -42,6 +45,7 @@ pub struct LangParser {
pub err_flag: RefCell<bool>, pub err_flag: RefCell<bool>,
pub state: RefCell<StateHolder>, pub state: RefCell<StateHolder>,
pub kernels: RefCell<HashMap<String, Kernel>>, pub kernels: RefCell<HashMap<String, Kernel>>,
pub styles: RefCell<HashMap<String, Rc<dyn ElementStyle>>>,
} }
impl LangParser { impl LangParser {
@ -52,12 +56,21 @@ impl LangParser {
err_flag: RefCell::new(false), err_flag: RefCell::new(false),
state: RefCell::new(StateHolder::new()), state: RefCell::new(StateHolder::new()),
kernels: RefCell::new(HashMap::new()), kernels: RefCell::new(HashMap::new()),
styles: RefCell::new(HashMap::new()),
}; };
// Register rules
register(&mut s); register(&mut s);
// Register default kernel
s.kernels s.kernels
.borrow_mut() .borrow_mut()
.insert("main".to_string(), Kernel::new(&s)); .insert("main".to_string(), Kernel::new(&s));
// Register default styles
for rule in &s.rules {
rule.register_styles(&s);
}
s s
} }
@ -292,3 +305,11 @@ impl KernelHolder for LangParser {
self.get_kernel(name.as_str()).unwrap() self.get_kernel(name.as_str()).unwrap()
} }
} }
impl StyleHolder for LangParser {
fn styles(&self) -> Ref<'_, HashMap<String, Rc<dyn ElementStyle>>> { self.styles.borrow() }
fn styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn ElementStyle>>> {
self.styles.borrow_mut()
}
}

View file

@ -10,6 +10,7 @@ use super::source::Source;
use super::state::StateHolder; use super::state::StateHolder;
use crate::document::document::Document; use crate::document::document::Document;
use crate::document::element::Element; use crate::document::element::Element;
use crate::document::style::StyleHolder;
use crate::lua::kernel::KernelHolder; use crate::lua::kernel::KernelHolder;
use ariadne::Color; use ariadne::Color;
@ -41,7 +42,7 @@ impl ReportColors {
} }
} }
pub trait Parser: KernelHolder { pub trait Parser: KernelHolder + StyleHolder {
/// Gets the colors for formatting errors /// Gets the colors for formatting errors
/// ///
/// When colors are disabled, all colors should resolve to empty string /// When colors are disabled, all colors should resolve to empty string

View file

@ -25,7 +25,10 @@ pub trait Rule {
match_data: Option<Box<dyn Any>>, match_data: Option<Box<dyn Any>>,
) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>); ) -> (Cursor, Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>);
/// Export bindings to lua /// Export bindings to lua
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>>; fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
/// Registers default styles
fn register_styles(&self, _parser: &dyn Parser) {}
} }
impl core::fmt::Debug for dyn Rule { impl core::fmt::Debug for dyn Rule {
@ -89,7 +92,8 @@ pub trait RegexRule {
matches: regex::Captures, matches: regex::Captures,
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>; ) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>;
fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>>; fn lua_bindings<'lua>(&self, _lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { None }
fn register_styles(&self, _parser: &dyn Parser) {}
} }
impl<T: RegexRule> Rule for T { impl<T: RegexRule> Rule for T {
@ -147,4 +151,8 @@ impl<T: RegexRule> Rule for T {
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> { fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> {
self.lua_bindings(lua) self.lua_bindings(lua)
} }
fn register_styles(&self, parser: &dyn Parser) {
self.register_styles(parser);
}
} }