1use std::borrow::Cow;
2
3use cmark::Tag as CmTag;
4use cmark::Event as CmEvent;
5use hamlet::Token as HmToken;
6
7pub struct Adapter<'a, I> {
9 cm_iter: I,
10 cm_looka: Option<CmEvent<'a>>, hm_queue: Vec<HmToken<'a>>,
12 group_text: bool,
13 table_head: bool,
14}
15
16impl<'a, I> Adapter<'a, I>
17 where I: Iterator<Item = CmEvent<'a>>
18{
19 pub fn new<T>(iterable: T, group_text: bool) -> Adapter<'a, I>
22 where T: IntoIterator<IntoIter = I, Item = CmEvent<'a>> + 'a
23 {
24 Adapter {
25 cm_iter: iterable.into_iter(),
26 cm_looka: None,
27 hm_queue: Vec::with_capacity(2),
28 group_text: group_text,
29 table_head: false,
30 }
31 }
32
33 fn cm_start_tag(&mut self, tag: CmTag<'a>) -> HmToken<'a> {
34 match tag {
35 CmTag::BlockQuote |
36 CmTag::Code |
37 CmTag::Emphasis |
38 CmTag::Header(_) |
39 CmTag::Item |
40 CmTag::List(None) |
41 CmTag::List(Some(1)) |
42 CmTag::Paragraph |
43 CmTag::Strong |
44 CmTag::Table(_) |
45 CmTag::TableCell |
46 CmTag::TableRow => HmToken::start_tag(self.tag_map(tag), attrs!()),
47 CmTag::CodeBlock(lang) => {
48 self.hm_queue.push(HmToken::start_tag("code", attrs!()));
49 if lang.is_empty() {
50 HmToken::start_tag("pre", attrs!())
51 } else {
52 HmToken::start_tag("pre", attrs!(dataLang = lang))
53 }
54 }
55 CmTag::FootnoteDefinition(_) => unimplemented!(),
56 CmTag::Image(src, title) => {
57 let mut alt = String::from("");
58 while let Some(cm_ev) = self.cm_iter.next() {
59 match cm_ev {
60 CmEvent::Text(text) => alt.push_str(text.as_ref()),
61 CmEvent::End(CmTag::Image(_, _)) => break,
62 CmEvent::Start(CmTag::Image(_, _)) => unreachable!(),
63 _ => (), }
65 }
66 let mut attrs = attrs!(src = src);
67 if !alt.is_empty() {
68 attrs.set("alt", alt);
69 }
70 if !title.is_empty() {
71 attrs.set("title", title);
72 }
73 HmToken::start_tag("img", attrs).closed()
74 }
75 CmTag::Link(href, title) => {
76 let mut attrs = attrs!(href = href);
77 if !title.is_empty() {
78 attrs.set("title", title);
79 }
80 HmToken::start_tag("a", attrs)
81 }
82 CmTag::List(Some(start)) => {
83 HmToken::start_tag("ol", attrs!(start = format!("{}", start)))
84 }
85 CmTag::Rule => {
86 let _ = self.cm_iter.next(); HmToken::start_tag("hr", attrs!()).closed()
88 }
89 CmTag::TableHead => {
90 self.table_head = true;
91 HmToken::start_tag("tr", attrs!())
92 }
93 }
94 }
95
96 fn cm_end_tag(&mut self, tag: CmTag<'a>) -> HmToken<'a> {
97 match tag {
98 CmTag::Rule => unreachable!(),
99 CmTag::BlockQuote |
100 CmTag::Code |
101 CmTag::Emphasis |
102 CmTag::Header(_) |
103 CmTag::Item |
104 CmTag::List(_) |
105 CmTag::Paragraph |
106 CmTag::Strong |
107 CmTag::Table(_) |
108 CmTag::TableCell |
109 CmTag::TableRow => HmToken::end_tag(self.tag_map(tag)),
110 CmTag::CodeBlock(_) => {
111 self.hm_queue.push(HmToken::end_tag("pre"));
112 HmToken::end_tag("code")
113 }
114 CmTag::FootnoteDefinition(_) => unimplemented!(),
115 CmTag::Image(_, _) => unreachable!(),
116 CmTag::Link(_, _) => HmToken::end_tag("a"),
117 CmTag::TableHead => {
118 self.table_head = false;
119 HmToken::end_tag("tr")
120 }
121 }
122 }
123
124 fn cm_text(&mut self, mut s: Cow<'a, str>) -> HmToken<'a> {
125 if self.group_text {
126 while let Some(cm_ev) = self.cm_iter.next() {
127 match cm_ev {
128 CmEvent::Text(text) => s.to_mut().push_str(text.as_ref()),
129 CmEvent::SoftBreak => s.to_mut().push_str("\n"),
130 _ => {
131 self.cm_looka = Some(cm_ev);
132 break;
133 }
134 }
135 }
136 }
137 HmToken::Text(s)
138 }
139
140 fn tag_map(&self, tag: CmTag<'a>) -> Cow<'a, str> {
141 match tag {
142 CmTag::BlockQuote => "blockquote".into(),
143 CmTag::Code => "code".into(),
144 CmTag::Emphasis => "em".into(),
145 CmTag::Header(level) => format!("h{}", level).into(),
146 CmTag::Item => "li".into(),
147 CmTag::List(None) => "ul".into(),
148 CmTag::List(Some(_)) => "ol".into(),
149 CmTag::Paragraph => "p".into(),
150 CmTag::Rule => "hr".into(),
151 CmTag::Strong => "strong".into(),
152 CmTag::Table(_) => "table".into(),
153 CmTag::TableCell => {
154 if self.table_head {
155 "th".into()
156 } else {
157 "td".into()
158 }
159 }
160 CmTag::TableRow => "tr".into(),
161 _ => unreachable!(),
162 }
163 }
164}
165
166impl<'a, I> Iterator for Adapter<'a, I>
167 where I: Iterator<Item = CmEvent<'a>>
168{
169 type Item = HmToken<'a>;
170 fn next(&mut self) -> Option<Self::Item> {
171 if !self.hm_queue.is_empty() {
172 Some(self.hm_queue.remove(0))
173 } else {
174 let cm_ev = if let Some(cm_ev) = self.cm_looka.take() {
175 cm_ev
176 } else if let Some(cm_ev) = self.cm_iter.next() {
177 cm_ev
178 } else {
179 return None;
180 };
181 let hm_ev = match cm_ev {
182 CmEvent::Start(tag) => self.cm_start_tag(tag),
183 CmEvent::End(tag) => self.cm_end_tag(tag),
184 CmEvent::Text(text) => self.cm_text(text),
185 CmEvent::Html(html) | CmEvent::InlineHtml(html) => HmToken::RawText(html),
186 CmEvent::SoftBreak => self.cm_text("\n".into()),
187 CmEvent::HardBreak => HmToken::start_tag("br", attrs!()).closed(),
188 CmEvent::FootnoteReference(_) => unimplemented!(),
189 };
190 Some(hm_ev)
191 }
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use hamlet::Token as HmToken;
198 use cmark::Event as CmEvent;
199 use cmark::Parser;
200 use Adapter;
201 use std::borrow::Cow;
202
203 fn html_skel_map<'a, I>(ada: Adapter<'a, I>) -> Vec<Cow<'a, str>>
204 where I: Iterator<Item = CmEvent<'a>>
205 {
206 ada.map(|hm_ev| {
207 match hm_ev {
208 HmToken::StartTag{name, ..} | HmToken::EndTag{name} => name,
209 HmToken::Text(text) => text,
210 _ => panic!("Bad token {:?}", hm_ev),
211 }
212 })
213 .collect()
214 }
215
216 #[test]
217 fn text_grouping() {
218 let md = "Multi\nLine\nText";
219 let ada = Adapter::new(Parser::new(md), true);
220 let res_vec = html_skel_map(ada);
221 assert_eq!(&["p", md, "p"], &*res_vec);
222 }
223
224 #[test]
225 fn code_grouping() {
226 let code = "Multi\n\nline[2][3]\n\ncode\n\n";
227 let md = String::from("```\n") + code + "```";
228 let ada = Adapter::new(Parser::new(&*md), true);
229 let res_vec = html_skel_map(ada);
230 assert_eq!(&["pre", "code", code, "code", "pre"], &*res_vec);
231 }
232}