use crate::markdown::ast::parsers::plugin::{
BlockParserContext, InlineParserContext, InlineVisitControl, PluginFactory, 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_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 BlockCallback = FunctionRef<(Block,), Option<VisitorControl>>;
#[cfg(feature = "npm")]
#[napi]
pub type InlineCallback = FunctionRef<(Inline,), Option<InlineVisitorControl>>;
#[cfg(feature = "npm")]
#[napi]
pub type BlockParseCallback = FunctionRef<(BlockParserContext,), Option<BlockParseResult>>;
#[cfg(feature = "npm")]
#[napi]
pub type InlineParseCallback = FunctionRef<(InlineParserContext,), Option<InlineParseResult>>;
#[cfg(not(feature = "npm"))]
pub type BlockCallback = Box<dyn Fn(Block) -> Option<VisitorControl> + Send + 'static>;
#[cfg(not(feature = "npm"))]
pub type InlineCallback = Box<dyn Fn(Inline) -> Option<InlineVisitorControl> + Send + 'static>;
#[cfg(not(feature = "npm"))]
pub type BlockParseCallback =
Box<dyn Fn(BlockParserContext) -> Option<BlockParseResult> + Send + 'static>;
#[cfg(not(feature = "npm"))]
pub type InlineParseCallback =
Box<dyn Fn(InlineParserContext) -> Option<InlineParseResult> + Send + 'static>;
#[cfg_attr(feature = "npm", napi(object, object_to_js = false))]
#[derive(Default)]
pub struct Plugin {
pub visit_block: Option<BlockCallback>,
pub visit_inline: Option<InlineCallback>,
pub parse_block: Option<BlockParseCallback>,
pub parse_inline: Option<InlineParseCallback>,
}
impl Plugin {
#[cfg(feature = "npm")]
pub(crate) fn register(self, env: Env) -> RegisteredPlugin {
RegisteredPlugin {
visit_block: self.visit_block,
visit_inline: self.visit_inline,
parse_block: self.parse_block,
parse_inline: self.parse_inline,
env,
}
}
#[cfg(not(feature = "npm"))]
pub(crate) fn register(self) -> RegisteredPlugin {
RegisteredPlugin {
visit_block: self.visit_block,
visit_inline: self.visit_inline,
parse_block: self.parse_block,
parse_inline: self.parse_inline,
}
}
}
#[cfg(feature = "npm")]
pub(crate) struct RegisteredPlugin {
pub visit_block: Option<BlockCallback>,
pub visit_inline: Option<InlineCallback>,
pub parse_block: Option<BlockParseCallback>,
pub parse_inline: Option<InlineParseCallback>,
pub env: Env,
}
#[cfg(feature = "npm")]
impl PluginFactory for RegisteredPlugin {
#[cfg(feature = "npm")]
fn has_parse_hooks(&self) -> bool {
self.parse_block.is_some() || self.parse_inline.is_some()
}
#[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 = match func.call((snapshot,)) {
Ok(control) => control.unwrap_or_default(),
Err(e) => {
eprintln!("visitBlock callback failed: {e}");
VisitorControl::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 = match func.call((snapshot,)) {
Ok(control) => control.unwrap_or_default(),
Err(e) => {
eprintln!("visitInline callback failed: {e}");
InlineVisitorControl::default()
}
};
js_ctrl.into()
}
fn try_parse_block(&self, ctx: &BlockParserContext) -> Option<(Block, usize)> {
let cb = self.parse_block.as_ref()?;
let func: Function<(BlockParserContext,), 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: &InlineParserContext) -> Option<(Inline, usize)> {
let cb = self.parse_inline.as_ref()?;
let func: Function<(InlineParserContext,), 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 RegisteredPlugin {
pub visit_block: Option<BlockCallback>,
pub visit_inline: Option<InlineCallback>,
pub parse_block: Option<BlockParseCallback>,
pub parse_inline: Option<InlineParseCallback>,
}
#[cfg(not(feature = "npm"))]
impl PluginFactory for RegisteredPlugin {
fn has_parse_hooks(&self) -> bool {
self.parse_block.is_some() || self.parse_inline.is_some()
}
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()
}
fn try_parse_block(&self, ctx: &BlockParserContext) -> 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: &InlineParserContext) -> 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))
}
}
#[allow(deprecated)]
mod deprecated_js {
#[cfg(feature = "npm")]
use super::Env;
use super::{
BlockCallback, BlockParseCallback, InlineCallback, InlineParseCallback, Plugin,
RegisteredPlugin,
};
#[cfg(feature = "npm")]
use napi_derive::napi;
#[cfg_attr(feature = "npm", napi(object, object_to_js = false))]
#[derive(Default)]
#[deprecated(since = "1.3.0", note = "Use `Plugin` instead")]
pub struct Visitor {
pub visit_block: Option<BlockCallback>,
pub visit_inline: Option<InlineCallback>,
}
#[allow(deprecated)]
impl Visitor {
#[cfg(feature = "npm")]
pub(crate) fn into_plugin(self, env: Env) -> RegisteredPlugin {
Plugin {
visit_block: self.visit_block,
visit_inline: self.visit_inline,
parse_block: None,
parse_inline: None,
}
.register(env)
}
#[cfg(not(feature = "npm"))]
pub(crate) fn into_plugin(self) -> RegisteredPlugin {
Plugin {
visit_block: self.visit_block,
visit_inline: self.visit_inline,
parse_block: None,
parse_inline: None,
}
.register()
}
}
#[cfg_attr(feature = "npm", napi(object, object_to_js = false))]
#[derive(Default)]
#[deprecated(since = "1.3.0", note = "Use `Plugin` instead")]
pub struct Parser {
pub parse_block: Option<BlockParseCallback>,
pub parse_inline: Option<InlineParseCallback>,
}
#[allow(deprecated)]
impl Parser {
#[cfg(feature = "npm")]
pub(crate) fn into_plugin(self, env: Env) -> RegisteredPlugin {
Plugin {
visit_block: None,
visit_inline: None,
parse_block: self.parse_block,
parse_inline: self.parse_inline,
}
.register(env)
}
#[cfg(not(feature = "npm"))]
pub(crate) fn into_plugin(self) -> RegisteredPlugin {
Plugin {
visit_block: None,
visit_inline: None,
parse_block: self.parse_block,
parse_inline: self.parse_inline,
}
.register()
}
}
}
#[allow(deprecated)]
pub use deprecated_js::{Parser, Visitor};