nml/src/elements/section.rs

447 lines
12 KiB
Rust
Raw Normal View History

2024-07-26 11:21:00 +02:00
use crate::compiler::compiler::Compiler;
use crate::compiler::compiler::Target;
use crate::document::document::Document;
use crate::document::element::ElemKind;
use crate::document::element::Element;
use crate::document::element::ReferenceableElement;
use crate::lua::kernel::CTX;
use crate::parser::parser::Parser;
use crate::parser::rule::RegexRule;
use crate::parser::source::Source;
use crate::parser::source::Token;
use ariadne::Fmt;
use ariadne::Label;
use ariadne::Report;
use ariadne::ReportKind;
use mlua::Error::BadArgument;
use mlua::Function;
use mlua::Lua;
2024-07-19 11:52:12 +02:00
use regex::Regex;
2024-08-03 09:33:21 +02:00
use section_style::SectionLinkPos;
use section_style::SectionStyle;
2024-07-26 11:21:00 +02:00
use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
2024-07-19 11:52:12 +02:00
#[derive(Debug)]
pub struct Section {
2024-07-26 11:21:00 +02:00
pub(self) location: Token,
2024-08-03 09:33:21 +02:00
/// 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<String>,
/// Style of the section
pub(self) style: Rc<section_style::SectionStyle>,
2024-07-19 11:52:12 +02:00
}
2024-07-26 11:21:00 +02:00
impl Element for Section {
fn location(&self) -> &Token { &self.location }
fn kind(&self) -> ElemKind { ElemKind::Block }
fn element_name(&self) -> &'static str { "Section" }
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> {
match compiler.target() {
Target::HTML => {
2024-08-03 09:33:21 +02:00
// Section numbering
2024-08-03 18:43:54 +02:00
let number = if (self.kind & section_kind::NO_NUMBER) != section_kind::NO_NUMBER {
2024-08-02 14:10:17 +02:00
let numbering = compiler.section_counter(self.depth);
2024-08-03 18:43:54 +02:00
let mut result = String::new();
for num in numbering.iter()
{
result = result + num.to_string().as_str() + ".";
}
result += " ";
result
2024-08-03 09:33:21 +02:00
} 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())
));
2024-08-02 14:10:17 +02:00
}
2024-08-03 09:33:21 +02:00
let refname = Compiler::refname(compiler.target(), self.title.as_str());
let link = format!(
2024-08-03 18:43:54 +02:00
"{}<a class=\"section-link\" href=\"#{refname}\">{}</a>{}",
Compiler::sanitize(compiler.target(), self.style.link[0].as_str()),
Compiler::sanitize(compiler.target(), self.style.link[1].as_str()),
Compiler::sanitize(compiler.target(), self.style.link[2].as_str())
2024-08-03 09:33:21 +02:00
);
if self.style.link_pos == SectionLinkPos::After {
Ok(format!(
r#"<h{0} id="{1}">{number}{2}{link}</h{0}>"#,
self.depth,
Compiler::refname(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())
))
}
2024-08-02 14:10:17 +02:00
}
2024-07-26 11:21:00 +02:00
Target::LATEX => Err("Unimplemented compiler".to_string()),
}
}
2024-08-03 09:33:21 +02:00
fn as_referenceable(&self) -> Option<&dyn ReferenceableElement> { Some(self) }
2024-07-19 11:52:12 +02:00
}
2024-07-26 11:21:00 +02:00
impl ReferenceableElement for Section {
fn reference_name(&self) -> Option<&String> { self.reference.as_ref() }
fn refcount_key(&self) -> &'static str { "section" }
2024-07-26 15:06:09 +02:00
fn compile_reference(
&self,
compiler: &Compiler,
_document: &dyn Document,
2024-07-26 15:06:09 +02:00
reference: &super::reference::Reference,
_refid: usize,
2024-07-26 15:06:09 +02:00
) -> Result<String, String> {
match compiler.target() {
Target::HTML => {
let caption = reference.caption().map_or(
format!(
"({})",
Compiler::sanitize(compiler.target(), self.title.as_str())
),
|cap| cap.clone(),
);
Ok(format!(
2024-08-03 18:43:54 +02:00
"<a class=\"section-reference\" href=\"#{}\">{caption}</a>",
Compiler::refname(compiler.target(), self.title.as_str())
))
}
_ => todo!(""),
}
2024-07-26 15:06:09 +02:00
}
2024-07-19 11:52:12 +02:00
}
pub struct SectionRule {
re: [Regex; 1],
}
impl SectionRule {
pub fn new() -> Self {
2024-07-26 11:21:00 +02:00
Self {
re: [Regex::new(r"(?:^|\n)(#{1,})(?:\{(.*)\})?((\*|\+){1,})?(.*)").unwrap()],
}
2024-07-19 11:52:12 +02:00
}
}
2024-07-26 11:21:00 +02:00
pub mod section_kind {
pub const NONE: u8 = 0x00;
pub const NO_TOC: u8 = 0x01;
pub const NO_NUMBER: u8 = 0x02;
2024-07-19 11:52:12 +02:00
}
impl RegexRule for SectionRule {
fn name(&self) -> &'static str { "Section" }
fn regexes(&self) -> &[Regex] { &self.re }
2024-07-26 11:21:00 +02:00
fn on_regex_match(
&self,
_: usize,
parser: &dyn Parser,
document: &dyn Document,
token: Token,
matches: regex::Captures,
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
2024-07-19 11:52:12 +02:00
let mut result = vec![];
2024-07-26 11:21:00 +02:00
let section_depth = match matches.get(1) {
2024-07-19 11:52:12 +02:00
Some(depth) => {
if depth.len() > 6 {
result.push(
Report::build(ReportKind::Error, token.source(), depth.start())
.with_message("Invalid section depth")
.with_label(
Label::new((token.source(), depth.range()))
.with_message(format!("Section is of depth {}, which is greather than {} (maximum depth allowed)",
depth.len().fg(parser.colors().info),
6.fg(parser.colors().info)))
.with_color(parser.colors().error))
.finish());
2024-07-26 11:21:00 +02:00
return result;
2024-07-19 11:52:12 +02:00
}
depth.len()
2024-07-26 11:21:00 +02:00
}
2024-07-19 11:52:12 +02:00
_ => panic!("Empty section depth"),
};
// [Optional] Reference name
2024-08-02 14:10:17 +02:00
let section_refname =
matches.get(2).map_or_else(
|| None,
|refname| {
// Check for duplicate reference
if let Some(elem_reference) = document.get_reference(refname.as_str()) {
let elem = document.get_from_reference(&elem_reference).unwrap();
result.push(
2024-07-19 11:52:12 +02:00
Report::build(ReportKind::Warning, token.source(), refname.start())
.with_message("Duplicate reference name")
.with_label(
Label::new((token.source(), refname.range()))
.with_message(format!("Reference with name `{}` is already defined in `{}`",
refname.as_str().fg(parser.colors().highlight),
2024-08-02 13:36:04 +02:00
elem.location().source().name().as_str().fg(parser.colors().highlight)))
2024-07-19 11:52:12 +02:00
.with_message(format!("`{}` conflicts with previously defined reference to {}",
refname.as_str().fg(parser.colors().highlight),
2024-08-02 13:36:04 +02:00
elem.element_name().fg(parser.colors().highlight)))
2024-07-19 11:52:12 +02:00
.with_color(parser.colors().warning))
2024-07-26 11:21:00 +02:00
.with_label(
2024-08-02 13:36:04 +02:00
Label::new((elem.location().source(), elem.location().start()..elem.location().end() ))
2024-07-26 11:21:00 +02:00
.with_message(format!("`{}` previously defined here",
refname.as_str().fg(parser.colors().highlight)))
2024-07-19 11:52:12 +02:00
.with_color(parser.colors().warning))
.with_note(format!("Previous reference was overwritten"))
.finish());
2024-08-02 14:10:17 +02:00
}
Some(refname.as_str().to_string())
},
);
2024-07-19 11:52:12 +02:00
// Section kind
2024-07-26 11:21:00 +02:00
let section_kind = match matches.get(3) {
Some(kind) => match kind.as_str() {
"*+" | "+*" => section_kind::NO_NUMBER | section_kind::NO_TOC,
"*" => section_kind::NO_NUMBER,
"+" => section_kind::NO_TOC,
"" => section_kind::NONE,
_ => {
result.push(
2024-07-19 11:52:12 +02:00
Report::build(ReportKind::Error, token.source(), kind.start())
.with_message("Invalid section numbering kind")
.with_label(
Label::new((token.source(), kind.range()))
.with_message(format!("Section numbering kind must be a combination of `{}` for unnumbered, and `{}` for non-listing; got `{}`",
"*".fg(parser.colors().info),
"+".fg(parser.colors().info),
kind.as_str().fg(parser.colors().highlight)))
.with_color(parser.colors().error))
.with_help(format!("Leave empty for a numbered listed section"))
.finish());
2024-07-26 11:21:00 +02:00
return result;
2024-07-19 11:52:12 +02:00
}
2024-07-26 11:21:00 +02:00
},
2024-07-19 11:52:12 +02:00
_ => section_kind::NONE,
};
// Spacing + Section name
2024-07-26 11:21:00 +02:00
let section_name = match matches.get(5) {
2024-07-19 11:52:12 +02:00
Some(name) => {
2024-07-26 11:21:00 +02:00
let split = name
.as_str()
.chars()
2024-07-19 11:52:12 +02:00
.position(|c| !c.is_whitespace())
.unwrap_or(0);
let section_name = &name.as_str()[split..];
2024-07-26 11:21:00 +02:00
if section_name.is_empty()
// No name
2024-07-19 11:52:12 +02:00
{
result.push(
Report::build(ReportKind::Error, token.source(), name.start())
2024-07-26 11:21:00 +02:00
.with_message("Missing section name")
.with_label(
Label::new((token.source(), name.range()))
.with_message("Sections require a name before line end")
.with_color(parser.colors().error),
)
.finish(),
);
2024-07-19 11:52:12 +02:00
return result;
}
// No spacing
2024-07-26 11:21:00 +02:00
if split == 0 {
2024-07-19 11:52:12 +02:00
result.push(
Report::build(ReportKind::Warning, token.source(), name.start())
.with_message("Missing section spacing")
.with_label(
Label::new((token.source(), name.range()))
.with_message("Sections require at least one whitespace before the section's name")
.with_color(parser.colors().warning))
.with_help(format!("Add a space before `{}`", section_name.fg(parser.colors().highlight)))
.finish());
return result;
}
section_name.to_string()
2024-07-26 11:21:00 +02:00
}
_ => panic!("Empty section name"),
2024-07-19 11:52:12 +02:00
};
2024-08-03 09:33:21 +02:00
// Get style
let style = parser
.current_style(section_style::STYLE_KEY)
.downcast_rc::<SectionStyle>()
.unwrap();
2024-07-26 11:21:00 +02:00
parser.push(
document,
Box::new(Section {
2024-07-21 15:56:56 +02:00
location: token.clone(),
2024-07-26 11:21:00 +02:00
title: section_name,
depth: section_depth,
kind: section_kind,
reference: section_refname,
2024-08-03 09:33:21 +02:00
style,
2024-07-26 11:21:00 +02:00
}),
);
return result;
2024-07-19 11:52:12 +02:00
}
2024-07-21 15:56:56 +02:00
2024-07-30 11:01:22 +02:00
fn lua_bindings<'lua>(&self, lua: &'lua Lua) -> Option<Vec<(String, Function<'lua>)>> {
2024-07-21 15:56:56 +02:00
let mut bindings = vec![];
2024-07-26 11:21:00 +02:00
bindings.push((
"push".to_string(),
lua.create_function(
|_, (title, depth, kind, reference): (String, usize, String, Option<String>)| {
let kind = match kind.as_str() {
"*+" | "+*" => section_kind::NO_NUMBER | section_kind::NO_TOC,
"*" => section_kind::NO_NUMBER,
"+" => section_kind::NO_TOC,
"" => section_kind::NONE,
_ => {
return Err(BadArgument {
to: Some("push".to_string()),
pos: 3,
name: Some("kind".to_string()),
cause: Arc::new(mlua::Error::external(format!(
"Unknown section kind specified"
))),
})
}
};
CTX.with_borrow(|ctx| {
ctx.as_ref().map(|ctx| {
2024-08-03 09:33:21 +02:00
// Get style
let style = ctx
.parser
.current_style(section_style::STYLE_KEY)
.downcast_rc::<SectionStyle>()
.unwrap();
2024-07-26 11:21:00 +02:00
ctx.parser.push(
ctx.document,
Box::new(Section {
location: ctx.location.clone(),
title,
depth,
kind,
reference,
2024-08-03 09:33:21 +02:00
style,
2024-07-26 11:21:00 +02:00
}),
);
})
});
Ok(())
},
)
.unwrap(),
));
2024-07-30 11:01:22 +02:00
Some(bindings)
2024-07-21 15:56:56 +02:00
}
2024-08-03 09:33:21 +02:00
fn register_styles(&self, parser: &dyn Parser) {
2024-08-03 11:01:32 +02:00
parser.set_current_style(Rc::new(SectionStyle::default()));
2024-08-03 09:33:21 +02:00
}
}
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,
2024-08-03 18:43:54 +02:00
pub link: [String; 3],
2024-08-03 09:33:21 +02:00
}
impl Default for SectionStyle {
fn default() -> Self {
Self {
2024-08-03 18:43:54 +02:00
link_pos: SectionLinkPos::Before,
link: ["".into(), "🔗".into(), " ".into()],
2024-08-03 09:33:21 +02:00
}
}
}
impl_elementstyle!(SectionStyle, STYLE_KEY);
2024-07-19 11:52:12 +02:00
}
2024-08-04 08:32:15 +02:00
#[cfg(test)]
mod tests
{
use crate::{parser::{langparser::LangParser, source::SourceFile}, validate_document};
use super::*;
#[test]
fn parser()
{
let source = Rc::new(SourceFile::with_content(
"".to_string(),
r#"
# 1
##+ 2
###* 3
####+* 4
#####*+ 5
######{refname} 6
"#
.to_string(),
None,
));
let parser = LangParser::default();
let doc = parser.parse(source, None);
validate_document!(doc.content().borrow(), 0,
Section { depth == 1, title == "1" };
Section { depth == 2, title == "2", kind == section_kind::NO_TOC };
Section { depth == 3, title == "3", kind == section_kind::NO_NUMBER };
Section { depth == 4, title == "4", kind == section_kind::NO_NUMBER | section_kind::NO_TOC };
Section { depth == 5, title == "5", kind == section_kind::NO_NUMBER | section_kind::NO_TOC };
Section { depth == 6, title == "6", reference == Some("refname".to_string()) };
);
}
}