equt_md_ext/
lib.rs

1//! # Extended Markdown Iterator
2//!
3//! This crate wraps the event iterator created by [`equt_md`] to provide a more general
4//! while powerful interface.
5//!
6//! ## Difference between those crates
7//!
8//! - [`equt_md`] parses the markdown into event iterator.
9//! - [`equt_md_frontmatter`] parse the markdown frontmatter into Rust data structure.
10//! - [`equt_md_error`] contains all errors in these crates.
11//! - [`equt_md_ext`] wraps and extends the crates above for easy using.
12//! - [`equt_md_html`] renders the iterator into HTML.
13//!
14//! [`equt_md`]: https://docs.rs/equt-md/*/equt_md
15//! [`equt_md_frontmatter`]: https://docs.rs/equt-md-frontmatter/*/equt_md_frontmatter
16//! [`equt_md_error`]: https://docs.rs/equt-md-error/*/equt_md_error
17//! [`equt_md_ext`]: https://docs.rs/equt-md-ext/*/equt_md_ext
18//! [`equt_md_html`]: https://docs.rs/equt-md-html/*/equt_md_html
19mod after;
20mod before;
21mod bottom;
22mod head;
23mod link;
24mod parser;
25mod raw;
26mod share;
27mod tail;
28mod within;
29
30pub use after::After;
31pub use before::Before;
32pub use bottom::Bottom;
33pub use equt_md::{Alignment, CodeBlockKind, CowStr, Event, LinkType, Options, Tag};
34pub use equt_md_error as error;
35pub use equt_md_frontmatter as frontmatter;
36pub use error::Result;
37pub use head::Head;
38pub use link::Link;
39pub use parser::Parser;
40pub use raw::Raw;
41pub use tail::Tail;
42pub use within::Within;
43pub use share::Share;
44
45#[macro_use]
46extern crate derive_new;
47use equt_md_frontmatter::FrontMatter;
48use std::cell::{Ref, RefCell};
49use std::rc::Rc;
50
51/// Extending the original markdown events iterator
52///
53/// All provided methods in this trait will keep the frontmatter data internally.
54///
55/// Any closures that needs frontmatter as an argument are lazy. With the laziness, the
56/// frontmatter doesn't have to be presented first, which means it could be updated or
57/// get replaced during the process.
58#[must_use = "iterators are lazy and do nothing unless consumed"]
59pub trait MarkdownExt<T>: Iterator<Item = T> + Sized {
60    /// Different from a normal iterator, `MarkdownExt` must own the frontmatter
61    /// data.
62    fn frontmatter(&mut self) -> &mut Share<RefCell<Option<FrontMatter>>>;
63
64    /// Inspect the current frontmatter
65    ///
66    /// ## Example
67    ///
68    /// ```
69    /// # use equt_md_ext::{Bottom, frontmatter::FrontMatter, MarkdownExt};
70    /// # fn e() -> impl MarkdownExt<u8> {
71    /// # let events: Bottom<u8> = Bottom::new();
72    /// let mut fm: Option<FrontMatter> = None;
73    /// events.inspect_frontmatter(&mut fm)
74    /// # }
75    /// ```
76    ///
77    /// The result would be a clone so it's still available even after the whole
78    /// [`MarkdownExt`] chain has been consumed.
79    ///
80    /// [`MarkdownExt`]: trait.MarkdownExt.html
81    fn inspect_frontmatter(mut self, frontmatter: &mut Option<FrontMatter>) -> Raw<Self, T> {
82        // 3. Create a new iterator with data
83        let curr = Rc::try_unwrap(self.frontmatter().transfer().unwrap())
84            .unwrap()
85            .into_inner();
86        *frontmatter = curr.clone();
87        Raw::new(Rc::new(RefCell::new(curr)).into(), self)
88    }
89
90    /// Place events after the _first_ occurence of a certain event.
91    ///
92    /// ## Example
93    ///
94    /// ```
95    /// # use equt_md_ext::{Bottom, Event, Tag, frontmatter::FrontMatter, MarkdownExt};
96    /// # use std::cell::Ref;
97    /// # use std::iter::{empty, Empty};
98    /// # let events = Bottom::new();
99    /// # fn days_since_last_update<'e>(_: Ref<Option<FrontMatter>>) -> Empty<Event<'e>> { empty() }
100    /// let outdate = events.after(|e| match &e {
101    ///     Event::End(Tag::Heading(1)) => true,
102    ///     _ => false,
103    /// }, |frontmatter| days_since_last_update(frontmatter));
104    /// ```
105    ///
106    /// Assume the `days_since_last_update` could help create a series of events that notify
107    /// the reader of the number of the days since the last update. The `after` could place the
108    /// new events right after the first heading.
109    fn after<P, F, S, G>(mut self, after: P, f: F) -> After<Self, S, G, F, P, T>
110    where
111        P: Fn(&T) -> bool,
112        S: Iterator<Item = T>,
113        G: IntoIterator<Item = T, IntoIter = S>,
114        F: Fn(Ref<Option<FrontMatter>>) -> G,
115    {
116        After::new(
117            self.frontmatter().transfer().unwrap().into(),
118            self,
119            after,
120            f,
121        )
122    }
123
124    /// Place events before the first occurence of a certain event.
125    ///
126    /// ## Example
127    ///
128    /// ```
129    /// # use equt_md_ext::{Bottom, Event, Tag, frontmatter::FrontMatter, MarkdownExt};
130    /// # use std::cell::Ref;
131    /// # use std::iter::{empty, Empty};
132    /// # let events = Bottom::new();
133    /// # fn disclaimer<'e>(_: Ref<Option<FrontMatter>>) -> Empty<Event<'e>> { empty() }
134    /// let official = events.before(|e| match &e {
135    ///     Event::Start(Tag::Heading(1)) => true,
136    ///     _ => false,
137    /// }, |frontmatter| disclaimer(frontmatter));
138    /// ```
139    ///
140    /// Assume the `disclaimer` could create some events and disclaim something important.
141    /// The `before` here places it before the first heading.
142    fn before<P, F, S, G>(mut self, before: P, f: F) -> Before<Self, S, G, F, P, T>
143    where
144        P: Fn(&T) -> bool,
145        S: Iterator<Item = T>,
146        G: IntoIterator<Item = T, IntoIter = S>,
147        F: Fn(Ref<Option<FrontMatter>>) -> G,
148    {
149        Before::new(
150            self.frontmatter().transfer().unwrap().into(),
151            self,
152            before,
153            f,
154        )
155    }
156
157    /// Just like the standard [`chain`] for iterator, the frontmatter will get transferred
158    /// by the following rules.
159    ///
160    /// - If both have frontmatter, the latter one would be picked
161    /// - If neither has frontmatter, it would be `None`
162    /// - Otherwise, the only frontmatter would be used
163    ///
164    /// [`chain`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.chain
165    ///
166    /// ## Example
167    ///
168    /// ```
169    /// # use equt_md_ext::{Bottom, Event, Parser, MarkdownExt, Result};
170    /// # use std::cell::Ref;
171    /// # use std::iter::{empty, Empty};
172    /// # fn main() -> Result<()> {
173    /// # let events: Bottom<Event<'_>> = Bottom::new();
174    /// # let text = "";
175    /// let new_events = events.link(Parser::new(text)?);
176    /// # Ok(())
177    /// # }
178    /// ```
179    ///
180    /// New events parsed from `text` will be placed after the original events.
181    fn link<E>(mut self, mut other: E) -> Link<Self, E>
182    where
183        E: MarkdownExt<T>,
184    {
185        // Both should be strong
186        let right = other.frontmatter();
187        let left = self.frontmatter();
188
189        // Set both sides to weak and take the only strong one
190        let strong = if right.upgrade().unwrap().borrow().is_some() {
191            *left = right.downgrade().into();
192            right.transfer().unwrap()
193        } else {
194            *right = left.downgrade().into();
195            left.transfer().unwrap()
196        };
197
198        Link::new(strong.into(), self, other)
199    }
200
201    /// Like [`link`], but accept an [`Iterator`].
202    ///
203    /// For dynamically generating events, consider the [`tail`] method.
204    ///
205    /// [`link`]: trait.MarkdownExt.html#method.link
206    /// [`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
207    /// [`tail`]: trait.MarkdownExt.html#method.tail
208    fn iter<I>(self, iter: I) -> Link<Self, Raw<I, T>>
209    where
210        I: Iterator<Item = T>,
211    {
212        self.link(Raw::new(Rc::new(RefCell::new(None)).into(), iter))
213    }
214
215    /// Place events at the start.
216    ///
217    /// ## Example
218    ///
219    /// ```
220    /// # use equt_md_ext::{Bottom, Event, frontmatter::FrontMatter, MarkdownExt};
221    /// # use std::cell::Ref;
222    /// # use std::iter::{empty, Empty};
223    /// # let events = Bottom::new();
224    /// # fn heading<'e>(_: Ref<Option<FrontMatter>>) -> Empty<Event<'e>> { empty() }
225    /// let with_heading = events.head(|frontmatter| heading(frontmatter));
226    /// ```
227    ///
228    /// Assume `heading` reads the `Title` section in the frontmatter and generate a heading
229    /// event, the `head` will place it at the start of the **current** iterator.
230    fn head<F, G, H>(mut self, f: F) -> Head<Self, H, F, G, T>
231    where
232        H: Iterator<Item = T>,
233        G: IntoIterator<Item = T, IntoIter = H>,
234        F: Fn(Ref<Option<FrontMatter>>) -> G,
235    {
236        Head::new(self.frontmatter().transfer().unwrap().into(), self, f)
237    }
238
239    /// Place events at the end.
240    ///
241    /// ## Example
242    ///
243    /// ```
244    /// # use equt_md_ext::{Bottom, Event, frontmatter::FrontMatter, MarkdownExt};
245    /// # use std::cell::Ref;
246    /// # use std::iter::{empty, Empty};
247    /// # let events = Bottom::new();
248    /// # fn copyright<'e>(_: Ref<Option<FrontMatter>>) -> Empty<Event<'e>> { empty() }
249    /// let with_copyright = events.tail(|frontmatter| copyright(frontmatter));
250    /// ```
251    ///
252    /// Assume `copyright` reads the meta data in the frontmatter and generate copyright related
253    /// event, the `tail` will place it at the end of the **current** iterator.
254    fn tail<F, G, H>(mut self, f: F) -> Tail<Self, H, F, G, T>
255    where
256        H: Iterator<Item = T>,
257        G: IntoIterator<Item = T, IntoIter = H>,
258        F: Fn(Ref<Option<FrontMatter>>) -> G,
259    {
260        Tail::new(self.frontmatter().transfer().unwrap().into(), self, f)
261    }
262
263    /// Maps function upon events within two specific events.
264    ///
265    /// ## Example
266    ///
267    /// ```
268    /// # use equt_md_ext::{Bottom, Event, Tag, MarkdownExt};
269    /// # let events = Bottom::new();
270    /// let uppercase = events.within(|e| match &e {
271    ///     Event::Start(Tag::Heading(_)) => true,
272    ///     _ => false,
273    /// }, |e| match &e {
274    ///     Event::End(Tag::Heading(_)) => true,
275    ///     _ => false,
276    /// }, |_, e| Some(match e {
277    ///     Event::Text(s) => Event::Text(s.into_string().to_uppercase().into()),
278    ///     event => event,
279    /// }));
280    /// ```
281    ///
282    /// The above code will turn all text in heading to uppercase.
283    fn within<P, Q, F>(mut self, start: P, end: Q, f: F) -> Within<Self, P, Q, F, T>
284    where
285        P: Fn(&T) -> bool,
286        Q: Fn(&T) -> bool,
287        F: Fn(Ref<Option<FrontMatter>>, T) -> Option<T>,
288    {
289        Within::new(
290            self.frontmatter().transfer().unwrap().into(),
291            self,
292            start,
293            end,
294            f,
295        )
296    }
297}