Skip to main content

markdown_it/common/
ruler.rs

1//! Plugin manager with dependency resolution.
2
3use std::collections::{HashMap, HashSet};
4use std::fmt::{Debug, Formatter};
5use std::hash::Hash;
6use std::slice::Iter;
7
8use once_cell::sync::OnceCell;
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 {:?}",
146                            dep.marks.first().unwrap(),
147                            v
148                        );
149                    }
150                }
151            }
152        }
153
154        // now go through the deps and push whatever doesn't have any
155        'outer: while deps_remaining > 0 {
156            for idx in deps_order.iter().copied() {
157                let inserted = deps_inserted.get_mut(idx).unwrap();
158                if *inserted {
159                    continue;
160                }
161
162                let dlist = deps_graph.get(idx).unwrap();
163                if dlist.is_empty() {
164                    let dep = self.deps.get(idx).unwrap();
165                    result.push(dep.value.clone());
166                    result_idx.push(idx);
167                    *inserted = true;
168                    deps_remaining -= 1;
169                    for d in deps_graph.iter_mut() {
170                        d.remove(&idx);
171                    }
172                    continue 'outer;
173                }
174            }
175
176            #[cfg(debug_assertions)]
177            {
178                // check cycles in dependency graph;
179                // this is very suboptimal, but only used to generate a nice panic message.
180                // in release mode we'll just simply panic
181                for idx in deps_order.iter().copied() {
182                    let mut seen = HashMap::new();
183                    let mut vec = vec![idx];
184                    while let Some(didx) = vec.pop() {
185                        let dlist = deps_graph.get(didx).unwrap();
186                        for x in dlist.iter() {
187                            if seen.contains_key(x) {
188                                continue;
189                            }
190                            vec.push(*x);
191                            seen.insert(*x, didx);
192                            if *x == idx {
193                                let mut backtrack = vec![];
194                                let mut curr = idx;
195                                while !backtrack.contains(&curr) {
196                                    backtrack.push(curr);
197                                    curr = *seen.get(&curr).unwrap();
198                                }
199                                backtrack.push(curr);
200                                let path = backtrack
201                                    .iter()
202                                    .rev()
203                                    .map(|x| {
204                                        format!(
205                                            "{:?}",
206                                            self.deps.get(*x).unwrap().marks.first().unwrap()
207                                        )
208                                    })
209                                    .collect::<Vec<String>>()
210                                    .join(" < ");
211                                panic!("cyclic dependency: {}", path);
212                            }
213                        }
214                    }
215                }
216            }
217
218            // if you see this in debug mode, report it as a bug
219            panic!("cyclic dependency: (use debug mode for more details)");
220        }
221
222        (result_idx, result)
223    }
224}
225
226impl<M: Eq + Hash + Copy + Debug, T: Clone> Debug for Ruler<M, T> {
227    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228        let vec: Vec<(usize, M)> = self
229            .compiled
230            .get_or_init(|| self.compile())
231            .0
232            .iter()
233            .map(|idx| (*idx, *self.deps.get(*idx).unwrap().marks.first().unwrap()))
234            .collect();
235
236        f.debug_struct("Ruler")
237            .field("deps", &self.deps)
238            .field("compiled", &vec)
239            .finish()
240    }
241}
242
243impl<M, T> Default for Ruler<M, T> {
244    fn default() -> Self {
245        Self {
246            deps: Vec::new(),
247            compiled: OnceCell::new(),
248        }
249    }
250}
251
252///
253/// Result of [Ruler::add](Ruler::add), allows to customize position of each rule.
254///
255pub struct RuleItem<M, T> {
256    marks: Vec<M>,
257    value: T,
258    prio: RuleItemPriority,
259    cons: Vec<RuleItemConstraint<M>>,
260}
261
262impl<M: std::fmt::Debug, T> Debug for RuleItem<M, T> {
263    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
264        f.debug_struct("RuleItem")
265            .field("marks", &self.marks)
266            .field("prio", &self.prio)
267            .field("cons", &self.cons)
268            .finish()
269    }
270}
271
272impl<M, T> RuleItem<M, T> {
273    fn new(mark: M, value: T) -> Self {
274        Self {
275            marks: vec![mark],
276            value,
277            prio: RuleItemPriority::Normal,
278            cons: vec![],
279        }
280    }
281}
282
283impl<M: Copy, T> RuleItem<M, T> {
284    /// Make sure this rule will be inserted before any rule defined by `mark` (if such rule exists).
285    /// ```
286    /// use markdown_it::common::ruler::Ruler;
287    /// let mut chain = Ruler::<&str, fn (&mut String)>::new();
288    ///
289    /// chain.add("a", |s| s.push_str("bar"));
290    /// chain.add("b", |s| s.push_str("foo")).before("a");
291    ///
292    /// let mut result = String::new();
293    /// for f in chain.iter() { f(&mut result); }
294    /// assert_eq!(result, "foobar");
295    /// ```
296    pub fn before(&mut self, mark: M) -> &mut Self {
297        self.cons.push(RuleItemConstraint::Before(mark));
298        self
299    }
300
301    /// Make sure this rule will be inserted after any rule defined by `mark` (if such rule exists).
302    /// Similar to [RuleItem::before](RuleItem::before).
303    pub fn after(&mut self, mark: M) -> &mut Self {
304        self.cons.push(RuleItemConstraint::After(mark));
305        self
306    }
307
308    /// This rule will be inserted as early as possible, while still taking into account dependencies,
309    /// i.e. `.after(X).before_all()` causes this to be first rule after X.
310    /// ```
311    /// use markdown_it::common::ruler::Ruler;
312    /// let mut chain = Ruler::<&str, fn (&mut String)>::new();
313    ///
314    /// chain.add("a", |s| s.push_str("A"));
315    /// chain.add("c", |s| s.push_str("C")).after("a");
316    /// chain.add("b", |s| s.push_str("B")).after("a").before_all();
317    ///
318    /// let mut result = String::new();
319    /// for f in chain.iter() { f(&mut result); }
320    /// // without before_all order will be ACB
321    /// assert_eq!(result, "ABC");
322    /// ```
323    pub fn before_all(&mut self) -> &mut Self {
324        self.prio = RuleItemPriority::BeforeAll;
325        self
326    }
327
328    /// This rule will be inserted as late as possible, while still taking into account dependencies,
329    /// i.e. `.before(X).after_all()` causes this to be last rule before X.
330    /// Similar to [RuleItem::before_all](RuleItem::before_all).
331    pub fn after_all(&mut self) -> &mut Self {
332        self.prio = RuleItemPriority::AfterAll;
333        self
334    }
335
336    /// Add another auxiliary identifier to this rule. It can be used to group together multiple
337    /// rules with similar functionality.
338    /// ```
339    /// use markdown_it::common::ruler::Ruler;
340    /// let mut chain = Ruler::<&str, fn (&mut String)>::new();
341    ///
342    /// chain.add("b", |s| s.push_str("B")).alias("BorC");
343    /// chain.add("c", |s| s.push_str("C")).alias("BorC");
344    /// chain.add("a", |s| s.push_str("A")).before("BorC");
345    ///
346    /// let mut result = String::new();
347    /// for f in chain.iter() { f(&mut result); }
348    /// assert_eq!(result, "ABC");
349    /// ```
350    pub fn alias(&mut self, mark: M) -> &mut Self {
351        self.marks.push(mark);
352        self
353    }
354
355    /// Require another rule identified by `mark`, panic if not found.
356    pub fn require(&mut self, mark: M) -> &mut Self {
357        self.cons.push(RuleItemConstraint::Require(mark));
358        self
359    }
360}
361
362#[derive(Debug)]
363enum RuleItemConstraint<M> {
364    Before(M),
365    After(M),
366    Require(M),
367}
368
369#[derive(Debug)]
370enum RuleItemPriority {
371    Normal,
372    BeforeAll,
373    AfterAll,
374}
375
376#[cfg(test)]
377mod tests {
378    use super::Ruler;
379
380    #[test]
381    #[should_panic(expected = r#"cyclic dependency: "A" < "B" < "C" < "D" < "E" < "F" < "A""#)]
382    #[cfg(debug_assertions)]
383    fn cyclic_dependency_debug() {
384        let mut r = Ruler::new();
385        r.add("%", ()).after("D");
386        r.add("A", ()).after("B");
387        r.add("E", ()).after("F");
388        r.add("C", ()).after("D");
389        r.add("B", ()).after("C");
390        r.add("D", ()).after("E");
391        r.add("F", ()).after("A");
392        r.compile();
393    }
394
395    #[test]
396    #[should_panic(expected = r#"cyclic dependency"#)]
397    fn cyclic_dependency() {
398        let mut r = Ruler::new();
399        r.add("A", ()).after("B");
400        r.add("B", ()).after("C");
401        r.add("C", ()).after("A");
402        r.compile();
403    }
404
405    #[test]
406    #[should_panic(expected = r#"missing dependency: "C" requires "Z"#)]
407    fn missing_require() {
408        let mut r = Ruler::new();
409        r.add("A", ());
410        r.add("B", ()).require("A");
411        r.add("C", ()).require("Z");
412        r.compile();
413    }
414}