2024-07-24 13:20:29 +02:00
|
|
|
use crate::compiler::compiler::Compiler;
|
|
|
|
use crate::compiler::compiler::Target;
|
|
|
|
use crate::document::document::Document;
|
2024-08-01 16:15:10 +02:00
|
|
|
use crate::document::element::ContainerElement;
|
2024-07-24 13:20:29 +02:00
|
|
|
use crate::document::element::ElemKind;
|
|
|
|
use crate::document::element::Element;
|
2024-08-04 10:25:51 +02:00
|
|
|
use crate::lua::kernel::CTX;
|
2024-08-05 18:40:17 +02:00
|
|
|
use crate::parser::parser::ParserState;
|
2024-07-24 13:20:29 +02:00
|
|
|
use crate::parser::rule::RegexRule;
|
|
|
|
use crate::parser::source::Source;
|
|
|
|
use crate::parser::source::Token;
|
2024-08-01 16:15:10 +02:00
|
|
|
use crate::parser::source::VirtualSource;
|
2024-07-24 13:20:29 +02:00
|
|
|
use crate::parser::util;
|
|
|
|
use ariadne::Fmt;
|
|
|
|
use ariadne::Label;
|
|
|
|
use ariadne::Report;
|
|
|
|
use ariadne::ReportKind;
|
2024-08-04 10:25:51 +02:00
|
|
|
use mlua::Error::BadArgument;
|
2024-07-24 13:20:29 +02:00
|
|
|
use mlua::Function;
|
|
|
|
use mlua::Lua;
|
|
|
|
use regex::Captures;
|
|
|
|
use regex::Regex;
|
|
|
|
use std::ops::Range;
|
|
|
|
use std::rc::Rc;
|
2024-08-04 10:25:51 +02:00
|
|
|
use std::sync::Arc;
|
2024-07-19 11:52:12 +02:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Link {
|
2024-08-02 13:36:04 +02:00
|
|
|
pub location: Token,
|
2024-08-01 16:15:10 +02:00
|
|
|
/// Display content of link
|
2024-08-02 13:36:04 +02:00
|
|
|
pub display: Vec<Box<dyn Element>>,
|
2024-08-01 16:15:10 +02:00
|
|
|
/// Url of link
|
2024-08-02 13:36:04 +02:00
|
|
|
pub url: String,
|
2024-07-19 11:52:12 +02:00
|
|
|
}
|
|
|
|
|
2024-07-24 13:20:29 +02:00
|
|
|
impl Element for Link {
|
|
|
|
fn location(&self) -> &Token { &self.location }
|
|
|
|
fn kind(&self) -> ElemKind { ElemKind::Inline }
|
|
|
|
fn element_name(&self) -> &'static str { "Link" }
|
2024-08-01 16:15:10 +02:00
|
|
|
fn compile(&self, compiler: &Compiler, document: &dyn Document) -> Result<String, String> {
|
2024-07-24 13:20:29 +02:00
|
|
|
match compiler.target() {
|
2024-08-01 16:15:10 +02:00
|
|
|
Target::HTML => {
|
|
|
|
let mut result = format!(
|
|
|
|
"<a href=\"{}\">",
|
|
|
|
Compiler::sanitize(compiler.target(), self.url.as_str())
|
|
|
|
);
|
|
|
|
|
2024-08-02 10:34:56 +02:00
|
|
|
for elem in &self.display {
|
|
|
|
result += elem.compile(compiler, document)?.as_str();
|
|
|
|
}
|
2024-08-01 16:15:10 +02:00
|
|
|
|
|
|
|
result += "</a>";
|
|
|
|
Ok(result)
|
|
|
|
}
|
|
|
|
_ => todo!(""),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn as_container(&self) -> Option<&dyn ContainerElement> { Some(self) }
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ContainerElement for Link {
|
2024-08-02 10:34:56 +02:00
|
|
|
fn contained(&self) -> &Vec<Box<dyn Element>> { &self.display }
|
2024-08-01 16:15:10 +02:00
|
|
|
|
|
|
|
fn push(&mut self, elem: Box<dyn Element>) -> Result<(), String> {
|
|
|
|
if elem.downcast_ref::<Link>().is_some() {
|
|
|
|
return Err("Tried to push a link inside of a link".to_string());
|
2024-07-24 13:20:29 +02:00
|
|
|
}
|
2024-08-02 10:34:56 +02:00
|
|
|
self.display.push(elem);
|
|
|
|
Ok(())
|
2024-07-24 13:20:29 +02:00
|
|
|
}
|
2024-07-19 11:52:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct LinkRule {
|
|
|
|
re: [Regex; 1],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl LinkRule {
|
|
|
|
pub fn new() -> Self {
|
2024-07-24 13:20:29 +02:00
|
|
|
Self {
|
|
|
|
re: [Regex::new(r"\[((?:\\.|[^\\\\])*?)\]\(((?:\\.|[^\\\\])*?)\)").unwrap()],
|
|
|
|
}
|
2024-07-19 11:52:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RegexRule for LinkRule {
|
|
|
|
fn name(&self) -> &'static str { "Link" }
|
|
|
|
|
|
|
|
fn regexes(&self) -> &[Regex] { &self.re }
|
|
|
|
|
2024-07-24 13:20:29 +02:00
|
|
|
fn on_regex_match<'a>(
|
|
|
|
&self,
|
|
|
|
_: usize,
|
2024-08-06 18:58:41 +02:00
|
|
|
state: &ParserState,
|
2024-08-01 16:15:10 +02:00
|
|
|
document: &'a (dyn Document<'a> + 'a),
|
2024-07-24 13:20:29 +02:00
|
|
|
token: Token,
|
|
|
|
matches: Captures,
|
|
|
|
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
|
2024-08-01 16:15:10 +02:00
|
|
|
let mut reports = vec![];
|
|
|
|
|
|
|
|
let link_display = match matches.get(1) {
|
|
|
|
Some(display) => {
|
|
|
|
if display.as_str().is_empty() {
|
|
|
|
reports.push(
|
|
|
|
Report::build(ReportKind::Error, token.source(), display.start())
|
2024-07-24 13:20:29 +02:00
|
|
|
.with_message("Empty link name")
|
|
|
|
.with_label(
|
2024-08-01 16:15:10 +02:00
|
|
|
Label::new((token.source().clone(), display.range()))
|
2024-07-24 13:20:29 +02:00
|
|
|
.with_message("Link name is empty")
|
2024-08-05 18:40:17 +02:00
|
|
|
.with_color(state.parser.colors().error),
|
2024-07-24 13:20:29 +02:00
|
|
|
)
|
|
|
|
.finish(),
|
|
|
|
);
|
2024-08-01 16:15:10 +02:00
|
|
|
return reports;
|
2024-07-24 13:20:29 +02:00
|
|
|
}
|
2024-08-01 16:15:10 +02:00
|
|
|
let processed = util::process_escaped('\\', "]", display.as_str());
|
|
|
|
if processed.is_empty() {
|
|
|
|
reports.push(
|
|
|
|
Report::build(ReportKind::Error, token.source(), display.start())
|
2024-07-24 13:20:29 +02:00
|
|
|
.with_message("Empty link name")
|
|
|
|
.with_label(
|
2024-08-01 16:15:10 +02:00
|
|
|
Label::new((token.source(), display.range()))
|
2024-07-24 13:20:29 +02:00
|
|
|
.with_message(format!(
|
|
|
|
"Link name is empty. Once processed, `{}` yields `{}`",
|
2024-08-05 18:40:17 +02:00
|
|
|
display.as_str().fg(state.parser.colors().highlight),
|
|
|
|
processed.fg(state.parser.colors().highlight),
|
2024-07-24 13:20:29 +02:00
|
|
|
))
|
2024-08-05 18:40:17 +02:00
|
|
|
.with_color(state.parser.colors().error),
|
2024-07-24 13:20:29 +02:00
|
|
|
)
|
|
|
|
.finish(),
|
|
|
|
);
|
2024-08-01 16:15:10 +02:00
|
|
|
return reports;
|
|
|
|
}
|
|
|
|
|
|
|
|
let source = Rc::new(VirtualSource::new(
|
|
|
|
Token::new(display.range(), token.source()),
|
|
|
|
"Link Display".to_string(),
|
|
|
|
processed,
|
|
|
|
));
|
2024-08-05 18:40:17 +02:00
|
|
|
match util::parse_paragraph(state, source, document) {
|
2024-08-01 16:15:10 +02:00
|
|
|
Err(err) => {
|
|
|
|
reports.push(
|
|
|
|
Report::build(ReportKind::Error, token.source(), display.start())
|
|
|
|
.with_message("Failed to parse link display")
|
|
|
|
.with_label(
|
|
|
|
Label::new((token.source(), display.range()))
|
|
|
|
.with_message(err.to_string())
|
2024-08-05 18:40:17 +02:00
|
|
|
.with_color(state.parser.colors().error),
|
2024-08-01 16:15:10 +02:00
|
|
|
)
|
|
|
|
.finish(),
|
|
|
|
);
|
|
|
|
return reports;
|
|
|
|
}
|
2024-08-02 10:34:56 +02:00
|
|
|
Ok(mut paragraph) => std::mem::replace(&mut paragraph.content, vec![]),
|
2024-07-24 13:20:29 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => panic!("Empty link name"),
|
|
|
|
};
|
2024-07-19 11:52:12 +02:00
|
|
|
|
2024-07-24 13:20:29 +02:00
|
|
|
let link_url = match matches.get(2) {
|
|
|
|
Some(url) => {
|
|
|
|
if url.as_str().is_empty() {
|
2024-08-01 16:15:10 +02:00
|
|
|
reports.push(
|
2024-07-19 11:52:12 +02:00
|
|
|
Report::build(ReportKind::Error, token.source(), url.start())
|
2024-07-24 13:20:29 +02:00
|
|
|
.with_message("Empty link url")
|
|
|
|
.with_label(
|
|
|
|
Label::new((token.source(), url.range()))
|
|
|
|
.with_message("Link url is empty")
|
2024-08-05 18:40:17 +02:00
|
|
|
.with_color(state.parser.colors().error),
|
2024-07-24 13:20:29 +02:00
|
|
|
)
|
|
|
|
.finish(),
|
|
|
|
);
|
2024-08-01 16:15:10 +02:00
|
|
|
return reports;
|
2024-07-24 13:20:29 +02:00
|
|
|
}
|
|
|
|
let text_content = util::process_text(document, url.as_str());
|
2024-07-19 11:52:12 +02:00
|
|
|
|
2024-07-24 13:20:29 +02:00
|
|
|
if text_content.as_str().is_empty() {
|
2024-08-01 16:15:10 +02:00
|
|
|
reports.push(
|
2024-07-19 11:52:12 +02:00
|
|
|
Report::build(ReportKind::Error, token.source(), url.start())
|
2024-07-24 13:20:29 +02:00
|
|
|
.with_message("Empty link url")
|
|
|
|
.with_label(
|
|
|
|
Label::new((token.source(), url.range()))
|
|
|
|
.with_message(format!(
|
|
|
|
"Link url is empty. Once processed, `{}` yields `{}`",
|
2024-08-05 18:40:17 +02:00
|
|
|
url.as_str().fg(state.parser.colors().highlight),
|
|
|
|
text_content.as_str().fg(state.parser.colors().highlight),
|
2024-07-24 13:20:29 +02:00
|
|
|
))
|
2024-08-05 18:40:17 +02:00
|
|
|
.with_color(state.parser.colors().error),
|
2024-07-24 13:20:29 +02:00
|
|
|
)
|
|
|
|
.finish(),
|
|
|
|
);
|
2024-08-01 16:15:10 +02:00
|
|
|
return reports;
|
2024-07-24 13:20:29 +02:00
|
|
|
}
|
2024-07-19 11:52:12 +02:00
|
|
|
text_content
|
2024-07-24 13:20:29 +02:00
|
|
|
}
|
|
|
|
_ => panic!("Empty link url"),
|
|
|
|
};
|
2024-07-19 11:52:12 +02:00
|
|
|
|
2024-08-06 18:58:41 +02:00
|
|
|
state.push(
|
2024-07-24 13:20:29 +02:00
|
|
|
document,
|
2024-08-01 16:15:10 +02:00
|
|
|
Box::new(Link {
|
|
|
|
location: token,
|
|
|
|
display: link_display,
|
|
|
|
url: link_url,
|
|
|
|
}),
|
2024-07-24 13:20:29 +02:00
|
|
|
);
|
2024-07-19 11:52:12 +02:00
|
|
|
|
2024-08-01 16:15:10 +02:00
|
|
|
return reports;
|
2024-07-19 11:52:12 +02:00
|
|
|
}
|
2024-07-21 15:56:56 +02:00
|
|
|
|
2024-08-05 18:40:17 +02:00
|
|
|
fn register_bindings<'lua>(&self, lua: &'lua Lua) -> Vec<(String, Function<'lua>)> {
|
2024-08-04 10:25:51 +02:00
|
|
|
let mut bindings = vec![];
|
|
|
|
|
|
|
|
bindings.push((
|
|
|
|
"push".to_string(),
|
|
|
|
lua.create_function(|_, (display, url): (String, String)| {
|
|
|
|
let mut result = Ok(());
|
|
|
|
CTX.with_borrow(|ctx| {
|
|
|
|
ctx.as_ref().map(|ctx| {
|
|
|
|
let source = Rc::new(VirtualSource::new(
|
|
|
|
ctx.location.clone(),
|
|
|
|
"Link Display".to_string(),
|
|
|
|
display,
|
|
|
|
));
|
|
|
|
let display_content =
|
2024-08-06 18:58:41 +02:00
|
|
|
match util::parse_paragraph(ctx.state, source, ctx.document) {
|
2024-08-04 10:25:51 +02:00
|
|
|
Err(err) => {
|
|
|
|
result = Err(BadArgument {
|
|
|
|
to: Some("push".to_string()),
|
|
|
|
pos: 1,
|
|
|
|
name: Some("display".to_string()),
|
|
|
|
cause: Arc::new(mlua::Error::external(format!(
|
|
|
|
"Failed to parse link display: {err}"
|
|
|
|
))),
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Ok(mut paragraph) => {
|
|
|
|
std::mem::replace(&mut paragraph.content, vec![])
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2024-08-06 18:58:41 +02:00
|
|
|
ctx.state.push(
|
2024-08-04 10:25:51 +02:00
|
|
|
ctx.document,
|
|
|
|
Box::new(Link {
|
|
|
|
location: ctx.location.clone(),
|
|
|
|
display: display_content,
|
|
|
|
url,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
result
|
|
|
|
})
|
|
|
|
.unwrap(),
|
|
|
|
));
|
|
|
|
|
2024-08-05 18:40:17 +02:00
|
|
|
bindings
|
2024-08-04 10:25:51 +02:00
|
|
|
}
|
2024-07-19 11:52:12 +02:00
|
|
|
}
|
2024-08-01 16:15:10 +02:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2024-08-02 10:34:56 +02:00
|
|
|
use crate::elements::paragraph::Paragraph;
|
2024-08-01 16:15:10 +02:00
|
|
|
use crate::elements::style::Style;
|
2024-08-02 10:32:00 +02:00
|
|
|
use crate::elements::text::Text;
|
2024-08-01 16:15:10 +02:00
|
|
|
use crate::parser::langparser::LangParser;
|
2024-08-06 18:58:41 +02:00
|
|
|
use crate::parser::parser::Parser;
|
2024-08-01 16:15:10 +02:00
|
|
|
use crate::parser::source::SourceFile;
|
|
|
|
use crate::validate_document;
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parser() {
|
|
|
|
let source = Rc::new(SourceFile::with_content(
|
|
|
|
"".to_string(),
|
|
|
|
r#"
|
|
|
|
Some [link](url).
|
|
|
|
[**BOLD link**](another url)
|
|
|
|
"#
|
|
|
|
.to_string(),
|
|
|
|
None,
|
|
|
|
));
|
|
|
|
let parser = LangParser::default();
|
2024-08-06 18:58:41 +02:00
|
|
|
let doc = parser.parse(ParserState::new(&parser, None), source, None);
|
2024-08-01 16:15:10 +02:00
|
|
|
|
|
|
|
validate_document!(doc.content().borrow(), 0,
|
|
|
|
Paragraph {
|
|
|
|
Text { content == "Some " };
|
|
|
|
Link { url == "url" } { Text { content == "link" }; };
|
|
|
|
Text { content == "." };
|
|
|
|
Link { url == "another url" } {
|
|
|
|
Style;
|
|
|
|
Text { content == "BOLD link" };
|
|
|
|
Style;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
);
|
|
|
|
}
|
2024-08-04 10:25:51 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn lua() {
|
|
|
|
let source = Rc::new(SourceFile::with_content(
|
|
|
|
"".to_string(),
|
|
|
|
r#"
|
|
|
|
Some %<nml.link.push("link", "url")>%.
|
|
|
|
%<
|
|
|
|
nml.link.push("**BOLD link**", "another url")
|
|
|
|
>%
|
|
|
|
"#
|
|
|
|
.to_string(),
|
|
|
|
None,
|
|
|
|
));
|
|
|
|
let parser = LangParser::default();
|
2024-08-06 18:58:41 +02:00
|
|
|
let doc = parser.parse(ParserState::new(&parser, None), source, None);
|
2024-08-04 10:25:51 +02:00
|
|
|
|
|
|
|
validate_document!(doc.content().borrow(), 0,
|
|
|
|
Paragraph {
|
|
|
|
Text { content == "Some " };
|
|
|
|
Link { url == "url" } { Text { content == "link" }; };
|
|
|
|
Text { content == "." };
|
|
|
|
Link { url == "another url" } {
|
|
|
|
Style;
|
|
|
|
Text { content == "BOLD link" };
|
|
|
|
Style;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
);
|
|
|
|
}
|
2024-08-01 16:15:10 +02:00
|
|
|
}
|