nml/src/elements/code.rs

761 lines
19 KiB
Rust
Raw Normal View History

2024-07-25 13:13:12 +02:00
use std::collections::HashMap;
use std::ops::Range;
use std::rc::Rc;
use std::sync::Once;
use ariadne::Fmt;
use ariadne::Label;
use ariadne::Report;
use ariadne::ReportKind;
use crypto::digest::Digest;
use crypto::sha2::Sha512;
use mlua::Function;
use mlua::Lua;
use regex::Captures;
use regex::Regex;
use syntect::easy::HighlightLines;
use syntect::highlighting::ThemeSet;
use syntect::parsing::SyntaxSet;
use crate::cache::cache::Cached;
use crate::cache::cache::CachedError;
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;
2024-07-27 08:23:15 +02:00
use crate::lua::kernel::CTX;
2024-08-05 18:40:17 +02:00
use crate::parser::parser::ParserState;
2024-07-25 13:13:12 +02:00
use crate::parser::rule::RegexRule;
use crate::parser::source::Source;
use crate::parser::source::Token;
use crate::parser::util::Property;
use crate::parser::util::PropertyMapError;
use crate::parser::util::PropertyParser;
use crate::parser::util::{self};
2024-07-19 11:52:12 +02:00
use lazy_static::lazy_static;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2024-07-25 13:13:12 +02:00
enum CodeKind {
2024-07-19 11:52:12 +02:00
FullBlock,
MiniBlock,
Inline,
}
2024-07-27 08:07:06 +02:00
impl From<&CodeKind> for ElemKind {
fn from(value: &CodeKind) -> Self {
match value {
CodeKind::FullBlock | CodeKind::MiniBlock => ElemKind::Block,
CodeKind::Inline => ElemKind::Inline,
}
}
}
2024-07-19 11:52:12 +02:00
#[derive(Debug)]
2024-07-25 13:13:12 +02:00
struct Code {
2024-07-19 11:52:12 +02:00
location: Token,
block: CodeKind,
language: String,
name: Option<String>,
code: String,
theme: Option<String>,
line_offset: usize,
}
impl Code {
2024-07-25 13:13:12 +02:00
fn new(
location: Token,
block: CodeKind,
language: String,
name: Option<String>,
code: String,
theme: Option<String>,
line_offset: usize,
) -> Self {
Self {
location,
block,
language,
name,
code,
theme,
line_offset,
}
}
2024-07-19 11:52:12 +02:00
2024-07-27 08:07:06 +02:00
pub fn get_syntaxes() -> &'static SyntaxSet {
2024-07-19 11:52:12 +02:00
lazy_static! {
2024-07-25 13:13:12 +02:00
static ref syntax_set: SyntaxSet = SyntaxSet::load_defaults_newlines();
2024-07-27 08:07:06 +02:00
}
&syntax_set
}
fn highlight_html(&self, compiler: &Compiler) -> Result<String, String> {
lazy_static! {
2024-07-25 13:13:12 +02:00
static ref theme_set: ThemeSet = ThemeSet::load_defaults();
2024-07-19 11:52:12 +02:00
}
2024-07-27 08:07:06 +02:00
let syntax = match Code::get_syntaxes().find_syntax_by_name(self.language.as_str()) {
2024-07-19 11:52:12 +02:00
Some(syntax) => syntax,
2024-07-25 13:13:12 +02:00
None => {
return Err(format!(
"Unable to find syntax for language: {}",
self.language
))
}
2024-07-19 11:52:12 +02:00
};
2024-07-25 13:13:12 +02:00
let theme_string = match self.theme.as_ref() {
2024-07-19 11:52:12 +02:00
Some(theme) => theme.as_str(),
None => "base16-ocean.dark",
};
let mut h = HighlightLines::new(syntax, &theme_set.themes[theme_string]);
let mut result = String::new();
2024-07-25 13:13:12 +02:00
if self.block == CodeKind::FullBlock {
2024-07-19 11:52:12 +02:00
result += "<div class=\"code-block\">";
2024-07-25 13:13:12 +02:00
if let Some(name) = &self.name {
result += format!(
"<div class=\"code-block-title\">{}</div>",
2024-07-29 16:45:14 +02:00
Compiler::sanitize(compiler.target(), name.as_str())
2024-07-25 13:13:12 +02:00
)
.as_str();
2024-07-19 11:52:12 +02:00
}
2024-07-25 13:13:12 +02:00
result +=
format!("<div class=\"code-block-content\"><table cellspacing=\"0\">").as_str();
for (line_id, line) in self.code.split(|c| c == '\n').enumerate() {
2024-07-19 11:52:12 +02:00
result += "<tr><td class=\"code-block-gutter\">";
// Line number
2024-07-25 13:13:12 +02:00
result +=
format!("<pre><span>{}</span></pre>", line_id + self.line_offset).as_str();
2024-07-19 11:52:12 +02:00
// Code
result += "</td><td class=\"code-block-line\"><pre>";
2024-07-27 08:07:06 +02:00
match h.highlight_line(line, Code::get_syntaxes()) {
2024-07-25 13:13:12 +02:00
Err(e) => {
return Err(format!(
"Error highlighting line `{line}`: {}",
e.to_string()
))
}
2024-07-19 11:52:12 +02:00
Ok(regions) => {
2024-07-25 13:13:12 +02:00
match syntect::html::styled_line_to_highlighted_html(
&regions[..],
syntect::html::IncludeBackground::No,
) {
Err(e) => {
return Err(format!("Error highlighting code: {}", e.to_string()))
}
Ok(highlighted) => {
result += if highlighted.is_empty() {
"<br>"
} else {
highlighted.as_str()
}
}
2024-07-19 11:52:12 +02:00
}
}
}
result += "</pre></td></tr>";
}
result += "</table></div></div>";
2024-07-25 13:13:12 +02:00
} else if self.block == CodeKind::MiniBlock {
2024-07-19 11:52:12 +02:00
result += "<div class=\"code-block\"><div class=\"code-block-content\"><table cellspacing=\"0\">";
2024-07-25 13:13:12 +02:00
for line in self.code.split(|c| c == '\n') {
2024-07-19 11:52:12 +02:00
result += "<tr><td class=\"code-block-line\"><pre>";
// Code
2024-07-27 08:07:06 +02:00
match h.highlight_line(line, Code::get_syntaxes()) {
2024-07-25 13:13:12 +02:00
Err(e) => {
return Err(format!(
"Error highlighting line `{line}`: {}",
e.to_string()
))
}
2024-07-19 11:52:12 +02:00
Ok(regions) => {
2024-07-25 13:13:12 +02:00
match syntect::html::styled_line_to_highlighted_html(
&regions[..],
syntect::html::IncludeBackground::No,
) {
Err(e) => {
return Err(format!("Error highlighting code: {}", e.to_string()))
}
Ok(highlighted) => {
result += if highlighted.is_empty() {
"<br>"
} else {
highlighted.as_str()
}
}
2024-07-19 11:52:12 +02:00
}
}
}
result += "</pre></td></tr>";
}
result += "</table></div></div>";
2024-07-25 13:13:12 +02:00
} else if self.block == CodeKind::Inline {
2024-07-19 11:52:12 +02:00
result += "<a class=\"inline-code\"><code>";
2024-07-27 08:07:06 +02:00
match h.highlight_line(self.code.as_str(), Code::get_syntaxes()) {
2024-07-25 13:13:12 +02:00
Err(e) => {
return Err(format!(
"Error highlighting line `{}`: {}",
self.code,
e.to_string()
))
}
2024-07-19 11:52:12 +02:00
Ok(regions) => {
2024-07-25 13:13:12 +02:00
match syntect::html::styled_line_to_highlighted_html(
&regions[..],
syntect::html::IncludeBackground::No,
) {
Err(e) => {
return Err(format!("Error highlighting code: {}", e.to_string()))
}
Ok(highlighted) => result += highlighted.as_str(),
2024-07-19 11:52:12 +02:00
}
}
}
result += "</code></a>";
}
Ok(result)
}
}
2024-07-25 13:13:12 +02:00
impl Cached for Code {
type Key = String;
type Value = String;
2024-07-19 11:52:12 +02:00
2024-07-25 13:13:12 +02:00
fn sql_table() -> &'static str {
2024-07-19 11:52:12 +02:00
"CREATE TABLE IF NOT EXISTS cached_code (
digest TEXT PRIMARY KEY,
highlighted BLOB NOT NULL);"
2024-07-25 13:13:12 +02:00
}
2024-07-19 11:52:12 +02:00
2024-07-25 13:13:12 +02:00
fn sql_get_query() -> &'static str { "SELECT highlighted FROM cached_code WHERE digest = (?1)" }
2024-07-19 11:52:12 +02:00
2024-07-25 13:13:12 +02:00
fn sql_insert_query() -> &'static str {
2024-07-19 11:52:12 +02:00
"INSERT INTO cached_code (digest, highlighted) VALUES (?1, ?2)"
2024-07-25 13:13:12 +02:00
}
2024-07-19 11:52:12 +02:00
2024-07-25 13:13:12 +02:00
fn key(&self) -> <Self as Cached>::Key {
2024-07-19 11:52:12 +02:00
let mut hasher = Sha512::new();
hasher.input((self.block as usize).to_be_bytes().as_slice());
hasher.input((self.line_offset as usize).to_be_bytes().as_slice());
2024-07-25 13:13:12 +02:00
self.theme
.as_ref()
.map(|theme| hasher.input(theme.as_bytes()));
2024-07-19 11:52:12 +02:00
self.name.as_ref().map(|name| hasher.input(name.as_bytes()));
hasher.input(self.language.as_bytes());
hasher.input(self.code.as_bytes());
hasher.result_str()
2024-07-25 13:13:12 +02:00
}
2024-07-19 11:52:12 +02:00
}
impl Element for Code {
2024-07-25 13:13:12 +02:00
fn location(&self) -> &Token { &self.location }
2024-07-19 11:52:12 +02:00
2024-07-27 08:07:06 +02:00
fn kind(&self) -> ElemKind { (&self.block).into() }
2024-07-19 11:52:12 +02:00
2024-07-25 13:13:12 +02:00
fn element_name(&self) -> &'static str { "Code Block" }
2024-07-19 11:52:12 +02:00
2024-07-25 13:13:12 +02:00
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> {
match compiler.target() {
2024-07-19 11:52:12 +02:00
Target::HTML => {
2024-07-25 13:13:12 +02:00
static CACHE_INIT: Once = Once::new();
CACHE_INIT.call_once(|| {
if let Some(mut con) = compiler.cache() {
if let Err(e) = Code::init(&mut con) {
eprintln!("Unable to create cache table: {e}");
}
2024-07-19 11:52:12 +02:00
}
});
2024-07-25 13:13:12 +02:00
if let Some(mut con) = compiler.cache() {
match self.cached(&mut con, |s| s.highlight_html(compiler)) {
2024-07-19 11:52:12 +02:00
Ok(s) => Ok(s),
2024-07-25 13:13:12 +02:00
Err(e) => match e {
CachedError::SqlErr(e) => {
Err(format!("Querying the cache failed: {e}"))
}
CachedError::GenErr(e) => Err(e),
},
2024-07-19 11:52:12 +02:00
}
2024-07-25 13:13:12 +02:00
} else {
2024-07-19 11:52:12 +02:00
self.highlight_html(compiler)
}
}
2024-07-25 13:13:12 +02:00
Target::LATEX => {
todo!("")
}
2024-07-19 11:52:12 +02:00
}
2024-07-25 13:13:12 +02:00
}
2024-07-19 11:52:12 +02:00
}
pub struct CodeRule {
re: [Regex; 2],
properties: PropertyParser,
}
impl CodeRule {
pub fn new() -> Self {
let mut props = HashMap::new();
2024-07-25 13:13:12 +02:00
props.insert(
"line_offset".to_string(),
2024-07-19 11:52:12 +02:00
Property::new(
true,
"Line number offset".to_string(),
2024-07-25 13:13:12 +02:00
Some("1".to_string()),
),
);
2024-07-19 11:52:12 +02:00
Self {
re: [
2024-07-25 13:13:12 +02:00
Regex::new(
r"(?:^|\n)```(?:\[((?:\\.|[^\\\\])*?)\])?(.*?)(?:,(.*))?\n((?:\\(?:.|\n)|[^\\\\])*?)```",
)
.unwrap(),
Regex::new(
2024-07-29 21:28:06 +02:00
r"``(?:\[((?:\\.|[^\\\\])*?)\])?(?:(.*?),)?((?:\\(?:.|\n)|[^\\\\])*?)``",
2024-07-25 13:13:12 +02:00
)
.unwrap(),
2024-07-19 11:52:12 +02:00
],
2024-08-06 18:58:41 +02:00
properties: PropertyParser { properties: props },
2024-07-19 11:52:12 +02:00
}
}
}
2024-07-25 13:13:12 +02:00
impl RegexRule for CodeRule {
fn name(&self) -> &'static str { "Code" }
2024-07-19 11:52:12 +02:00
2024-07-25 13:13:12 +02:00
fn regexes(&self) -> &[regex::Regex] { &self.re }
2024-07-19 11:52:12 +02:00
2024-07-25 13:13:12 +02:00
fn on_regex_match<'a>(
&self,
index: usize,
2024-08-06 18:58:41 +02:00
state: &ParserState,
2024-07-25 13:13:12 +02:00
document: &'a dyn Document,
token: Token,
matches: Captures,
) -> Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>> {
2024-07-19 11:52:12 +02:00
let mut reports = vec![];
2024-07-25 13:13:12 +02:00
let properties = match matches.get(1) {
2024-07-19 11:52:12 +02:00
None => match self.properties.default() {
Ok(properties) => properties,
Err(e) => {
reports.push(
Report::build(ReportKind::Error, token.source(), token.start())
2024-07-25 13:13:12 +02:00
.with_message("Invalid code")
.with_label(
Label::new((token.source().clone(), token.range.clone()))
.with_message(format!("Code is missing properties: {e}"))
2024-08-05 18:40:17 +02:00
.with_color(state.parser.colors().error),
2024-07-25 13:13:12 +02:00
)
.finish(),
);
return reports;
}
},
2024-07-19 11:52:12 +02:00
Some(props) => {
2024-07-25 13:13:12 +02:00
let processed =
util::process_escaped('\\', "]", props.as_str().trim_start().trim_end());
match self.properties.parse(processed.as_str()) {
2024-07-19 11:52:12 +02:00
Err(e) => {
reports.push(
Report::build(ReportKind::Error, token.source(), props.start())
2024-07-25 13:13:12 +02:00
.with_message("Invalid Code Properties")
.with_label(
Label::new((token.source().clone(), props.range()))
.with_message(e)
2024-08-05 18:40:17 +02:00
.with_color(state.parser.colors().error),
2024-07-25 13:13:12 +02:00
)
.finish(),
);
2024-07-19 11:52:12 +02:00
return reports;
}
2024-07-25 13:13:12 +02:00
Ok(properties) => properties,
2024-07-19 11:52:12 +02:00
}
}
};
2024-07-25 13:13:12 +02:00
let code_lang = match matches.get(2) {
2024-07-19 11:52:12 +02:00
None => "Plain Text".to_string(),
Some(lang) => {
2024-07-27 08:07:06 +02:00
let code_lang = lang.as_str().trim_start().trim_end().to_string();
2024-07-25 13:13:12 +02:00
if code_lang.is_empty() {
2024-07-19 11:52:12 +02:00
reports.push(
Report::build(ReportKind::Error, token.source(), lang.start())
2024-07-27 08:07:06 +02:00
.with_message("Missing Code Language")
2024-07-25 13:13:12 +02:00
.with_label(
Label::new((token.source().clone(), lang.range()))
.with_message("No language specified")
2024-08-05 18:40:17 +02:00
.with_color(state.parser.colors().error),
2024-07-25 13:13:12 +02:00
)
.finish(),
);
2024-07-19 11:52:12 +02:00
return reports;
}
2024-07-27 08:07:06 +02:00
if Code::get_syntaxes()
.find_syntax_by_name(code_lang.as_str())
.is_none()
{
reports.push(
Report::build(ReportKind::Error, token.source(), lang.start())
.with_message("Unknown Code Language")
.with_label(
Label::new((token.source().clone(), lang.range()))
.with_message(format!(
"Language `{}` cannot be found",
2024-08-05 18:40:17 +02:00
code_lang.fg(state.parser.colors().info)
2024-07-27 08:07:06 +02:00
))
2024-08-05 18:40:17 +02:00
.with_color(state.parser.colors().error),
2024-07-27 08:07:06 +02:00
)
.finish(),
);
2024-07-25 13:13:12 +02:00
2024-07-27 08:07:06 +02:00
return reports;
}
2024-07-19 11:52:12 +02:00
code_lang
}
};
2024-07-25 13:13:12 +02:00
let mut code_content = if index == 0 {
util::process_escaped('\\', "```", matches.get(4).unwrap().as_str())
} else {
util::process_escaped('\\', "``", matches.get(3).unwrap().as_str())
};
if code_content.bytes().last() == Some('\n' as u8)
// Remove newline
2024-07-19 11:52:12 +02:00
{
code_content.pop();
}
2024-07-25 13:13:12 +02:00
if code_content.is_empty() {
2024-07-19 11:52:12 +02:00
reports.push(
Report::build(ReportKind::Error, token.source(), token.start())
2024-07-25 13:13:12 +02:00
.with_message("Missing code content")
.with_label(
Label::new((token.source().clone(), token.range.clone()))
.with_message("Code content cannot be empty")
2024-08-05 18:40:17 +02:00
.with_color(state.parser.colors().error),
2024-07-25 13:13:12 +02:00
)
.finish(),
);
2024-07-19 11:52:12 +02:00
return reports;
}
2024-07-25 13:13:12 +02:00
let theme = document
.get_variable("code.theme")
2024-07-23 14:04:57 +02:00
.and_then(|var| Some(var.to_string()));
2024-07-19 11:52:12 +02:00
2024-07-25 13:13:12 +02:00
if index == 0
// Block
2024-07-19 11:52:12 +02:00
{
2024-07-25 13:13:12 +02:00
let code_name = matches.get(3).and_then(|name| {
let code_name = name.as_str().trim_end().trim_start().to_string();
(!code_name.is_empty()).then_some(code_name)
});
let line_offset =
match properties.get("line_offset", |prop, value| {
value.parse::<usize>().map_err(|e| (prop, e))
}) {
Ok((_prop, offset)) => offset,
Err(e) => {
match e {
PropertyMapError::ParseError((prop, err)) => {
reports.push(
2024-07-24 09:05:57 +02:00
Report::build(ReportKind::Error, token.source(), token.start())
.with_message("Invalid Code Property")
.with_label(
Label::new((token.source().clone(), token.start()+1..token.end()))
.with_message(format!("Property `line_offset: {}` cannot be converted: {}",
2024-08-05 18:40:17 +02:00
prop.fg(state.parser.colors().info),
err.fg(state.parser.colors().error)))
.with_color(state.parser.colors().warning))
2024-07-24 09:05:57 +02:00
.finish());
2024-07-25 13:13:12 +02:00
return reports;
}
PropertyMapError::NotFoundError(err) => {
reports.push(
Report::build(ReportKind::Error, token.source(), token.start())
.with_message("Invalid Code Property")
.with_label(
Label::new((
token.source().clone(),
token.start() + 1..token.end(),
))
.with_message(format!(
"Property `{}` doesn't exist",
2024-08-05 18:40:17 +02:00
err.fg(state.parser.colors().info)
2024-07-25 13:13:12 +02:00
))
2024-08-05 18:40:17 +02:00
.with_color(state.parser.colors().warning),
2024-07-25 13:13:12 +02:00
)
.finish(),
);
return reports;
}
}
2024-07-24 09:05:57 +02:00
}
2024-07-25 13:13:12 +02:00
};
2024-08-06 18:58:41 +02:00
state.push(
2024-07-25 13:13:12 +02:00
document,
Box::new(Code::new(
token.clone(),
CodeKind::FullBlock,
code_lang,
code_name,
code_content,
theme,
line_offset,
)),
);
} else
// Maybe inline
{
let block = if code_content.contains('\n') {
CodeKind::MiniBlock
} else {
CodeKind::Inline
2024-07-19 11:52:12 +02:00
};
2024-08-06 18:58:41 +02:00
state.push(
2024-07-25 13:13:12 +02:00
document,
Box::new(Code::new(
token.clone(),
block,
code_lang,
None,
code_content,
theme,
1,
)),
);
2024-07-19 11:52:12 +02:00
}
reports
2024-07-25 13:13: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-07-27 08:23:15 +02:00
let mut bindings = vec![];
bindings.push((
"push_inline".to_string(),
lua.create_function(|_, (language, content): (String, String)| {
CTX.with_borrow(|ctx| {
ctx.as_ref().map(|ctx| {
let theme = ctx
.document
.get_variable("code.theme")
.and_then(|var| Some(var.to_string()));
2024-08-06 18:58:41 +02:00
ctx.state.push(
2024-07-27 08:23:15 +02:00
ctx.document,
Box::new(Code {
location: ctx.location.clone(),
block: CodeKind::Inline,
language,
name: None,
code: content,
theme,
line_offset: 1,
}),
);
})
});
Ok(())
})
.unwrap(),
));
bindings.push((
"push_miniblock".to_string(),
lua.create_function(
|_, (language, content, line_offset): (String, String, Option<usize>)| {
CTX.with_borrow(|ctx| {
ctx.as_ref().map(|ctx| {
let theme = ctx
.document
.get_variable("code.theme")
.and_then(|var| Some(var.to_string()));
2024-08-06 18:58:41 +02:00
ctx.state.push(
2024-07-27 08:23:15 +02:00
ctx.document,
Box::new(Code {
location: ctx.location.clone(),
block: CodeKind::MiniBlock,
language,
name: None,
code: content,
theme,
line_offset: line_offset.unwrap_or(1),
}),
);
})
});
Ok(())
},
)
.unwrap(),
));
bindings.push((
"push_block".to_string(),
lua.create_function(
2024-07-29 16:45:14 +02:00
|_,
(language, name, content, line_offset): (
String,
Option<String>,
String,
Option<usize>,
)| {
2024-07-27 08:23:15 +02:00
CTX.with_borrow(|ctx| {
ctx.as_ref().map(|ctx| {
let theme = ctx
.document
.get_variable("code.theme")
.and_then(|var| Some(var.to_string()));
2024-08-06 18:58:41 +02:00
ctx.state.push(
2024-07-27 08:23:15 +02:00
ctx.document,
Box::new(Code {
location: ctx.location.clone(),
block: CodeKind::FullBlock,
language,
name,
code: content,
theme,
line_offset: line_offset.unwrap_or(1),
}),
);
})
});
Ok(())
},
)
.unwrap(),
));
2024-08-05 18:40:17 +02:00
bindings
2024-07-27 08:23:15 +02:00
}
2024-07-19 11:52:12 +02:00
}
2024-07-27 08:07:06 +02:00
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::langparser::LangParser;
2024-08-06 18:58:41 +02:00
use crate::parser::parser::Parser;
2024-07-27 08:07:06 +02:00
use crate::parser::source::SourceFile;
#[test]
fn code_block() {
let source = Rc::new(SourceFile::with_content(
"".to_string(),
r#"
```[line_offset=32] C, Some Code...
static int INT32_MIN = 0x80000000;
```
2024-07-27 08:23:15 +02:00
%<nml.code.push_block("Lua", "From Lua", "print(\"Hello, World!\")", nil)>%
2024-08-01 14:23:41 +02:00
``Rust,
2024-07-27 08:07:06 +02:00
fn fact(n: usize) -> usize
{
match n
{
0 | 1 => 1,
_ => n * fact(n-1)
}
}
2024-07-27 08:23:15 +02:00
``
%<nml.code.push_miniblock("Bash", "NUM=$(($RANDOM % 10))", 18)>%
"#
2024-07-27 08:07:06 +02:00
.to_string(),
None,
));
let parser = LangParser::default();
2024-08-05 18:40:17 +02:00
let doc = parser.parse(ParserState::new(&parser, None), source, None);
2024-07-27 08:07:06 +02:00
let borrow = doc.content().borrow();
let found = borrow
.iter()
.filter_map(|e| e.downcast_ref::<Code>())
.collect::<Vec<_>>();
assert_eq!(found[0].block, CodeKind::FullBlock);
assert_eq!(found[0].language, "C");
assert_eq!(found[0].name, Some("Some Code...".to_string()));
assert_eq!(found[0].code, "static int INT32_MIN = 0x80000000;");
assert_eq!(found[0].line_offset, 32);
2024-07-27 08:23:15 +02:00
assert_eq!(found[1].block, CodeKind::FullBlock);
assert_eq!(found[1].language, "Lua");
assert_eq!(found[1].name, Some("From Lua".to_string()));
assert_eq!(found[1].code, "print(\"Hello, World!\")");
2024-07-27 08:07:06 +02:00
assert_eq!(found[1].line_offset, 1);
2024-07-27 08:23:15 +02:00
assert_eq!(found[2].block, CodeKind::MiniBlock);
assert_eq!(found[2].language, "Rust");
assert_eq!(found[2].name, None);
assert_eq!(found[2].code, "fn fact(n: usize) -> usize\n{\n\tmatch n\n\t{\n\t\t0 | 1 => 1,\n\t\t_ => n * fact(n-1)\n\t}\n}");
assert_eq!(found[2].line_offset, 1);
assert_eq!(found[3].block, CodeKind::MiniBlock);
assert_eq!(found[3].language, "Bash");
assert_eq!(found[3].name, None);
assert_eq!(found[3].code, "NUM=$(($RANDOM % 10))");
assert_eq!(found[3].line_offset, 18);
2024-07-27 08:07:06 +02:00
}
#[test]
fn code_inline() {
let source = Rc::new(SourceFile::with_content(
"".to_string(),
r#"
``C, int fact(int n)``
``Plain Text, Text in a code block!``
2024-07-27 08:23:15 +02:00
%<nml.code.push_inline("C++", "std::vector<std::vector<int>> u;")>%
2024-07-27 08:07:06 +02:00
"#
.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-07-27 08:07:06 +02:00
let borrow = doc.content().borrow();
let found = borrow
.first()
.unwrap()
.as_container()
.unwrap()
.contained()
.iter()
.filter_map(|e| e.downcast_ref::<Code>())
.collect::<Vec<_>>();
assert_eq!(found[0].block, CodeKind::Inline);
assert_eq!(found[0].language, "C");
assert_eq!(found[0].name, None);
assert_eq!(found[0].code, "int fact(int n)");
assert_eq!(found[0].line_offset, 1);
assert_eq!(found[1].block, CodeKind::Inline);
assert_eq!(found[1].language, "Plain Text");
assert_eq!(found[1].name, None);
assert_eq!(found[1].code, "Text in a code block!");
assert_eq!(found[1].line_offset, 1);
2024-07-27 08:23:15 +02:00
assert_eq!(found[2].block, CodeKind::Inline);
assert_eq!(found[2].language, "C++");
assert_eq!(found[2].name, None);
assert_eq!(found[2].code, "std::vector<std::vector<int>> u;");
assert_eq!(found[2].line_offset, 1);
2024-07-27 08:07:06 +02:00
}
}