mod collectors;
mod visitor;
pub use visitor::*;
use crate::notebook::CellOutput;
use crate::parsers::shortcodes::Argument;
use anyhow::Context;
use pulldown_cmark::{HeadingLevel, LinkType, Options, Parser};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Clone, Debug)]
pub enum Inline {
Text(String),
Emphasis(Vec<Inline>),
Strong(Vec<Inline>),
Strikethrough(Vec<Inline>),
Code(String),
SoftBreak,
HardBreak,
Rule,
Image(LinkType, String, String, Vec<Inline>),
Link(LinkType, String, String, Vec<Inline>),
Html(String),
Math {
source: String,
display_block: bool,
trailing_space: bool,
},
Shortcode(Shortcode),
}
fn vec_inline_to_string(vec: &[Inline]) -> String {
vec.iter().map(|item| item.to_string()).collect()
}
impl ToString for Inline {
fn to_string(&self) -> String {
match self {
Inline::Text(s) => s.clone(),
Inline::Emphasis(inner) => vec_inline_to_string(inner),
Inline::Strong(inner) => vec_inline_to_string(inner),
Inline::Strikethrough(inner) => vec_inline_to_string(inner),
Inline::Code(s) => s.clone(),
Inline::SoftBreak => String::default(),
Inline::HardBreak => String::default(),
Inline::Rule => String::default(),
Inline::Html(s) => s.to_string(),
Inline::Math { source, .. } => source.to_string(),
Inline::Shortcode(s) => s.to_string(),
_ => String::default(),
}
}
}
impl ToString for Shortcode {
fn to_string(&self) -> String {
match self {
Shortcode::Inline(base) => base.to_string(),
Shortcode::Block(base, _) => base.to_string(),
}
}
}
impl ToString for ShortcodeBase {
fn to_string(&self) -> String {
format!("{}#{}", self.name, self.id.clone().unwrap_or_default(),)
}
}
#[derive(Clone, Debug)]
pub struct Ast(pub Vec<Block>);
#[derive(Clone, Debug, Default)]
pub struct CodeAttributes {
#[allow(unused)]
pub(crate) editable: bool,
#[allow(unused)]
pub(crate) fold: bool,
}
#[derive(Clone, Debug)]
pub enum CodeOutput {
Image(String),
Svg(String),
Json(HashMap<String, Value>),
Html(String),
Javascript(String),
}
#[derive(Clone, Debug)]
pub enum Block {
Heading {
lvl: HeadingLevel,
id: Option<String>,
classes: Vec<String>,
inner: Vec<Inline>,
},
Plain(Vec<Inline>),
Paragraph(Vec<Inline>),
BlockQuote(Vec<Inline>),
CodeBlock {
source: String,
reference: Option<String>,
attr: CodeAttributes,
tags: Option<Vec<String>>,
outputs: Vec<CellOutput>,
},
List(Option<u64>, Vec<Block>),
ListItem(Vec<Block>),
}
#[derive(Debug, Clone)]
pub enum Shortcode {
Inline(ShortcodeBase),
Block(ShortcodeBase, Vec<Block>),
}
pub(crate) fn str_to_blocks(input: &str) -> anyhow::Result<Ast> {
Ast::make_from_iter(Parser::new_ext(input, Options::all()))
.context("when parsing markdown source")
}
#[derive(Debug, Clone)]
pub struct ShortcodeBase {
pub(crate) name: String,
pub(crate) id: Option<String>,
pub(crate) num: usize,
pub(crate) parameters: Vec<Argument<Vec<Block>>>,
}
pub(crate) enum ShortcodeIdx {
Inline(usize, usize),
Block {
def: (usize, usize),
end: (usize, usize),
},
}
fn extract_block(start: usize, input: &str) -> Option<ShortcodeIdx> {
let end = start + input[start..].find("%}")?;
let mut level = 1;
let mut cur_start = end;
let mut end_block = end;
while level > 0 {
let new_start = input[(cur_start + 2)..]
.find("{%")
.map(|s| s + cur_start + 2);
end_block = cur_start + 2 + input[cur_start + 2..].find("{% end")?;
match new_start {
Some(s) => {
if s < end_block {
level += 1;
cur_start = s + 2;
} else {
level -= 1;
cur_start = end_block + 7;
}
}
None => {
level -= 1;
cur_start = end_block + 7;
}
}
}
let end_of_block = end_block + 6 + input[end_block + 6..].find("%}")?;
Some(ShortcodeIdx::Block {
def: (start, end),
end: (end_block, end_of_block),
})
}
fn extract_inline(start: usize, input: &str) -> Option<ShortcodeIdx> {
let mut level = 1;
let mut end = start;
let mut cur_start = start;
while level > 0 {
let new_start = input[(cur_start + 2)..]
.find("{{")
.map(|s| s + cur_start + 2);
end = cur_start + 2 + input[(cur_start + 2)..].find("}}")?;
match new_start {
Some(s) => {
if s < end {
level += 1;
cur_start = s + 2;
} else {
level -= 1;
cur_start = end + 2;
}
}
None => {
level -= 1;
cur_start = end + 2;
}
}
}
Some(ShortcodeIdx::Inline(start, end))
}
pub(crate) fn find_shortcode(input: &str) -> Option<ShortcodeIdx> {
let start_inline = input.find("{{");
let start_block = input.find("{%");
match start_inline {
None => start_block.and_then(|start| extract_block(start, input)),
Some(inline_start_idx) => match start_block {
None => extract_inline(inline_start_idx, input),
Some(block_start_idx) => {
if inline_start_idx < block_start_idx {
extract_inline(inline_start_idx, input)
} else {
extract_block(block_start_idx, input)
}
}
},
}
}