Refactor TOC
This commit is contained in:
parent
161dd44b63
commit
0f9cf7057a
9 changed files with 341 additions and 111 deletions
|
@ -1,6 +1,6 @@
|
||||||
@import ../template.nml
|
@import ../template.nml
|
||||||
@nav.previous = Code
|
@nav.previous = Code
|
||||||
%<make_doc({"Blocks"}, "Blockquotes", "Blockquotes")>%
|
%<make_doc({"Code"}, "Code", "Code")>%
|
||||||
|
|
||||||
# Blockquotes
|
# Blockquotes
|
||||||
|
|
||||||
|
|
|
@ -30,3 +30,5 @@ end
|
||||||
"link_pos": "Before",
|
"link_pos": "Before",
|
||||||
"link": ["", "🔗 ", " "]
|
"link": ["", "🔗 ", " "]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#+TABLE_OF_CONTENT Table of Content
|
||||||
|
|
|
@ -9,8 +9,6 @@ use crate::document::document::CrossReference;
|
||||||
use crate::document::document::Document;
|
use crate::document::document::Document;
|
||||||
use crate::document::document::ElemReference;
|
use crate::document::document::ElemReference;
|
||||||
use crate::document::variable::Variable;
|
use crate::document::variable::Variable;
|
||||||
use crate::elements::section::section_kind;
|
|
||||||
use crate::elements::section::Section;
|
|
||||||
|
|
||||||
use super::postprocess::PostProcess;
|
use super::postprocess::PostProcess;
|
||||||
|
|
||||||
|
@ -209,7 +207,6 @@ impl<'a> Compiler<'a> {
|
||||||
}
|
}
|
||||||
result += r#"</head><body><div class="layout">"#;
|
result += r#"</head><body><div class="layout">"#;
|
||||||
|
|
||||||
// TODO: TOC
|
|
||||||
// TODO: Author, Date, Title, Div
|
// TODO: Author, Date, Title, Div
|
||||||
}
|
}
|
||||||
Target::LATEX => {}
|
Target::LATEX => {}
|
||||||
|
@ -228,93 +225,6 @@ impl<'a> Compiler<'a> {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toc(&self, document: &dyn Document) -> String {
|
|
||||||
let toc_title = if let Some(title) = document.get_variable("toc.title") {
|
|
||||||
title
|
|
||||||
} else {
|
|
||||||
return String::new();
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut result = String::new();
|
|
||||||
let mut sections: Vec<(&Section, usize)> = vec![];
|
|
||||||
// Find last section with given depth
|
|
||||||
fn last_matching(depth: usize, sections: &Vec<(&Section, usize)>) -> Option<usize> {
|
|
||||||
for (idx, (section, _number)) in sections.iter().rev().enumerate() {
|
|
||||||
if section.depth < depth {
|
|
||||||
return None;
|
|
||||||
} else if section.depth == depth {
|
|
||||||
return Some(sections.len() - idx - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
let content_borrow = document.content().borrow();
|
|
||||||
for elem in content_borrow.iter() {
|
|
||||||
if let Some(section) = elem.downcast_ref::<Section>() {
|
|
||||||
if section.kind & section_kind::NO_TOC != 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let last = last_matching(section.depth, §ions);
|
|
||||||
if let Some(last) = last {
|
|
||||||
if sections[last].0.kind & section_kind::NO_NUMBER != 0 {
|
|
||||||
sections.push((section, sections[last].1));
|
|
||||||
} else {
|
|
||||||
sections.push((section, sections[last].1 + 1))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sections.push((section, 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.target() {
|
|
||||||
Target::HTML => {
|
|
||||||
let match_depth = |current: usize, target: usize| -> String {
|
|
||||||
let mut result = String::new();
|
|
||||||
for _ in current..target {
|
|
||||||
result += "<ol>";
|
|
||||||
}
|
|
||||||
for _ in target..current {
|
|
||||||
result += "</ol>";
|
|
||||||
}
|
|
||||||
result
|
|
||||||
};
|
|
||||||
result += "<div class=\"toc\">";
|
|
||||||
result += format!(
|
|
||||||
"<span>{}</span>",
|
|
||||||
Compiler::sanitize(self.target(), toc_title.to_string())
|
|
||||||
)
|
|
||||||
.as_str();
|
|
||||||
let mut current_depth = 0;
|
|
||||||
for (section, number) in sections {
|
|
||||||
result += match_depth(current_depth, section.depth).as_str();
|
|
||||||
if section.kind & section_kind::NO_NUMBER != 0 {
|
|
||||||
result += format!(
|
|
||||||
"<li><a href=\"#{}\">{}</a></li>",
|
|
||||||
Compiler::refname(self.target(), section.title.as_str()),
|
|
||||||
Compiler::sanitize(self.target(), section.title.as_str())
|
|
||||||
)
|
|
||||||
.as_str();
|
|
||||||
} else {
|
|
||||||
result += format!(
|
|
||||||
"<li value=\"{number}\"><a href=\"#{}\">{}</a></li>",
|
|
||||||
Compiler::refname(self.target(), section.title.as_str()),
|
|
||||||
Compiler::sanitize(self.target(), section.title.as_str())
|
|
||||||
)
|
|
||||||
.as_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
current_depth = section.depth;
|
|
||||||
}
|
|
||||||
match_depth(current_depth, 0);
|
|
||||||
result += "</div>";
|
|
||||||
}
|
|
||||||
_ => todo!(""),
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compile(&self, document: &dyn Document) -> (CompiledDocument, PostProcess) {
|
pub fn compile(&self, document: &dyn Document) -> (CompiledDocument, PostProcess) {
|
||||||
let borrow = document.content().borrow();
|
let borrow = document.content().borrow();
|
||||||
|
|
||||||
|
@ -324,9 +234,6 @@ impl<'a> Compiler<'a> {
|
||||||
// Body
|
// Body
|
||||||
let mut body = r#"<div class="content">"#.to_string();
|
let mut body = r#"<div class="content">"#.to_string();
|
||||||
|
|
||||||
// Table of content
|
|
||||||
body += self.toc(document).as_str();
|
|
||||||
|
|
||||||
for i in 0..borrow.len() {
|
for i in 0..borrow.len() {
|
||||||
let elem = &borrow[i];
|
let elem = &borrow[i];
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use ariadne::Fmt;
|
use ariadne::Fmt;
|
||||||
|
@ -24,9 +23,7 @@ use crate::parser::parser::ParserState;
|
||||||
use crate::parser::reports::macros::*;
|
use crate::parser::reports::macros::*;
|
||||||
use crate::parser::reports::*;
|
use crate::parser::reports::*;
|
||||||
use crate::parser::rule::RegexRule;
|
use crate::parser::rule::RegexRule;
|
||||||
use crate::parser::source::Source;
|
|
||||||
use crate::parser::source::Token;
|
use crate::parser::source::Token;
|
||||||
use crate::parser::source::VirtualSource;
|
|
||||||
use crate::parser::util;
|
use crate::parser::util;
|
||||||
use crate::parser::util::parse_paragraph;
|
use crate::parser::util::parse_paragraph;
|
||||||
use crate::parser::util::Property;
|
use crate::parser::util::Property;
|
||||||
|
@ -451,21 +448,33 @@ impl RegexRule for MediaRule {
|
||||||
.ok()
|
.ok()
|
||||||
.map(|(_, value)| value);
|
.map(|(_, value)| value);
|
||||||
|
|
||||||
if let Some((sems, tokens)) =
|
if let Some((sems, tokens)) = Semantics::from_source(token.source(), &state.shared.lsp) {
|
||||||
Semantics::from_source(token.source(), &state.shared.lsp)
|
sems.add(
|
||||||
{
|
matches.get(0).unwrap().start()..matches.get(0).unwrap().start() + 1,
|
||||||
sems.add(matches.get(0).unwrap().start()..matches.get(0).unwrap().start()+1, tokens.media_sep);
|
tokens.media_sep,
|
||||||
|
);
|
||||||
// Refname
|
// Refname
|
||||||
sems.add(matches.get(0).unwrap().start()+1..matches.get(0).unwrap().start()+2, tokens.media_refname_sep);
|
sems.add(
|
||||||
|
matches.get(0).unwrap().start() + 1..matches.get(0).unwrap().start() + 2,
|
||||||
|
tokens.media_refname_sep,
|
||||||
|
);
|
||||||
sems.add(matches.get(1).unwrap().range(), tokens.media_refname);
|
sems.add(matches.get(1).unwrap().range(), tokens.media_refname);
|
||||||
sems.add(matches.get(1).unwrap().end()..matches.get(1).unwrap().end()+1, tokens.media_refname_sep);
|
sems.add(
|
||||||
|
matches.get(1).unwrap().end()..matches.get(1).unwrap().end() + 1,
|
||||||
|
tokens.media_refname_sep,
|
||||||
|
);
|
||||||
// Uri
|
// Uri
|
||||||
sems.add(matches.get(2).unwrap().start()-1..matches.get(2).unwrap().start(), tokens.media_uri_sep);
|
sems.add(
|
||||||
|
matches.get(2).unwrap().start() - 1..matches.get(2).unwrap().start(),
|
||||||
|
tokens.media_uri_sep,
|
||||||
|
);
|
||||||
sems.add(matches.get(2).unwrap().range(), tokens.media_uri);
|
sems.add(matches.get(2).unwrap().range(), tokens.media_uri);
|
||||||
sems.add(matches.get(2).unwrap().end()..matches.get(2).unwrap().end()+1, tokens.media_uri_sep);
|
sems.add(
|
||||||
|
matches.get(2).unwrap().end()..matches.get(2).unwrap().end() + 1,
|
||||||
|
tokens.media_uri_sep,
|
||||||
|
);
|
||||||
// Props
|
// Props
|
||||||
if let Some(props) = matches.get(3)
|
if let Some(props) = matches.get(3) {
|
||||||
{
|
|
||||||
sems.add(props.start() - 1..props.start(), tokens.media_props_sep);
|
sems.add(props.start() - 1..props.start(), tokens.media_props_sep);
|
||||||
sems.add(props.range(), tokens.media_props);
|
sems.add(props.range(), tokens.media_props);
|
||||||
sems.add(props.end()..props.end() + 1, tokens.media_props_sep);
|
sems.add(props.end()..props.end() + 1, tokens.media_props_sep);
|
||||||
|
@ -474,7 +483,13 @@ impl RegexRule for MediaRule {
|
||||||
|
|
||||||
let description = match matches.get(4) {
|
let description = match matches.get(4) {
|
||||||
Some(content) => {
|
Some(content) => {
|
||||||
let source = escape_source(token.source(), content.range(), format!("Media[{refname}] description"), '\\', "\n");
|
let source = escape_source(
|
||||||
|
token.source(),
|
||||||
|
content.range(),
|
||||||
|
format!("Media[{refname}] description"),
|
||||||
|
'\\',
|
||||||
|
"\n",
|
||||||
|
);
|
||||||
if source.content().is_empty() {
|
if source.content().is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
@ -536,6 +551,8 @@ impl RegexRule for MediaRule {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::parser::langparser::LangParser;
|
use crate::parser::langparser::LangParser;
|
||||||
use crate::parser::parser::Parser;
|
use crate::parser::parser::Parser;
|
||||||
use crate::parser::source::SourceFile;
|
use crate::parser::source::SourceFile;
|
||||||
|
|
|
@ -18,3 +18,4 @@ pub mod style;
|
||||||
pub mod tex;
|
pub mod tex;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
pub mod variable;
|
pub mod variable;
|
||||||
|
pub mod toc;
|
||||||
|
|
|
@ -151,7 +151,7 @@ static STATE_NAME: &str = "elements.style";
|
||||||
impl RegexRule for StyleRule {
|
impl RegexRule for StyleRule {
|
||||||
fn name(&self) -> &'static str { "Style" }
|
fn name(&self) -> &'static str { "Style" }
|
||||||
|
|
||||||
fn previous(&self) -> Option<&'static str> { Some("Layout") }
|
fn previous(&self) -> Option<&'static str> { Some("Toc") }
|
||||||
|
|
||||||
fn regexes(&self) -> &[regex::Regex] { &self.re }
|
fn regexes(&self) -> &[regex::Regex] { &self.re }
|
||||||
|
|
||||||
|
|
294
src/elements/toc.rs
Normal file
294
src/elements/toc.rs
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
use regex::Captures;
|
||||||
|
use regex::Regex;
|
||||||
|
use regex::RegexBuilder;
|
||||||
|
|
||||||
|
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::elements::section::section_kind;
|
||||||
|
use crate::elements::section::Section;
|
||||||
|
use crate::lsp::semantic::Semantics;
|
||||||
|
use crate::lua::kernel::CTX;
|
||||||
|
use crate::parser::parser::ParseMode;
|
||||||
|
use crate::parser::parser::ParserState;
|
||||||
|
use crate::parser::reports::Report;
|
||||||
|
use crate::parser::rule::RegexRule;
|
||||||
|
use crate::parser::source::Token;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Toc {
|
||||||
|
pub(self) location: Token,
|
||||||
|
pub(self) title: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for Toc {
|
||||||
|
fn location(&self) -> &Token { &self.location }
|
||||||
|
fn kind(&self) -> ElemKind { ElemKind::Block }
|
||||||
|
fn element_name(&self) -> &'static str { "Toc" }
|
||||||
|
fn compile(
|
||||||
|
&self,
|
||||||
|
compiler: &Compiler,
|
||||||
|
document: &dyn Document,
|
||||||
|
_cursor: usize,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut sections: Vec<(&Section, usize)> = vec![];
|
||||||
|
// Find last section with given depth
|
||||||
|
fn last_matching(depth: usize, sections: &Vec<(&Section, usize)>) -> Option<usize> {
|
||||||
|
for (idx, (section, _number)) in sections.iter().rev().enumerate() {
|
||||||
|
if section.depth < depth {
|
||||||
|
return None;
|
||||||
|
} else if section.depth == depth {
|
||||||
|
return Some(sections.len() - idx - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
let content_borrow = document.content().borrow();
|
||||||
|
for elem in content_borrow.iter() {
|
||||||
|
if let Some(section) = elem.downcast_ref::<Section>() {
|
||||||
|
if section.kind & section_kind::NO_TOC != 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let last = last_matching(section.depth, §ions);
|
||||||
|
if let Some(last) = last {
|
||||||
|
if sections[last].0.kind & section_kind::NO_NUMBER != 0 {
|
||||||
|
sections.push((section, sections[last].1));
|
||||||
|
} else {
|
||||||
|
sections.push((section, sections[last].1 + 1))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sections.push((section, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match compiler.target() {
|
||||||
|
Target::HTML => {
|
||||||
|
let match_depth = |current: usize, target: usize| -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
for _ in current..target {
|
||||||
|
result += "<ol>";
|
||||||
|
}
|
||||||
|
for _ in target..current {
|
||||||
|
result += "</ol>";
|
||||||
|
}
|
||||||
|
result
|
||||||
|
};
|
||||||
|
result += "<div class=\"toc\">";
|
||||||
|
result += format!(
|
||||||
|
"<span>{}</span>",
|
||||||
|
Compiler::sanitize(
|
||||||
|
compiler.target(),
|
||||||
|
self.title.as_ref().unwrap_or(&String::new())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.as_str();
|
||||||
|
let mut current_depth = 0;
|
||||||
|
for (section, number) in sections {
|
||||||
|
result += match_depth(current_depth, section.depth).as_str();
|
||||||
|
if section.kind & section_kind::NO_NUMBER != 0 {
|
||||||
|
result += format!(
|
||||||
|
"<li><a href=\"#{}\">{}</a></li>",
|
||||||
|
Compiler::refname(compiler.target(), section.title.as_str()),
|
||||||
|
Compiler::sanitize(compiler.target(), section.title.as_str())
|
||||||
|
)
|
||||||
|
.as_str();
|
||||||
|
} else {
|
||||||
|
result += format!(
|
||||||
|
"<li value=\"{number}\"><a href=\"#{}\">{}</a></li>",
|
||||||
|
Compiler::refname(compiler.target(), section.title.as_str()),
|
||||||
|
Compiler::sanitize(compiler.target(), section.title.as_str())
|
||||||
|
)
|
||||||
|
.as_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
current_depth = section.depth;
|
||||||
|
}
|
||||||
|
match_depth(current_depth, 0);
|
||||||
|
result += "</div>";
|
||||||
|
}
|
||||||
|
_ => todo!(""),
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[auto_registry::auto_registry(registry = "rules", path = "crate::elements::toc")]
|
||||||
|
pub struct TocRule {
|
||||||
|
re: [Regex; 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TocRule {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
re: [
|
||||||
|
RegexBuilder::new(r"(?:^|\n)(?:[^\S\n]*)#\+TABLE_OF_CONTENT(.*)")
|
||||||
|
.multi_line(true)
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegexRule for TocRule {
|
||||||
|
fn name(&self) -> &'static str { "Toc" }
|
||||||
|
|
||||||
|
fn previous(&self) -> Option<&'static str> { Some("Layout") }
|
||||||
|
|
||||||
|
fn regexes(&self) -> &[regex::Regex] { &self.re }
|
||||||
|
|
||||||
|
fn enabled(&self, mode: &ParseMode, _id: usize) -> bool { !mode.paragraph_only }
|
||||||
|
|
||||||
|
fn on_regex_match(
|
||||||
|
&self,
|
||||||
|
_index: usize,
|
||||||
|
state: &ParserState,
|
||||||
|
document: &dyn Document,
|
||||||
|
token: Token,
|
||||||
|
matches: Captures,
|
||||||
|
) -> Vec<Report> {
|
||||||
|
let mut reports = vec![];
|
||||||
|
|
||||||
|
let name = matches.get(1).unwrap().as_str().trim_start().trim_end();
|
||||||
|
|
||||||
|
state.push(
|
||||||
|
document,
|
||||||
|
Box::new(Toc {
|
||||||
|
location: token.clone(),
|
||||||
|
title: (!name.is_empty()).then_some(name.to_string()),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some((sems, tokens)) = Semantics::from_source(token.source(), &state.shared.lsp) {
|
||||||
|
let start = matches
|
||||||
|
.get(0)
|
||||||
|
.map(|m| m.start() + token.source().content()[m.start()..].find('#').unwrap())
|
||||||
|
.unwrap();
|
||||||
|
sems.add(start..start + 2, tokens.toc_sep);
|
||||||
|
sems.add(
|
||||||
|
start + 2..start + 2 + "TABLE_OF_CONTENT".len(),
|
||||||
|
tokens.toc_token,
|
||||||
|
);
|
||||||
|
sems.add(matches.get(1).unwrap().range(), tokens.toc_title);
|
||||||
|
}
|
||||||
|
|
||||||
|
reports
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_bindings<'lua>(&self, lua: &'lua mlua::Lua) -> Vec<(String, mlua::Function<'lua>)> {
|
||||||
|
let mut bindings = vec![];
|
||||||
|
bindings.push((
|
||||||
|
"push".to_string(),
|
||||||
|
lua.create_function(|_, title: Option<String>| {
|
||||||
|
CTX.with_borrow(|ctx| {
|
||||||
|
ctx.as_ref().map(|ctx| {
|
||||||
|
ctx.state.push(
|
||||||
|
ctx.document,
|
||||||
|
Box::new(Toc {
|
||||||
|
location: ctx.location.clone(),
|
||||||
|
title,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
));
|
||||||
|
bindings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::parser::langparser::LangParser;
|
||||||
|
use crate::parser::parser::Parser;
|
||||||
|
use crate::parser::source::SourceFile;
|
||||||
|
use crate::validate_document;
|
||||||
|
use crate::validate_semantics;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser() {
|
||||||
|
let source = Rc::new(SourceFile::with_content(
|
||||||
|
"".to_string(),
|
||||||
|
r#"
|
||||||
|
#+TABLE_OF_CONTENT TOC
|
||||||
|
# Section1
|
||||||
|
## SubSection
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
let parser = LangParser::default();
|
||||||
|
let (doc, _) = parser.parse(
|
||||||
|
ParserState::new(&parser, None),
|
||||||
|
source,
|
||||||
|
None,
|
||||||
|
ParseMode::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
validate_document!(doc.content().borrow(), 0,
|
||||||
|
Toc { title == Some("TOC".to_string()) };
|
||||||
|
Section;
|
||||||
|
Section;
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lua() {
|
||||||
|
let source = Rc::new(SourceFile::with_content(
|
||||||
|
"".to_string(),
|
||||||
|
r#"
|
||||||
|
%<nml.toc.push("TOC")>%
|
||||||
|
%<nml.toc.push()>%
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
let parser = LangParser::default();
|
||||||
|
let (doc, _) = parser.parse(
|
||||||
|
ParserState::new(&parser, None),
|
||||||
|
source,
|
||||||
|
None,
|
||||||
|
ParseMode::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
validate_document!(doc.content().borrow(), 0,
|
||||||
|
Toc { title == Some("TOC".to_string()) };
|
||||||
|
Toc { title == Option::<String>::None };
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn semantic() {
|
||||||
|
let source = Rc::new(SourceFile::with_content(
|
||||||
|
"".to_string(),
|
||||||
|
r#"
|
||||||
|
#+TABLE_OF_CONTENT TOC
|
||||||
|
"#
|
||||||
|
.to_string(),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
let parser = LangParser::default();
|
||||||
|
let (_, state) = parser.parse(
|
||||||
|
ParserState::new_with_semantics(&parser, None),
|
||||||
|
source.clone(),
|
||||||
|
None,
|
||||||
|
ParseMode::default(),
|
||||||
|
);
|
||||||
|
validate_semantics!(state, source.clone(), 0,
|
||||||
|
toc_sep { delta_line == 1, delta_start == 0, length == 2 };
|
||||||
|
toc_token { delta_line == 0, delta_start == 2, length == 16 };
|
||||||
|
toc_title { delta_line == 0, delta_start == 16, length == 4 };
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -175,6 +175,10 @@ pub struct Tokens {
|
||||||
pub layout_props: (u32, u32),
|
pub layout_props: (u32, u32),
|
||||||
pub layout_type: (u32, u32),
|
pub layout_type: (u32, u32),
|
||||||
|
|
||||||
|
pub toc_sep: (u32, u32),
|
||||||
|
pub toc_token: (u32, u32),
|
||||||
|
pub toc_title: (u32, u32),
|
||||||
|
|
||||||
pub media_sep: (u32, u32),
|
pub media_sep: (u32, u32),
|
||||||
pub media_refname_sep: (u32, u32),
|
pub media_refname_sep: (u32, u32),
|
||||||
pub media_refname: (u32, u32),
|
pub media_refname: (u32, u32),
|
||||||
|
@ -271,6 +275,10 @@ impl Tokens {
|
||||||
layout_props: token!("enum"),
|
layout_props: token!("enum"),
|
||||||
layout_type: token!("function"),
|
layout_type: token!("function"),
|
||||||
|
|
||||||
|
toc_sep: token!("number"),
|
||||||
|
toc_token: token!("number"),
|
||||||
|
toc_title: token!("function"),
|
||||||
|
|
||||||
media_sep: token!("macro"),
|
media_sep: token!("macro"),
|
||||||
media_refname_sep: token!("macro"),
|
media_refname_sep: token!("macro"),
|
||||||
media_refname: token!("enum"),
|
media_refname: token!("enum"),
|
||||||
|
|
|
@ -224,6 +224,7 @@ mod tests {
|
||||||
"Graphviz",
|
"Graphviz",
|
||||||
"Media",
|
"Media",
|
||||||
"Layout",
|
"Layout",
|
||||||
|
"Toc",
|
||||||
"Style",
|
"Style",
|
||||||
"Custom Style",
|
"Custom Style",
|
||||||
"Section",
|
"Section",
|
||||||
|
|
Loading…
Reference in a new issue