use crate::markdown::ast::parsers::hooks::{AstParser, BlockParseContext, InlineParseContext};
use crate::markdown::ast::parsers::visitor::{AstVisitor, InlineVisitControl, VisitControl};
use crate::markdown::ast::tokens::token::{Block, Inline};
#[cfg(feature = "npm")]
use napi::bindgen_prelude::{Env, Function, FunctionRef};
#[cfg(feature = "npm")]
use napi_derive::napi;
#[cfg_attr(feature = "npm", napi(object))]
#[derive(Debug, Default, Clone)]
pub struct VisitorControl {
pub insert_before: Option<Vec<Block>>,
pub insert_after: Option<Vec<Block>>,
pub replace_with: Option<Vec<Block>>,
pub remove: Option<bool>,
pub recurse: Option<bool>,
}
impl From<VisitorControl> for VisitControl {
fn from(js: VisitorControl) -> Self {
VisitControl {
insert_before: js.insert_before.unwrap_or_default(),
insert_after: js.insert_after.unwrap_or_default(),
replace_with: js.replace_with,
remove: js.remove.unwrap_or(false),
recurse: js.recurse.unwrap_or(false),
}
}
}
#[cfg_attr(feature = "npm", napi(object))]
#[derive(Debug, Default, Clone)]
pub struct InlineVisitorControl {
pub insert_before: Option<Vec<Inline>>,
pub insert_after: Option<Vec<Inline>>,
pub replace_with: Option<Vec<Inline>>,
pub remove: Option<bool>,
pub recurse: Option<bool>,
}
impl From<InlineVisitorControl> for InlineVisitControl {
fn from(js: InlineVisitorControl) -> Self {
InlineVisitControl {
insert_before: js.insert_before.unwrap_or_default(),
insert_after: js.insert_after.unwrap_or_default(),
replace_with: js.replace_with,
remove: js.remove.unwrap_or(false),
recurse: js.recurse.unwrap_or(false),
}
}
}
#[cfg(feature = "npm")]
#[napi]
pub type BlockCallback = FunctionRef<(Block,), Option<VisitorControl>>;
#[cfg(not(feature = "npm"))]
pub type BlockCallback = Box<dyn Fn(Block) -> Option<VisitorControl> + Send + 'static>;
#[cfg(feature = "npm")]
#[napi]
pub type InlineCallback = FunctionRef<(Inline,), Option<InlineVisitorControl>>;
#[cfg(not(feature = "npm"))]
pub type InlineCallback = Box<dyn Fn(Inline) -> Option<InlineVisitorControl> + Send + 'static>;
#[cfg_attr(feature = "npm", napi(object, object_to_js = false))]
#[derive(Default)]
pub struct Visitor {
pub visit_block: Option<BlockCallback>,
pub visit_inline: Option<InlineCallback>,
}
impl Visitor {
#[cfg(feature = "npm")]
pub(crate) fn register(self, env: Env) -> RegisteredVisitorJs {
RegisteredVisitorJs {
visit_block: self.visit_block,
visit_inline: self.visit_inline,
env,
}
}
#[cfg(not(feature = "npm"))]
pub(crate) fn register(self) -> RegisteredVisitorRs {
RegisteredVisitorRs {
visit_block: self.visit_block,
visit_inline: self.visit_inline,
}
}
}
#[cfg(feature = "npm")]
pub(crate) struct RegisteredVisitorJs {
pub visit_block: Option<BlockCallback>,
pub visit_inline: Option<InlineCallback>,
pub env: Env,
}
#[cfg(feature = "npm")]
pub type RegisteredVisitor = RegisteredVisitorJs;
#[cfg(feature = "npm")]
impl AstVisitor for RegisteredVisitorJs {
#[cfg(feature = "npm")]
fn visit_block(&mut self, block: &mut Block) -> VisitControl {
let cb = match &self.visit_block {
Some(cb) => cb,
_ => return VisitControl::default(),
};
let func: Function<(Block,), Option<VisitorControl>> = match cb.borrow_back(&self.env) {
Ok(f) => f,
Err(_) => return VisitControl::default(),
};
let snapshot = block.clone();
let js_ctrl = func.call((snapshot,)).unwrap_or(None).unwrap_or_default();
js_ctrl.into()
}
#[cfg(feature = "npm")]
fn visit_inline(&mut self, inline: &mut Inline) -> InlineVisitControl {
let cb = match &self.visit_inline {
Some(cb) => cb,
_ => return InlineVisitControl::default(),
};
let func: Function<(Inline,), Option<InlineVisitorControl>> =
match cb.borrow_back(&self.env) {
Ok(f) => f,
Err(_) => return InlineVisitControl::default(),
};
let snapshot = inline.clone();
let js_ctrl = func.call((snapshot,)).unwrap_or(None).unwrap_or_default();
js_ctrl.into()
}
}
#[cfg(not(feature = "npm"))]
pub(crate) struct RegisteredVisitorRs {
pub visit_block: Option<BlockCallback>,
pub visit_inline: Option<InlineCallback>,
}
#[cfg(not(feature = "npm"))]
pub(crate) type RegisteredVisitor = RegisteredVisitorRs;
#[cfg(not(feature = "npm"))]
impl AstVisitor for RegisteredVisitorRs {
fn visit_block(&mut self, block: &mut Block) -> VisitControl {
let cb = match &self.visit_block {
Some(cb) => cb,
_ => return VisitControl::default(),
};
let snapshot = block.clone();
cb(snapshot).unwrap_or_default().into()
}
fn visit_inline(&mut self, inline: &mut Inline) -> InlineVisitControl {
let cb = match &self.visit_inline {
Some(cb) => cb,
_ => return InlineVisitControl::default(),
};
let snapshot = inline.clone();
cb(snapshot).unwrap_or_default().into()
}
}
#[cfg_attr(feature = "npm", napi(object))]
#[derive(Debug, Default, Clone)]
pub struct BlockParseResult {
pub block: Option<Block>,
pub consumed: Option<u32>,
}
#[cfg_attr(feature = "npm", napi(object))]
#[derive(Debug, Default, Clone)]
pub struct InlineParseResult {
pub inline: Option<Inline>,
pub consumed: Option<u32>,
}
#[cfg(feature = "npm")]
#[napi]
pub type BlockParseCallback = FunctionRef<(BlockParseContext,), Option<BlockParseResult>>;
#[cfg(not(feature = "npm"))]
pub type BlockParseCallback =
Box<dyn Fn(BlockParseContext) -> Option<BlockParseResult> + Send + 'static>;
#[cfg(feature = "npm")]
#[napi]
pub type InlineParseCallback = FunctionRef<(InlineParseContext,), Option<InlineParseResult>>;
#[cfg(not(feature = "npm"))]
pub type InlineParseCallback =
Box<dyn Fn(InlineParseContext) -> Option<InlineParseResult> + Send + 'static>;
#[cfg_attr(feature = "npm", napi(object, object_to_js = false))]
#[derive(Default)]
pub struct Parser {
pub parse_block: Option<BlockParseCallback>,
pub parse_inline: Option<InlineParseCallback>,
}
impl Parser {
#[cfg(feature = "npm")]
pub(crate) fn register(self, env: Env) -> RegisteredParserJs {
RegisteredParserJs {
parse_block: self.parse_block,
parse_inline: self.parse_inline,
env,
}
}
#[cfg(not(feature = "npm"))]
pub(crate) fn register(self) -> RegisteredParserRs {
RegisteredParserRs {
parse_block: self.parse_block,
parse_inline: self.parse_inline,
}
}
}
#[cfg(feature = "npm")]
pub(crate) struct RegisteredParserJs {
pub parse_block: Option<BlockParseCallback>,
pub parse_inline: Option<InlineParseCallback>,
pub env: Env,
}
#[cfg(feature = "npm")]
pub(crate) type RegisteredParser = RegisteredParserJs;
#[cfg(feature = "npm")]
impl AstParser for RegisteredParserJs {
fn try_parse_block(&self, ctx: &BlockParseContext) -> Option<(Block, usize)> {
let cb = self.parse_block.as_ref()?;
let func: Function<(BlockParseContext,), Option<BlockParseResult>> =
match cb.borrow_back(&self.env) {
Ok(f) => f,
Err(e) => {
eprintln!("{}{}", "parseBlock hook could not be invoked: ", e);
return None;
}
};
let result = match func.call((ctx.clone(),)) {
Ok(r) => r?,
Err(e) => {
eprintln!(
"{}",
format!(
"parseBlock hook returned an invalid result: {e}\n \
note: the returned `block` must be a complete node — every \
field (including `pos` with line/column/offset) is required"
)
);
return None;
}
};
let block = result.block?;
let consumed = result.consumed.unwrap_or(1).max(1) as usize;
Some((block, consumed))
}
fn try_parse_inline(&self, ctx: &InlineParseContext) -> Option<(Inline, usize)> {
let cb = self.parse_inline.as_ref()?;
let func: Function<(InlineParseContext,), Option<InlineParseResult>> =
match cb.borrow_back(&self.env) {
Ok(f) => f,
Err(e) => {
eprintln!("parseInline hook could not be invoked: {e}");
return None;
}
};
let result = match func.call((ctx.clone(),)) {
Ok(r) => r?,
Err(e) => {
eprintln!(
"parseInline hook returned an invalid result: {e}\n \
note: the returned `inline` must be a complete node — every \
field (including `pos` with line/column/offset) is required"
);
return None;
}
};
let inline = result.inline?;
let consumed = result.consumed.unwrap_or(0) as usize;
(consumed > 0).then_some((inline, consumed))
}
}
#[cfg(not(feature = "npm"))]
pub(crate) struct RegisteredParserRs {
pub parse_block: Option<BlockParseCallback>,
pub parse_inline: Option<InlineParseCallback>,
}
#[cfg(not(feature = "npm"))]
pub(crate) type RegisteredParser = RegisteredParserRs;
#[cfg(not(feature = "npm"))]
impl AstParser for RegisteredParserRs {
fn try_parse_block(&self, ctx: &BlockParseContext) -> Option<(Block, usize)> {
let cb = self.parse_block.as_ref()?;
let result = cb(ctx.clone())?;
let block = result.block?;
let consumed = result.consumed.unwrap_or(1).max(1) as usize;
Some((block, consumed))
}
fn try_parse_inline(&self, ctx: &InlineParseContext) -> Option<(Inline, usize)> {
let cb = self.parse_inline.as_ref()?;
let result = cb(ctx.clone())?;
let inline = result.inline?;
let consumed = result.consumed.unwrap_or(0) as usize;
(consumed > 0).then_some((inline, consumed))
}
}