witx/docs/
md.rs

1use std::{
2    any::Any,
3    cell::{self, RefCell},
4    fmt,
5    rc::{Rc, Weak},
6};
7
8/// Helper trait which simplifies generation of the Markdown document represented
9/// as a tree of `MdNodeRef`s.
10pub(super) trait ToMarkdown {
11    /// Drives the generation of the `MdNodeRef` tree by either mutating
12    /// the outer (parent) `MdNodeRef`, shared reference to the `MdNode` `node`,
13    /// or spawning new child `MdNodeRef` references to nodes.
14    fn generate(&self, node: MdNodeRef);
15}
16
17/// Interface required for any "content" that is expected to be generated into a
18/// Markdown valid format, hence the constraint of `fmt::Display`.
19///
20/// In essence, any AST element that is meant to be rendered into Markdown, should
21/// define a type implementing this trait.
22pub(super) trait MdElement: fmt::Display + fmt::Debug + 'static {
23    /// Returns `Some(id)` of this `MdElement`. Here `id` is synonym for a Markdown actionable
24    /// link.
25    fn id(&self) -> Option<&str>;
26
27    /// Returns `Some(docs)`, the "docs" of this `MdElement`.
28    fn docs(&self) -> Option<&str>;
29
30    /// Sets `docs`, the "docs" of this `MdElement`.
31    fn set_docs(&mut self, docs: &str);
32
33    fn as_any(&self) -> &dyn Any;
34    fn as_any_mut(&mut self) -> &mut dyn Any;
35}
36
37/// A Markdown node containing:
38/// * the Markdown renderable `content`,
39/// * a weak reference to the `parent` `MdNode` (if any), and
40/// * children `MdNodeRef`s
41///
42/// `content` is expected to implement the `MdElement` trait.
43#[derive(Debug)]
44pub(super) struct MdNode {
45    content: Box<dyn MdElement>,
46    parent: Option<Weak<RefCell<MdNode>>>,
47    children: Vec<MdNodeRef>,
48}
49
50/// Helper function for walking the tree up from some starting `MdNode`, all the way up
51/// to the root of the tree.
52fn walk_ancestors(parent: Option<&Weak<RefCell<MdNode>>>, cb: &mut impl FnMut(MdNodeRef)) {
53    if let Some(parent) = parent.and_then(|x| x.upgrade()) {
54        cb(parent.clone().into());
55        walk_ancestors(parent.borrow().parent.as_ref(), cb)
56    }
57}
58
59impl MdNode {
60    fn new<T: MdElement + 'static>(item: T) -> Self {
61        Self {
62            content: Box::new(item),
63            parent: None,
64            children: vec![],
65        }
66    }
67
68    /// Returns all ancestors of this `MdNode` all the way to the tree's root.
69    pub fn ancestors(&self) -> Vec<MdNodeRef> {
70        let mut ancestors = Vec::new();
71        walk_ancestors(self.parent.as_ref(), &mut |parent| ancestors.push(parent));
72        ancestors
73    }
74
75    /// Returns all children of this `MdNode` in a BFS order.
76    pub fn children(&self) -> Vec<MdNodeRef> {
77        let mut children = self.children.clone();
78        for child in &self.children {
79            children.append(&mut child.borrow().children());
80        }
81        children
82    }
83}
84
85impl fmt::Display for MdNode {
86    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87        self.content.fmt(f)?;
88
89        for child in &self.children {
90            child.fmt(f)?;
91        }
92
93        Ok(())
94    }
95}
96
97/// Helper struct for storing a shared mutable reference to `MdNode`.
98#[derive(Debug)]
99pub(super) struct MdNodeRef(Rc<RefCell<MdNode>>);
100
101impl MdNodeRef {
102    pub fn new<T: MdElement + 'static>(item: T) -> Self {
103        Self(Rc::new(RefCell::new(MdNode::new(item))))
104    }
105
106    /// Spawns new `MdNode` child node, automatically wrapping it in a
107    /// `MdNodeRef` and creating a weak link from child to itself.
108    pub fn new_child<T: MdElement + 'static>(&self, item: T) -> Self {
109        let mut child_node = MdNode::new(item);
110        child_node.parent = Some(Rc::downgrade(&self.0));
111        let child_ref = Self(Rc::new(RefCell::new(child_node)));
112        self.borrow_mut().children.push(child_ref.clone());
113        child_ref
114    }
115
116    pub fn borrow(&self) -> cell::Ref<MdNode> {
117        self.0.borrow()
118    }
119
120    pub fn borrow_mut(&self) -> cell::RefMut<MdNode> {
121        self.0.borrow_mut()
122    }
123
124    /// Returns an immutable reference to `MdNode`'s `content` as-is, that
125    /// is as some type implementing the `MdElement` trait.
126    pub fn any_ref(&self) -> cell::Ref<Box<dyn MdElement>> {
127        cell::Ref::map(self.borrow(), |b| &b.content)
128    }
129
130    /// Returns a mutable reference to `MdNode`'s `content` as-is, that
131    /// is as some type implementing the `MdElement` trait.
132    pub fn any_ref_mut(&self) -> cell::RefMut<Box<dyn MdElement>> {
133        cell::RefMut::map(self.borrow_mut(), |b| &mut b.content)
134    }
135
136    /// Returns a mutable reference to `MdNode`'s `content` cast to some type
137    /// `T` which implements `MdElement` trait.
138    ///
139    /// Panics if `content` cannot be downcast to `T`.
140    pub fn content_ref_mut<T: MdElement + 'static>(&self) -> cell::RefMut<T> {
141        cell::RefMut::map(self.borrow_mut(), |b| {
142            let r = b.content.as_any_mut();
143            r.downcast_mut::<T>().expect("reference is not T type")
144        })
145    }
146}
147
148impl Clone for MdNodeRef {
149    fn clone(&self) -> Self {
150        Self(self.0.clone())
151    }
152}
153
154impl From<Rc<RefCell<MdNode>>> for MdNodeRef {
155    fn from(node: Rc<RefCell<MdNode>>) -> Self {
156        Self(node)
157    }
158}
159
160impl fmt::Display for MdNodeRef {
161    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
162        self.borrow().fmt(f)
163    }
164}
165
166/// Record representing the Markdown tree's root.
167///
168/// Doesn't render to anything.
169#[derive(Debug, Default)]
170pub(super) struct MdRoot;
171
172impl MdElement for MdRoot {
173    fn id(&self) -> Option<&str> {
174        None
175    }
176
177    fn docs(&self) -> Option<&str> {
178        None
179    }
180
181    fn set_docs(&mut self, _: &str) {}
182
183    fn as_any(&self) -> &dyn Any {
184        self
185    }
186
187    fn as_any_mut(&mut self) -> &mut dyn Any {
188        self
189    }
190}
191
192impl fmt::Display for MdRoot {
193    fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result {
194        Ok(())
195    }
196}
197
198/// Helper enum representing either a Markdown header "#" nested at some
199/// `level` down the tree, or a bullet "-" in a list which is idempotent
200/// to changing the nesting level.
201#[derive(Debug, Clone, Copy)]
202pub(super) enum MdHeading {
203    Header { level: usize },
204    Bullet,
205}
206
207impl MdHeading {
208    /// Creates new instance of `MdHeading::Header` variant nested at some
209    /// `level` down the Markdown tree.
210    pub fn new_header(level: usize) -> Self {
211        MdHeading::Header { level }
212    }
213
214    /// Creates new instance of `MdHeading::Bullet` variant.
215    pub fn new_bullet() -> Self {
216        MdHeading::Bullet
217    }
218
219    /// Copies `MdHeading` and if `MdHeading::Header`, pushes it down one
220    /// level in the Markdown tree by incrementing `level`.
221    pub fn new_level_down(&self) -> Self {
222        let mut copy = *self;
223        if let Self::Header { ref mut level } = &mut copy {
224            *level += 1;
225        }
226        copy
227    }
228}
229
230impl fmt::Display for MdHeading {
231    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
232        let as_string = match self {
233            Self::Header { level } => "#".repeat(*level),
234            Self::Bullet => "-".to_owned(),
235        };
236        f.write_str(&as_string)
237    }
238}
239
240/// Record representing a Markdown section without any `docs`, consisting
241/// of only a `header` (e.g., "###"), maybe some referencable `id` (i.e.,
242/// a Markdown link), and some `title`.
243///
244/// Example rendering:
245///
246/// ### Typenames
247///
248#[derive(Debug)]
249pub(super) struct MdSection {
250    pub heading: MdHeading,
251    pub id: Option<String>,
252    pub title: String,
253}
254
255impl MdSection {
256    pub fn new<S: AsRef<str>>(heading: MdHeading, title: S) -> Self {
257        Self {
258            heading,
259            id: None,
260            title: title.as_ref().to_owned(),
261        }
262    }
263}
264
265impl MdElement for MdSection {
266    fn id(&self) -> Option<&str> {
267        self.id.as_ref().map(|s| s.as_str())
268    }
269
270    fn docs(&self) -> Option<&str> {
271        None
272    }
273
274    fn set_docs(&mut self, _: &str) {}
275
276    fn as_any(&self) -> &dyn Any {
277        self
278    }
279
280    fn as_any_mut(&mut self) -> &mut dyn Any {
281        self
282    }
283}
284
285fn gen_link<S: AsRef<str>>(id: S) -> String {
286    format!("<a href=\"#{id}\" name=\"{id}\"></a>", id = id.as_ref())
287}
288
289impl fmt::Display for MdSection {
290    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
291        f.write_fmt(format_args!("{} ", self.heading))?;
292
293        if let Some(id) = &self.id {
294            f.write_fmt(format_args!("{} ", gen_link(id)))?;
295        }
296
297        writeln!(f, "{}", self.title)
298    }
299}
300
301/// Record representing a Markdown section representing any `NamedType` element
302/// of the AST.
303/// Consists of:
304/// * `header`, e.g., "###", or "-" for Enum variants, etc.,
305/// * referencable `id`,
306/// * some `name`, e.g., `errno`,
307/// * `docs` paragraph, and
308/// * maybe `MdType`.
309///
310/// Example rendering (recursive):
311///
312/// ### <a href="#errno" name="errno"></a> `errno`: Enum(`u16`)
313/// Error codes returned by...
314///
315/// #### Variants
316/// - `success` No error occurred...
317/// - `2big` Argument list too long...
318///
319#[derive(Debug)]
320pub(super) struct MdNamedType {
321    pub heading: MdHeading,
322    pub id: String,
323    pub name: String,
324    pub docs: String,
325    pub ty: Option<String>,
326}
327
328impl MdNamedType {
329    pub fn new<S: AsRef<str>>(heading: MdHeading, id: S, name: S, docs: S) -> Self {
330        Self {
331            heading,
332            id: id.as_ref().to_owned(),
333            name: name.as_ref().to_owned(),
334            docs: docs.as_ref().to_owned(),
335            ty: None,
336        }
337    }
338}
339
340impl MdElement for MdNamedType {
341    fn id(&self) -> Option<&str> {
342        Some(&self.id)
343    }
344
345    fn docs(&self) -> Option<&str> {
346        Some(&self.docs)
347    }
348
349    fn set_docs(&mut self, docs: &str) {
350        self.docs = docs.to_owned();
351    }
352
353    fn as_any(&self) -> &dyn Any {
354        self
355    }
356
357    fn as_any_mut(&mut self) -> &mut dyn Any {
358        self
359    }
360}
361
362impl fmt::Display for MdNamedType {
363    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
364        f.write_fmt(format_args!(
365            "{heading} {link} `{name}`",
366            heading = self.heading,
367            link = gen_link(&self.id),
368            name = self.name,
369        ))?;
370
371        if let Some(tt) = &self.ty {
372            f.write_fmt(format_args!(": {}", tt))?;
373        }
374
375        writeln!(f, "\n{}", self.docs)
376    }
377}
378
379/// Record representing a Markdown section representing any `InterfaceFunc` element
380/// of the AST.
381/// Consists of:
382/// * `header`, e.g., "###",
383/// * referencable `id`,
384/// * some `name`, e.g., `path_open`,
385/// * function `inputs`, i.e., arguments,
386/// * function `outputs`, i.e., results, and
387/// * `docs` paragraph.
388///
389/// Example rendering:
390///
391/// ### <a href="#args_get" name="args_get"></a> Fn args_get(argv: `Pointer<Pointer<u8>>`, ...) -> `errno`
392/// Read command-line...
393///
394/// #### Params
395/// - `argv`: `Pointer<Pointer<u8>>` Some docs...
396/// - ...
397///
398/// #### Results
399/// - `error`: `errno` Error code...
400///
401#[derive(Debug)]
402pub(super) struct MdFunc {
403    pub heading: MdHeading,
404    pub id: String,
405    pub name: String,
406    pub inputs: Vec<(String, String)>,
407    pub outputs: Vec<String>,
408    pub docs: String,
409}
410
411impl MdFunc {
412    pub fn new<S: AsRef<str>>(heading: MdHeading, id: S, name: S, docs: S) -> Self {
413        Self {
414            heading,
415            id: id.as_ref().to_owned(),
416            name: name.as_ref().to_owned(),
417            inputs: vec![],
418            outputs: vec![],
419            docs: docs.as_ref().to_owned(),
420        }
421    }
422}
423
424impl MdElement for MdFunc {
425    fn id(&self) -> Option<&str> {
426        Some(&self.id)
427    }
428
429    fn docs(&self) -> Option<&str> {
430        Some(&self.docs)
431    }
432
433    fn set_docs(&mut self, docs: &str) {
434        self.docs = docs.to_owned();
435    }
436
437    fn as_any(&self) -> &dyn Any {
438        self
439    }
440
441    fn as_any_mut(&mut self) -> &mut dyn Any {
442        self
443    }
444}
445
446impl fmt::Display for MdFunc {
447    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
448        // Expand inputs
449        let inputs = self
450            .inputs
451            .iter()
452            .map(|(name, r#type)| format!("{}: {}", name, r#type))
453            .collect::<Vec<_>>()
454            .join(", ");
455        // Expand outputs
456        let outputs: Vec<_> = self
457            .outputs
458            .iter()
459            .map(|r#type| format!("{}", r#type))
460            .collect();
461        let outputs = match outputs.len() {
462            0 => "".to_owned(),
463            1 => format!(" -> {}", outputs[0]),
464            _ => format!(" -> ({})", outputs.join(", ")),
465        };
466        // Format
467        writeln!(f, "\n---\n")?;
468
469        f.write_fmt(format_args!(
470            "{heading} {link} `{name}({inputs}){outputs}`",
471            heading = self.heading,
472            link = gen_link(&self.id),
473            name = self.name,
474            inputs = inputs,
475            outputs = outputs,
476        ))?;
477
478        writeln!(f, "\n{}", self.docs)
479    }
480}