1use std::collections::BTreeMap;
4
5use mos_core::{AttrMap, AttrValue, Document, NodeId, NodeKind, NodeSpec, SourceSpan};
6use mos_parse::{Item, ListItem};
7
8use crate::inline::lower_inlines;
9
10pub(super) fn lower_list(
15 doc: &mut Document,
16 parent: NodeId,
17 ordered: bool,
18 items: &[ListItem],
19 span: &SourceSpan,
20) {
21 let mut attributes: AttrMap = BTreeMap::new();
22 attributes.insert("ordered".to_owned(), AttrValue::Bool(ordered));
23 let list_id = doc.alloc_child(
24 parent,
25 NodeSpec::new(NodeKind::List, span.clone()).with_attributes(attributes),
26 );
27 for item in items {
28 lower_list_item(doc, list_id, item);
29 }
30}
31
32fn lower_list_item(doc: &mut Document, parent: NodeId, item: &ListItem) {
33 let item_id = doc.alloc_child(parent, NodeSpec::new(NodeKind::ListItem, item.span.clone()));
34 lower_inlines(doc, item_id, &item.inlines);
35 for child in &item.children {
36 if let Item::List {
37 ordered,
38 items,
39 span,
40 } = child
41 {
42 lower_list(doc, item_id, *ordered, items, span);
43 }
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 #![allow(
50 clippy::unwrap_used,
51 clippy::expect_used,
52 clippy::panic,
53 reason = "tests panic loudly on setup failure; matches crate-wide test-module convention"
54 )]
55
56 use std::path::PathBuf;
57
58 use mos_core::{AttrValue, Node, NodeKind};
59
60 use crate::lower;
61
62 #[test]
63 fn lowers_unordered_list() {
64 let r = lower("- one\n- two\n", &PathBuf::from("test.mos"));
65 assert!(!r.has_errors(), "{:?}", r.diagnostics);
66 let list = r
67 .document
68 .nodes()
69 .find(|n| n.kind == NodeKind::List)
70 .expect("list node");
71 assert_eq!(
72 list.attributes.get("ordered"),
73 Some(&AttrValue::Bool(false))
74 );
75 assert_eq!(list.children.len(), 2);
76 let items: Vec<&Node> = list
77 .children
78 .iter()
79 .filter_map(|id| r.document.get(*id))
80 .collect();
81 assert!(items.iter().all(|n| n.kind == NodeKind::ListItem));
82 for (item, expected) in items.iter().zip(["one", "two"]) {
83 let text_child = item
84 .children
85 .iter()
86 .filter_map(|id| r.document.get(*id))
87 .find(|n| n.kind == NodeKind::Text)
88 .expect("text child");
89 assert_eq!(
90 text_child.attributes.get("text"),
91 Some(&AttrValue::Str(expected.to_owned()))
92 );
93 }
94 }
95
96 #[test]
97 fn lowers_ordered_list_flag() {
98 let r = lower("1. a\n2. b\n", &PathBuf::from("test.mos"));
99 assert!(!r.has_errors(), "{:?}", r.diagnostics);
100 let list = r
101 .document
102 .nodes()
103 .find(|n| n.kind == NodeKind::List)
104 .expect("list node");
105 assert_eq!(list.attributes.get("ordered"), Some(&AttrValue::Bool(true)));
106 }
107
108 #[test]
109 fn lowers_nested_list_as_listitem_child() {
110 let r = lower("- outer\n - inner\n", &PathBuf::from("test.mos"));
111 assert!(!r.has_errors(), "{:?}", r.diagnostics);
112 let outer = r
113 .document
114 .nodes()
115 .find(|n| n.kind == NodeKind::List)
116 .expect("outer list");
117 let outer_item = r.document.get(outer.children[0]).unwrap();
118 let nested = outer_item
119 .children
120 .iter()
121 .filter_map(|id| r.document.get(*id))
122 .find(|n| n.kind == NodeKind::List)
123 .expect("nested list");
124 assert_eq!(nested.children.len(), 1);
125 let nested_item = r.document.get(nested.children[0]).unwrap();
126 assert_eq!(nested_item.kind, NodeKind::ListItem);
127 }
128}