sphinx_ultra/doctree/
mod.rs1pub mod ids;
14pub mod kinds;
15pub mod messages;
16pub mod pformat;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct Span {
22 pub source: u16,
23 pub start: u32,
24 pub end: u32,
25}
26
27impl Span {
28 pub const ZERO: Span = Span {
29 source: 0,
30 start: 0,
31 end: 0,
32 };
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum AttrValue {
40 Int(i64),
41 Str(String),
42}
43
44#[derive(Debug, Clone, Default, PartialEq, Eq)]
49pub struct Attrs {
50 pub ids: Vec<String>,
51 pub names: Vec<String>,
52 pub dupnames: Vec<String>,
53 pub classes: Vec<String>,
54 pub backrefs: Vec<String>,
55 pub extra: Vec<(&'static str, AttrValue)>,
57}
58
59#[derive(Debug, Clone, PartialEq)]
62pub struct Node {
63 pub kind: &'static str,
64 pub span: Span,
65 pub text: Option<String>,
66 pub attrs: Attrs,
67 pub children: Vec<Node>,
68}
69
70impl Node {
71 pub fn elem(kind: &'static str, span: Span) -> Node {
72 Node {
73 kind,
74 span,
75 text: None,
76 attrs: Attrs::default(),
77 children: Vec::new(),
78 }
79 }
80
81 pub fn text_node(s: impl Into<String>, span: Span) -> Node {
82 Node {
83 kind: kinds::TEXT,
84 span,
85 text: Some(s.into()),
86 attrs: Attrs::default(),
87 children: Vec::new(),
88 }
89 }
90
91 pub fn set(&mut self, key: &'static str, value: AttrValue) {
94 match self.attrs.extra.binary_search_by(|(k, _)| k.cmp(&key)) {
95 Ok(i) => self.attrs.extra[i].1 = value,
96 Err(i) => self.attrs.extra.insert(i, (key, value)),
97 }
98 }
99
100 pub fn get(&self, key: &'static str) -> Option<&AttrValue> {
101 self.attrs
102 .extra
103 .binary_search_by(|(k, _)| k.cmp(&key))
104 .ok()
105 .map(|i| &self.attrs.extra[i].1)
106 }
107
108 pub fn astext(&self) -> String {
115 match &self.text {
116 Some(t) => t.clone(),
117 None => self.children.iter().map(Node::astext).collect(),
118 }
119 }
120
121 pub fn pformat(&self) -> String {
123 pformat::pformat(self)
124 }
125}
126
127#[derive(Debug, Clone, PartialEq)]
129pub struct Doctree {
130 pub root: Node,
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn elem_constructs_with_kind_and_span() {
139 let n = Node::elem(
140 kinds::PARAGRAPH,
141 Span {
142 source: 0,
143 start: 0,
144 end: 10,
145 },
146 );
147 assert_eq!(n.kind, "paragraph");
148 assert!(n.text.is_none());
149 assert!(n.children.is_empty());
150 }
151
152 #[test]
153 fn text_node_holds_text() {
154 let t = Node::text_node(
155 "hello",
156 Span {
157 source: 0,
158 start: 0,
159 end: 5,
160 },
161 );
162 assert_eq!(t.kind, kinds::TEXT);
163 assert_eq!(t.text.as_deref(), Some("hello"));
164 }
165
166 #[test]
167 fn set_keeps_extra_sorted_and_get_finds() {
168 let mut n = Node::elem(kinds::TARGET, Span::ZERO);
169 n.set("refuri", AttrValue::Str("https://x/".into()));
170 n.set("anonymous", AttrValue::Int(1));
171 assert_eq!(n.attrs.extra[0].0, "anonymous");
172 assert_eq!(n.get("refuri"), Some(&AttrValue::Str("https://x/".into())));
173 n.set("refuri", AttrValue::Str("https://y/".into()));
174 assert_eq!(n.attrs.extra.len(), 2);
175 assert_eq!(n.get("refuri"), Some(&AttrValue::Str("https://y/".into())));
176 }
177
178 #[test]
179 fn astext_joins_text_descendants() {
180 let mut p = Node::elem(kinds::PARAGRAPH, Span::ZERO);
181 p.children
182 .push(Node::text_node("line one\nline two", Span::ZERO));
183 assert_eq!(p.astext(), "line one\nline two");
184 }
185}