unidok_repr/into_ir/
macros.rs1use std::{iter, mem};
2
3use detached_str::StrSlice;
4
5use crate::ast::macros::*;
6use crate::ast::AstData;
7use crate::config::HeadingAnchor;
8use crate::ir::blocks::{AnnBlock, Block};
9use crate::ir::html::HtmlNode;
10use crate::ir::macros::{Attr, AttrValue, Footnote, Macro};
11use crate::ir::segments::Segment;
12use crate::IntoIR;
13
14impl<'a> IntoIR<'a> for BlockMacro {
15 type IR = AnnBlock<'a>;
16
17 fn into_ir(self, text: &'a str, data: &mut AstData) -> Self::IR {
18 let mut block = self.content.into_ir(text, data);
19 let r#macro = MacroAst { name: self.name, args: self.args }.into_ir(text, data);
20
21 if r#macro.is_for_list() {
22 if let AnnBlock { block: Block::List(list), .. } = &mut block {
23 list.macros.push(r#macro);
24 }
25 } else {
26 block.macros.push(r#macro);
27 }
28
29 block
30 }
31}
32
33impl<'a> IntoIR<'a> for InlineMacroAst {
34 type IR = Segment<'a>;
35
36 fn into_ir(self, text: &'a str, data: &mut AstData) -> Self::IR {
37 let mut segment = (*self.segment).into_ir(text, data);
38 let r#macro = MacroAst { name: self.name, args: self.args };
39 match &mut segment {
40 Segment::Braces(b) => b.macros.push(r#macro.into_ir(text, data)),
41 Segment::Math(b) => b.macros.push(r#macro.into_ir(text, data)),
42 Segment::Link(b) => b.macros.push(r#macro.into_ir(text, data)),
43 Segment::Image(b) => b.macros.push(r#macro.into_ir(text, data)),
44 Segment::Code(b) => b.macros.push(r#macro.into_ir(text, data)),
45 Segment::InlineHtml(HtmlNode::Element(b)) => b.macros.push(r#macro.into_ir(text, data)),
46
47 _ => {}
48 }
49 segment
50 }
51}
52
53impl<'a> IntoIR<'a> for BlockMacroContent {
54 type IR = AnnBlock<'a>;
55
56 fn into_ir(self, text: &'a str, data: &mut AstData) -> Self::IR {
57 match self {
58 BlockMacroContent::Prefixed(p) => (*p).into_ir(text, data),
59 BlockMacroContent::Braces(b) => {
60 AnnBlock { macros: vec![], block: Block::Braces(b.into_ir(text, data)) }
61 }
62 BlockMacroContent::None => AnnBlock { macros: vec![], block: Block::Empty },
63 }
64 }
65}
66
67struct MacroAst {
68 name: StrSlice,
69 args: Option<MacroArgs>,
70}
71
72impl<'a> IntoIR<'a> for MacroAst {
73 type IR = Macro<'a>;
74
75 fn into_ir(self, text: &'a str, data: &mut AstData) -> Self::IR {
76 match self.name.to_str(text) {
77 "" => {
78 if let Some(MacroArgs::TokenTrees(tts)) = self.args {
79 if tts.is_empty() {
80 return Macro::Invalid;
81 }
82 let mut result = Vec::new();
83
84 for tt in tts {
85 match tt {
86 TokenTree::Atom(TokenTreeAtom::Word(arg)) => {
87 let arg = arg.to_str(text);
88 if let Some(arg) = arg.strip_prefix('.') {
89 result.push(Attr {
90 key: "class",
91 value: Some(AttrValue::Word(arg)),
92 });
93 } else if let Some(arg) = arg.strip_prefix('#') {
94 result.push(Attr {
95 key: "id",
96 value: Some(AttrValue::Word(arg)),
97 });
98 } else {
99 result.push(Attr { key: arg, value: None })
100 }
101 }
102 TokenTree::Atom(TokenTreeAtom::QuotedWord(word)) => result.push(Attr {
103 key: "style",
104 value: Some(AttrValue::QuotedWord(word)),
105 }),
106 TokenTree::KV(key, TokenTreeAtom::Word(word)) => {
107 let key = key.to_str(text);
108 let word = word.to_str(text);
109 result.push(Attr { key, value: Some(AttrValue::Word(word)) })
110 }
111 TokenTree::KV(key, TokenTreeAtom::QuotedWord(word)) => {
112 let key = key.to_str(text);
113 result.push(Attr { key, value: Some(AttrValue::QuotedWord(word)) })
114 }
115 _ => return Macro::Invalid,
116 }
117 }
118
119 Macro::HtmlAttrs(result)
120 } else {
121 Macro::Invalid
122 }
123 }
124 "TOC" => {
125 if self.args.is_none() {
126 Macro::Toc
127 } else {
128 Macro::Invalid
129 }
130 }
131 "NOTOC" => {
132 if self.args.is_none() {
133 Macro::NoToc
134 } else {
135 Macro::Invalid
136 }
137 }
138 "NOTXT" => {
139 if self.args.is_none() {
140 Macro::NoText
141 } else {
142 Macro::Invalid
143 }
144 }
145 "LOOSE" => {
146 if self.args.is_none() {
147 Macro::Loose
148 } else {
149 Macro::Invalid
150 }
151 }
152 "BULLET" => {
153 if let Some(MacroArgs::TokenTrees(tts)) = self.args {
154 if tts.is_empty() {
155 return Macro::Invalid;
156 }
157 let mut style = String::new();
158
159 for tt in tts {
160 if let TokenTree::Atom(atom) = tt {
161 match atom {
162 TokenTreeAtom::Word(word) => {
163 style.push_str(word.to_str(text));
164 style.push(' ');
165 }
166 TokenTreeAtom::QuotedWord(word) => {
167 style.push('"');
168 style.extend(word.chars().flat_map(|c| {
169 iter::once('\\')
170 .filter(move |_| matches!(c, '"' | '\'' | '\\'))
171 .chain(iter::once(c))
172 }));
173 style.push_str("\" ");
174 }
175 _ => return Macro::Invalid,
176 }
177 } else {
178 return Macro::Invalid;
179 }
180 }
181 if style.ends_with(' ') {
182 style.pop();
183 }
184
185 Macro::ListStyle(style)
186 } else {
187 Macro::Invalid
188 }
189 }
190 "MATH_SCRIPT" => {
191 if self.args.is_none() {
192 Macro::MathScript
193 } else {
194 Macro::Invalid
195 }
196 }
197 "BLANK" => {
198 if self.args.is_none() {
199 Macro::Blank
200 } else {
201 Macro::Invalid
202 }
203 }
204 "FOOTNOTES" => {
205 if self.args.is_none() {
206 if data.footnotes.is_empty() {
207 Macro::Footnotes(vec![])
208 } else {
209 let links = mem::take(&mut data.footnotes);
210 let footnotes = links
211 .into_iter()
212 .flat_map(|link| {
213 let num = data.next_footnote_def;
214 data.next_footnote_def += 1;
215
216 link.text.map(|t| Footnote { num, text: t.into_ir(text, data) })
217 })
218 .collect();
219 Macro::Footnotes(footnotes)
220 }
221 } else {
222 Macro::Invalid
223 }
224 }
225 "CONFIG" => {
226 if let Some(MacroArgs::TokenTrees(args)) = self.args {
227 for arg in args {
228 if let TokenTree::KV(key, value) = arg {
229 match key.to_str(text) {
230 "heading_anchor" => match value.as_str(text) {
231 Some("start" | "before") => {
232 data.config.heading_anchor = HeadingAnchor::Start
233 }
234 Some("end" | "after") => {
235 data.config.heading_anchor = HeadingAnchor::End
236 }
237 Some("none" | "no" | "false") => {
238 data.config.heading_anchor = HeadingAnchor::None
239 }
240 _ => {}
241 },
242 "lang" => {
243 if let Some(value) = value.as_str(text) {
244 if let Ok(quote_style) = value.parse() {
245 data.config.quote_style = quote_style;
246 } else {
247 return Macro::Invalid;
248 }
249 } else {
250 return Macro::Invalid;
251 }
252 }
253 _ => return Macro::Invalid,
254 }
255 }
256 }
257 Macro::Config
258 } else {
259 Macro::Invalid
260 }
261 }
262 "INCLUDE" => {
263 if let Some(MacroArgs::Raw(path)) = self.args {
264 Macro::Include(path.to_str(text))
265 } else {
266 Macro::Invalid
267 }
268 }
269 _ => Macro::Invalid,
270 }
271 }
272}