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}