Skip to content

Commit d2b8cd4

Browse files
committed
implement try_run and error propagation
1 parent e4b6beb commit d2b8cd4

File tree

18 files changed

+671
-99
lines changed

18 files changed

+671
-99
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ default = ["linkify", "syntect"]
2828
# Lower range limit of these dependencies was manually checked to work with
2929
# minimal versions possible, higher range limit is best guess based on semver.
3030
# So older versions will not work, but newer versions might.
31+
anyhow = ">= 1.0.18, < 2"
3132
argparse = ">= 0.2.1, < 0.3"
3233
const_format = ">= 0.1.0, < 0.3"
3334
derivative = ">= 1.0.2, < 3"

examples/error_handling/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This is an example of error handling and error propagation in custom markdown-it rules.

examples/error_handling/main.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
use markdown_it::parser::block::{BlockRule, BlockState};
2+
use markdown_it::parser::core::CoreRule;
3+
use markdown_it::parser::inline::{InlineRule, InlineState};
4+
use markdown_it::{MarkdownIt, Node, Result};
5+
use std::error::Error;
6+
use std::fmt::Display;
7+
8+
#[derive(Debug)]
9+
struct MyError(&'static str);
10+
11+
impl Display for MyError {
12+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
13+
f.write_str(self.0)
14+
}
15+
}
16+
17+
impl Error for MyError {}
18+
19+
struct FallibleInlineRule;
20+
21+
impl InlineRule for FallibleInlineRule {
22+
const MARKER: char = '@';
23+
24+
// This is implementation of a rule that always fails on `@` character.
25+
fn try_run(state: &mut InlineState) -> Result<Option<(Node, usize)>> {
26+
// skip other characters
27+
if !state.src[state.pos..].starts_with(Self::MARKER) { return Ok(None); };
28+
29+
Err(MyError("AAA").into())
30+
}
31+
32+
fn run(state: &mut InlineState) -> Option<(Node, usize)> {
33+
Self::try_run(state).unwrap_or_default()
34+
}
35+
}
36+
37+
struct FallibleBlockRule;
38+
39+
impl BlockRule for FallibleBlockRule {
40+
// This is implementation of a rule that always fails on `@@@` at the start of the line.
41+
fn try_run(state: &mut BlockState) -> Result<Option<(Node, usize)>> {
42+
if !state.get_line(state.line).starts_with("@@@") { return Ok(None); };
43+
44+
Err(MyError("BBB").into())
45+
}
46+
47+
fn run(state: &mut BlockState) -> Option<(Node, usize)> {
48+
Self::try_run(state).unwrap_or_default()
49+
}
50+
}
51+
52+
struct FallibleCoreRule;
53+
54+
impl CoreRule for FallibleCoreRule {
55+
fn try_run(_root: &mut Node, _md: &MarkdownIt) -> Result<()> {
56+
Err(MyError("CCC").into())
57+
}
58+
59+
fn run(root: &mut Node, md: &MarkdownIt) {
60+
let _ = Self::try_run(root, md);
61+
}
62+
}
63+
64+
fn main() {
65+
let md = &mut markdown_it::MarkdownIt::new();
66+
markdown_it::plugins::cmark::add(md);
67+
68+
md.inline.add_rule::<FallibleInlineRule>();
69+
md.block.add_rule::<FallibleBlockRule>();
70+
md.add_rule::<FallibleCoreRule>().after_all();
71+
72+
// inline rule fails
73+
let text1 = r#"*hello @world*"#;
74+
let err = md.try_parse(text1).err().unwrap();
75+
println!("{err}");
76+
assert_eq!(err.source().unwrap().to_string(), "AAA");
77+
78+
// block rule fails
79+
let text2 = r#"@@@ *hello*"#;
80+
let err = md.try_parse(text2).err().unwrap();
81+
println!("{err}");
82+
assert_eq!(err.source().unwrap().to_string(), "BBB");
83+
84+
// core rule fails
85+
let text3 = r#"*hello*"#;
86+
let err = md.try_parse(text3).err().unwrap();
87+
println!("{err}");
88+
assert_eq!(err.source().unwrap().to_string(), "CCC");
89+
90+
// If you run parse() function instead of try_parse(), failing rules will be skipped.
91+
// This will result in custom syntax being left as user wrote it (not parsed).
92+
let html = md.parse(text1).render();
93+
print!("{html}");
94+
assert_eq!(html, "<p><em>hello @world</em></p>\n");
95+
96+
let html = md.parse(text2).render();
97+
print!("{html}");
98+
assert_eq!(html, "<p>@@@ <em>hello</em></p>\n");
99+
100+
let html = md.parse(text3).render();
101+
print!("{html}");
102+
assert_eq!(html, "<p><em>hello</em></p>\n");
103+
}

src/common/ruler.rs

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
//! Plugin manager with dependency resolution.
22
33
use derivative::Derivative;
4-
use once_cell::sync::OnceCell;
54
use std::collections::{HashMap, HashSet};
65
use std::fmt::Debug;
76
use std::hash::Hash;
8-
use std::slice::Iter;
97

108
///
119
/// Ruler allows you to implement a plugin system with dependency management and ensure that
@@ -35,7 +33,7 @@ use std::slice::Iter;
3533
///
3634
/// // now we run this chain
3735
/// let mut result = String::new();
38-
/// for f in chain.iter() { f(&mut result); }
36+
/// for f in chain.compile() { f(&mut result); }
3937
/// assert_eq!(result, "[ hello, world! ]");
4038
/// ```
4139
///
@@ -50,7 +48,6 @@ use std::slice::Iter;
5048
///
5149
pub struct Ruler<M, T> {
5250
deps: Vec<RuleItem<M, T>>,
53-
compiled: OnceCell<(Vec<usize>, Vec<T>)>,
5451
}
5552

5653
impl<M, T> Ruler<M, T> {
@@ -62,7 +59,6 @@ impl<M, T> Ruler<M, T> {
6259
impl<M: Eq + Hash + Copy + Debug, T: Clone> Ruler<M, T> {
6360
/// Add a new rule identified by `mark` with payload `value`.
6461
pub fn add(&mut self, mark: M, value: T) -> &mut RuleItem<M, T> {
65-
self.compiled = OnceCell::new();
6662
let dep = RuleItem::new(mark, value);
6763
self.deps.push(dep);
6864
self.deps.last_mut().unwrap()
@@ -74,17 +70,12 @@ impl<M: Eq + Hash + Copy + Debug, T: Clone> Ruler<M, T> {
7470
}
7571

7672
/// Check if there are any rules identified by `mark`.
77-
pub fn contains(&mut self, mark: M) -> bool {
73+
pub fn contains(&self, mark: M) -> bool {
7874
self.deps.iter().any(|dep| dep.marks.contains(&mark))
7975
}
8076

81-
/// Ordered iteration through rules.
82-
#[inline]
83-
pub fn iter(&self) -> Iter<T> {
84-
self.compiled.get_or_init(|| self.compile()).1.iter()
85-
}
86-
87-
fn compile(&self) -> (Vec<usize>, Vec<T>) {
77+
/// Convert dependency tree into an ordered list.
78+
pub fn compile(&self) -> Vec<T> {
8879
// ID -> [RuleItem index]
8980
let mut idhash = HashMap::<M, Vec<usize>>::new();
9081

@@ -206,20 +197,15 @@ impl<M: Eq + Hash + Copy + Debug, T: Clone> Ruler<M, T> {
206197
panic!("cyclic dependency: (use debug mode for more details)");
207198
}
208199

209-
(result_idx, result)
200+
//(result_idx, result)
201+
result
210202
}
211203
}
212204

213205
impl<M: Eq + Hash + Copy + Debug, T: Clone> Debug for Ruler<M, T> {
214206
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215-
let vec: Vec<(usize, M)> = self.compiled.get_or_init(|| self.compile()).0
216-
.iter()
217-
.map(|idx| (*idx, *self.deps.get(*idx).unwrap().marks.get(0).unwrap()))
218-
.collect();
219-
220207
f.debug_struct("Ruler")
221208
.field("deps", &self.deps)
222-
.field("compiled", &vec)
223209
.finish()
224210
}
225211
}
@@ -228,7 +214,6 @@ impl<M, T> Default for Ruler<M, T> {
228214
fn default() -> Self {
229215
Self {
230216
deps: Vec::new(),
231-
compiled: OnceCell::new(),
232217
}
233218
}
234219
}
@@ -267,7 +252,7 @@ impl<M: Copy, T> RuleItem<M, T> {
267252
/// chain.add("b", |s| s.push_str("foo")).before("a");
268253
///
269254
/// let mut result = String::new();
270-
/// for f in chain.iter() { f(&mut result); }
255+
/// for f in chain.compile() { f(&mut result); }
271256
/// assert_eq!(result, "foobar");
272257
/// ```
273258
pub fn before(&mut self, mark: M) -> &mut Self {
@@ -293,7 +278,7 @@ impl<M: Copy, T> RuleItem<M, T> {
293278
/// chain.add("b", |s| s.push_str("B")).after("a").before_all();
294279
///
295280
/// let mut result = String::new();
296-
/// for f in chain.iter() { f(&mut result); }
281+
/// for f in chain.compile() { f(&mut result); }
297282
/// // without before_all order will be ACB
298283
/// assert_eq!(result, "ABC");
299284
/// ```
@@ -321,7 +306,7 @@ impl<M: Copy, T> RuleItem<M, T> {
321306
/// chain.add("a", |s| s.push_str("A")).before("BorC");
322307
///
323308
/// let mut result = String::new();
324-
/// for f in chain.iter() { f(&mut result); }
309+
/// for f in chain.compile() { f(&mut result); }
325310
/// assert_eq!(result, "ABC");
326311
/// ```
327312
pub fn alias(&mut self, mark: M) -> &mut Self {

src/common/typekey.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ impl TypeKey {
3535
pub fn of<T: ?Sized + 'static>() -> Self {
3636
Self { id: TypeId::of::<T>(), name: any::type_name::<T>() }
3737
}
38+
39+
#[must_use]
40+
pub fn short_name(&self) -> &str {
41+
&self.name[self.name.rfind("::").map(|p| p + 2).unwrap_or(0)..]
42+
}
3843
}
3944

4045
impl Hash for TypeKey {

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
// just a style choice that clippy has no business complaining about
1919
#![allow(clippy::uninlined_format_args)]
2020

21+
// reexport for using in try_parse apis
22+
pub use anyhow::Result;
23+
2124
pub mod common;
2225
pub mod examples;
2326
pub mod generics;
Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::parser::core::{CoreRule, Root};
2-
use crate::{MarkdownIt, Node};
2+
use crate::parser::main::RootNodeWrongType;
3+
use crate::{MarkdownIt, Node, Result};
34

45
pub fn add(md: &mut MarkdownIt) {
56
md.add_rule::<BlockParserRule>()
@@ -8,16 +9,39 @@ pub fn add(md: &mut MarkdownIt) {
89

910
pub struct BlockParserRule;
1011
impl CoreRule for BlockParserRule {
12+
fn try_run(root: &mut Node, md: &MarkdownIt) -> Result<()> {
13+
Self::_run::<true>(root, md)?;
14+
Ok(())
15+
}
16+
1117
fn run(root: &mut Node, md: &MarkdownIt) {
18+
let _ = Self::_run::<false>(root, md);
19+
}
20+
}
21+
22+
impl BlockParserRule {
23+
fn _run<const CAN_FAIL: bool>(root: &mut Node, md: &MarkdownIt) -> Result<()> {
1224
let mut node = std::mem::take(root);
13-
let data = node.cast_mut::<Root>().unwrap();
25+
let Some(data) = node.cast_mut::<Root>() else {
26+
return Err(RootNodeWrongType.into());
27+
};
1428
let source = std::mem::take(&mut data.content);
1529
let mut ext = std::mem::take(&mut data.ext);
1630

17-
node = md.block.parse(source.as_str(), node, md, &mut ext);
18-
let data = node.cast_mut::<Root>().unwrap();
31+
md.block.compile();
32+
node = if CAN_FAIL {
33+
md.block.try_parse(source.as_str(), node, md, &mut ext)?
34+
} else {
35+
md.block.parse(source.as_str(), node, md, &mut ext)
36+
};
37+
*root = node;
38+
39+
let Some(data) = root.cast_mut::<Root>() else {
40+
return Err(RootNodeWrongType.into());
41+
};
1942
data.content = source;
2043
data.ext = ext;
21-
*root = node;
44+
45+
Ok(())
2246
}
2347
}

0 commit comments

Comments
 (0)