markdown_it/common/
ruler.rs

1//! Plugin manager with dependency resolution.
2
3use derivative::Derivative;
4use once_cell::sync::OnceCell;
5use std::collections::{HashMap, HashSet};
6use std::fmt::Debug;
7use std::hash::Hash;
8use std::slice::Iter;
9
10///
11/// Ruler allows you to implement a plugin system with dependency management and ensure that
12/// your dependencies are called in the correct order.
13///
14/// You can use it like this:
15/// ```
16/// use markdown_it::common::ruler::Ruler;
17///
18/// // this example prints "[ hello, world! ]",
19/// // where each token is printed by separate closure
20/// let mut chain = Ruler::<&str, fn (&mut String)>::new();
21///
22/// // define rules printing "hello" and "world"
23/// chain.add("hello", |s| s.push_str("hello"));
24/// chain.add("world", |s| s.push_str("world"));
25///
26/// // open bracket should be before "hello", and closing one after "world"
27/// chain.add("open_bracket", |s| s.push_str("[ ")).before("hello");
28/// chain.add("close_bracket", |s| s.push_str(" ]")).after("world");
29///
30/// // between "hello" and "world" we shall have a comma
31/// chain.add("comma", |s| s.push_str(", ")).after("hello").before("world");
32///
33/// // after "world" we should have "!" as a first rule, but ensure "world" exists first
34/// chain.add("bang", |s| s.push_str("!")).require("world").after("world").before_all();
35///
36/// // now we run this chain
37/// let mut result = String::new();
38/// for f in chain.iter() { f(&mut result); }
39/// assert_eq!(result, "[ hello, world! ]");
40/// ```
41///
42/// This data structure contains any number of elements (M, T), where T is any type and
43/// M (mark) is its identifier.
44///
45///  - `M` is used for ordering and dependency checking, it must implement `Eq + Copy + Hash + Debug`
46/// . Common choices for `M` are `u32`, `&'static str`, or a special `Symbol` type
47/// designed for this purpose.
48///
49///  - `T` is any user-defined type. It's usually a function or boxed trait.
50///
51pub struct Ruler<M, T> {
52    deps: Vec<RuleItem<M, T>>,
53    compiled: OnceCell<(Vec<usize>, Vec<T>)>,
54}
55
56impl<M, T> Ruler<M, T> {
57    pub fn new() -> Self {
58        Self::default()
59    }
60}
61
62impl<M: Eq + Hash + Copy + Debug, T: Clone> Ruler<M, T> {
63    /// Add a new rule identified by `mark` with payload `value`.
64    pub fn add(&mut self, mark: M, value: T) -> &mut RuleItem<M, T> {
65        self.compiled = OnceCell::new();
66        let dep = RuleItem::new(mark, value);
67        self.deps.push(dep);
68        self.deps.last_mut().unwrap()
69    }
70
71    /// Remove all rules identified by `mark`.
72    pub fn remove(&mut self, mark: M) {
73        self.deps.retain(|dep| !dep.marks.contains(&mark));
74    }
75
76    /// Check if there are any rules identified by `mark`.
77    pub fn contains(&mut self, mark: M) -> bool {
78        self.deps.iter().any(|dep| dep.marks.contains(&mark))
79    }
80
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>) {
88        // ID -> [RuleItem index]
89        let mut idhash = HashMap::<M, Vec<usize>>::new();
90
91        // RuleItem index -> [RuleItem index] - dependency graph, None if already inserted
92        let mut deps_graph = vec![HashSet::new(); self.deps.len()];
93
94        // additional level of indirection that takes into account item priority
95        let mut deps_order = vec![];
96        let mut beforeall_len = 0;
97        let mut afterall_len = 0;
98
99        // compiled result
100        let mut result = vec![];
101        let mut result_idx = vec![];
102
103        // track which rules have been added already
104        let mut deps_inserted = vec![false; self.deps.len()];
105        let mut deps_remaining = self.deps.len();
106
107        for (idx, dep) in self.deps.iter().enumerate() {
108            match dep.prio {
109                RuleItemPriority::Normal => {
110                    deps_order.insert(deps_order.len() - afterall_len, idx);
111                }
112                RuleItemPriority::BeforeAll => {
113                    deps_order.insert(beforeall_len, idx);
114                    beforeall_len += 1;
115                }
116                RuleItemPriority::AfterAll => {
117                    deps_order.insert(deps_order.len(), idx);
118                    afterall_len += 1;
119                }
120            }
121            for mark in &dep.marks {
122                idhash.entry(*mark).or_default().push(idx);
123            }
124        }
125
126        // build dependency graph, replacing all after's with before's,
127        // i.e. B.after(A) -> A.before(B)
128        for idx in deps_order.iter().copied() {
129            let dep = self.deps.get(idx).unwrap();
130            for constraint in &dep.cons {
131                match constraint {
132                    RuleItemConstraint::Before(v) => {
133                        for depidx in idhash.entry(*v).or_default().iter() {
134                            deps_graph.get_mut(*depidx).unwrap().insert(idx);
135                        }
136                    }
137                    RuleItemConstraint::After(v) => {
138                        for depidx in idhash.entry(*v).or_default().iter() {
139                            deps_graph.get_mut(idx).unwrap().insert(*depidx);
140                        }
141                    }
142                    RuleItemConstraint::Require(v) => {
143                        assert!(
144                            idhash.contains_key(v),
145                            "missing dependency: {:?} requires {:?}", dep.marks.first().unwrap(), v
146                        );
147                    }
148                }
149            }
150        }
151
152        // now go through the deps and push whatever doesn't have any
153        'outer: while deps_remaining > 0 {
154            for idx in deps_order.iter().copied() {
155                let inserted = deps_inserted.get_mut(idx).unwrap();
156                if *inserted { continue; }
157
158                let dlist = deps_graph.get(idx).unwrap();
159                if dlist.is_empty() {
160                    let dep = self.deps.get(idx).unwrap();
161                    result.push(dep.value.clone());
162                    result_idx.push(idx);
163                    *inserted = true;
164                    deps_remaining -= 1;
165                    for d in deps_graph.iter_mut() {
166                        d.remove(&idx);
167                    }
168                    continue 'outer;
169                }
170            }
171
172            #[cfg(debug_assertions)] {
173                // check cycles in dependency graph;
174                // this is very suboptimal, but only used to generate a nice panic message.
175                // in release mode we'll just simply panic
176                for idx in deps_order.iter().copied() {
177                    let mut seen = HashMap::new();
178                    let mut vec = vec![idx];
179                    while let Some(didx) = vec.pop() {
180                        let dlist = deps_graph.get(didx).unwrap();
181                        for x in dlist.iter() {
182                            if seen.contains_key(x) { continue; }
183                            vec.push(*x);
184                            seen.insert(*x, didx);
185                            if *x == idx {
186                                let mut backtrack = vec![];
187                                let mut curr = idx;
188                                while !backtrack.contains(&curr) {
189                                    backtrack.push(curr);
190                                    curr = *seen.get(&curr).unwrap();
191                                }
192                                backtrack.push(curr);
193                                let path = backtrack.iter()
194                                    .rev()
195                                    .map(|x| format!("{:?}", self.deps.get(*x).unwrap().marks.first().unwrap()))
196                                    .collect::<Vec<String>>()
197                                    .join(" < ");
198                                panic!("cyclic dependency: {}", path);
199                            }
200                        }
201                    }
202                }
203            }
204
205            // if you see this in debug mode, report it as a bug
206            panic!("cyclic dependency: (use debug mode for more details)");
207        }
208
209        (result_idx, result)
210    }
211}
212
213impl<M: Eq + Hash + Copy + Debug, T: Clone> Debug for Ruler<M, T> {
214    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.first().unwrap()))
218                                    .collect();
219
220        f.debug_struct("Ruler")
221            .field("deps", &self.deps)
222            .field("compiled", &vec)
223            .finish()
224    }
225}
226
227impl<M, T> Default for Ruler<M, T> {
228    fn default() -> Self {
229        Self {
230            deps: Vec::new(),
231            compiled: OnceCell::new(),
232        }
233    }
234}
235
236///
237/// Result of [Ruler::add](Ruler::add), allows to customize position of each rule.
238///
239#[derive(Derivative)]
240#[derivative(Debug)]
241pub struct RuleItem<M, T> {
242    marks: Vec<M>,
243    #[derivative(Debug="ignore")]
244    value: T,
245    prio: RuleItemPriority,
246    cons: Vec<RuleItemConstraint<M>>,
247}
248
249impl<M, T> RuleItem<M, T> {
250    fn new(mark: M, value: T) -> Self {
251        Self {
252            marks: vec![mark],
253            value,
254            prio: RuleItemPriority::Normal,
255            cons: vec![],
256        }
257    }
258}
259
260impl<M: Copy, T> RuleItem<M, T> {
261    /// Make sure this rule will be inserted before any rule defined by `mark` (if such rule exists).
262    /// ```
263    /// use markdown_it::common::ruler::Ruler;
264    /// let mut chain = Ruler::<&str, fn (&mut String)>::new();
265    ///
266    /// chain.add("a", |s| s.push_str("bar"));
267    /// chain.add("b", |s| s.push_str("foo")).before("a");
268    ///
269    /// let mut result = String::new();
270    /// for f in chain.iter() { f(&mut result); }
271    /// assert_eq!(result, "foobar");
272    /// ```
273    pub fn before(&mut self, mark: M) -> &mut Self {
274        self.cons.push(RuleItemConstraint::Before(mark));
275        self
276    }
277
278    /// Make sure this rule will be inserted after any rule defined by `mark` (if such rule exists).
279    /// Similar to [RuleItem::before](RuleItem::before).
280    pub fn after(&mut self, mark: M) -> &mut Self {
281        self.cons.push(RuleItemConstraint::After(mark));
282        self
283    }
284
285    /// This rule will be inserted as early as possible, while still taking into account dependencies,
286    /// i.e. `.after(X).before_all()` causes this to be first rule after X.
287    /// ```
288    /// use markdown_it::common::ruler::Ruler;
289    /// let mut chain = Ruler::<&str, fn (&mut String)>::new();
290    ///
291    /// chain.add("a", |s| s.push_str("A"));
292    /// chain.add("c", |s| s.push_str("C")).after("a");
293    /// chain.add("b", |s| s.push_str("B")).after("a").before_all();
294    ///
295    /// let mut result = String::new();
296    /// for f in chain.iter() { f(&mut result); }
297    /// // without before_all order will be ACB
298    /// assert_eq!(result, "ABC");
299    /// ```
300    pub fn before_all(&mut self) -> &mut Self {
301        self.prio = RuleItemPriority::BeforeAll;
302        self
303    }
304
305    /// This rule will be inserted as late as possible, while still taking into account dependencies,
306    /// i.e. `.before(X).after_all()` causes this to be last rule before X.
307    /// Similar to [RuleItem::before_all](RuleItem::before_all).
308    pub fn after_all(&mut self) -> &mut Self {
309        self.prio = RuleItemPriority::AfterAll;
310        self
311    }
312
313    /// Add another auxiliary identifier to this rule. It can be used to group together multiple
314    /// rules with similar functionality.
315    /// ```
316    /// use markdown_it::common::ruler::Ruler;
317    /// let mut chain = Ruler::<&str, fn (&mut String)>::new();
318    ///
319    /// chain.add("b", |s| s.push_str("B")).alias("BorC");
320    /// chain.add("c", |s| s.push_str("C")).alias("BorC");
321    /// chain.add("a", |s| s.push_str("A")).before("BorC");
322    ///
323    /// let mut result = String::new();
324    /// for f in chain.iter() { f(&mut result); }
325    /// assert_eq!(result, "ABC");
326    /// ```
327    pub fn alias(&mut self, mark: M) -> &mut Self {
328        self.marks.push(mark);
329        self
330    }
331
332    /// Require another rule identified by `mark`, panic if not found.
333    pub fn require(&mut self, mark: M) -> &mut Self {
334        self.cons.push(RuleItemConstraint::Require(mark));
335        self
336    }
337}
338
339#[derive(Debug)]
340enum RuleItemConstraint<M> {
341    Before(M),
342    After(M),
343    Require(M),
344}
345
346#[derive(Debug)]
347enum RuleItemPriority {
348    Normal,
349    BeforeAll,
350    AfterAll,
351}
352
353
354#[cfg(test)]
355mod tests {
356    use super::Ruler;
357
358    #[test]
359    #[should_panic(expected=r#"cyclic dependency: "A" < "B" < "C" < "D" < "E" < "F" < "A""#)]
360    #[cfg(debug_assertions)]
361    fn cyclic_dependency_debug() {
362        let mut r = Ruler::new();
363        r.add("%", ()).after("D");
364        r.add("A", ()).after("B");
365        r.add("E", ()).after("F");
366        r.add("C", ()).after("D");
367        r.add("B", ()).after("C");
368        r.add("D", ()).after("E");
369        r.add("F", ()).after("A");
370        r.compile();
371    }
372
373    #[test]
374    #[should_panic(expected=r#"cyclic dependency"#)]
375    fn cyclic_dependency() {
376        let mut r = Ruler::new();
377        r.add("A", ()).after("B");
378        r.add("B", ()).after("C");
379        r.add("C", ()).after("A");
380        r.compile();
381    }
382
383
384    #[test]
385    #[should_panic(expected=r#"missing dependency: "C" requires "Z"#)]
386    fn missing_require() {
387        let mut r = Ruler::new();
388        r.add("A", ());
389        r.add("B", ()).require("A");
390        r.add("C", ()).require("Z");
391        r.compile();
392    }
393}