Fix nested lists
parent
122f9d120b
commit
a739b0da61
93
src/lib.rs
93
src/lib.rs
|
@ -19,7 +19,7 @@ pub fn convert(markdown_text: &str) -> String {
|
||||||
md::Event::Start(md::Tag::Heading(level)) => state.start_heading(level),
|
md::Event::Start(md::Tag::Heading(level)) => state.start_heading(level),
|
||||||
md::Event::Start(md::Tag::BlockQuote) => state.start_block_quote(),
|
md::Event::Start(md::Tag::BlockQuote) => state.start_block_quote(),
|
||||||
md::Event::Start(md::Tag::CodeBlock(_)) => state.start_code_block(),
|
md::Event::Start(md::Tag::CodeBlock(_)) => state.start_code_block(),
|
||||||
md::Event::Start(md::Tag::List(_)) => (),
|
md::Event::Start(md::Tag::List(_)) => state.start_list(),
|
||||||
md::Event::Start(md::Tag::Item) => state.start_list_item(),
|
md::Event::Start(md::Tag::Item) => state.start_list_item(),
|
||||||
md::Event::Start(md::Tag::FootnoteDefinition(_)) => {
|
md::Event::Start(md::Tag::FootnoteDefinition(_)) => {
|
||||||
unimplemented!("footnotes disabled")
|
unimplemented!("footnotes disabled")
|
||||||
|
@ -39,7 +39,7 @@ pub fn convert(markdown_text: &str) -> String {
|
||||||
md::Event::End(md::Tag::BlockQuote) => (),
|
md::Event::End(md::Tag::BlockQuote) => (),
|
||||||
md::Event::End(md::Tag::CodeBlock(_)) => state.finish_node(),
|
md::Event::End(md::Tag::CodeBlock(_)) => state.finish_node(),
|
||||||
md::Event::End(md::Tag::List(_)) => state.finish_list(),
|
md::Event::End(md::Tag::List(_)) => state.finish_list(),
|
||||||
md::Event::End(md::Tag::Item) => state.finish_node(),
|
md::Event::End(md::Tag::Item) => state.finish_item(),
|
||||||
md::Event::End(md::Tag::FootnoteDefinition(_)) => unimplemented!("footnotes disabled"),
|
md::Event::End(md::Tag::FootnoteDefinition(_)) => unimplemented!("footnotes disabled"),
|
||||||
md::Event::End(md::Tag::Table(_)) => state.finish_table_building(),
|
md::Event::End(md::Tag::Table(_)) => state.finish_table_building(),
|
||||||
md::Event::End(md::Tag::TableHead) => (),
|
md::Event::End(md::Tag::TableHead) => (),
|
||||||
|
@ -118,6 +118,8 @@ struct State {
|
||||||
link_text_stack: Vec<String>,
|
link_text_stack: Vec<String>,
|
||||||
table: Vec<Vec<String>>,
|
table: Vec<Vec<String>>,
|
||||||
building_table: bool,
|
building_table: bool,
|
||||||
|
nested_list_level: Option<u8>,
|
||||||
|
list_items: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
|
@ -130,6 +132,8 @@ impl State {
|
||||||
link_text_stack: vec![],
|
link_text_stack: vec![],
|
||||||
table: vec![],
|
table: vec![],
|
||||||
building_table: false,
|
building_table: false,
|
||||||
|
nested_list_level: None,
|
||||||
|
list_items: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,8 +170,16 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn start_list(&mut self) {
|
||||||
|
let level = match self.nested_list_level {
|
||||||
|
Some(n) => n + 1,
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
self.nested_list_level = Some(level);
|
||||||
|
}
|
||||||
|
|
||||||
fn start_list_item(&mut self) {
|
fn start_list_item(&mut self) {
|
||||||
self.pending_node_type = NodeType::ListItem;
|
self.list_items.push(String::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_emphasis(&mut self) {
|
fn toggle_emphasis(&mut self) {
|
||||||
|
@ -191,6 +203,12 @@ impl State {
|
||||||
self.pending_node_content += "[image: ";
|
self.pending_node_content += "[image: ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn finish_item(&mut self) {
|
||||||
|
if self.nested_list_level.is_none() {
|
||||||
|
self.finish_node();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn finish_table_building(&mut self) {
|
fn finish_table_building(&mut self) {
|
||||||
let mut table = Table::new();
|
let mut table = Table::new();
|
||||||
|
|
||||||
|
@ -209,14 +227,31 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_list(&mut self) {
|
fn finish_list(&mut self) {
|
||||||
self.nodes.push(vec![]);
|
let level = match self.nested_list_level {
|
||||||
|
Some(0) => {
|
||||||
|
for item in self.list_items.clone() {
|
||||||
|
self.pending_node_type = NodeType::ListItem;
|
||||||
|
self.pending_node_content = item;
|
||||||
|
self.finish_node();
|
||||||
|
}
|
||||||
|
self.list_items.clear();
|
||||||
|
self.force_links();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Some(n) => Some(n - 1),
|
||||||
|
None => unreachable!("How can you finish a list without level?"),
|
||||||
|
};
|
||||||
|
self.nested_list_level = level;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_link(&mut self, href: &str) {
|
fn end_link(&mut self, href: &str) {
|
||||||
let text = self
|
let text = if self.nested_list_level.is_some() {
|
||||||
.link_text_stack
|
href.to_string()
|
||||||
|
} else {
|
||||||
|
self.link_text_stack
|
||||||
.pop()
|
.pop()
|
||||||
.unwrap_or_else(|| href.to_string());
|
.unwrap_or_else(|| href.to_string())
|
||||||
|
};
|
||||||
self.pending_other.push(gemtext::Node::Link {
|
self.pending_other.push(gemtext::Node::Link {
|
||||||
to: href.to_string(),
|
to: href.to_string(),
|
||||||
name: Some(text),
|
name: Some(text),
|
||||||
|
@ -236,6 +271,11 @@ impl State {
|
||||||
self.pending_node_content += "]";
|
self.pending_node_content += "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn force_links(&mut self) {
|
||||||
|
let last_cluster = self.nodes.last_mut().expect("empty cluster list??");
|
||||||
|
last_cluster.extend(self.pending_other.drain(..));
|
||||||
|
}
|
||||||
|
|
||||||
// will create an empty paragraph if pending_text is empty
|
// will create an empty paragraph if pending_text is empty
|
||||||
fn finish_node(&mut self) {
|
fn finish_node(&mut self) {
|
||||||
match (
|
match (
|
||||||
|
@ -249,13 +289,19 @@ impl State {
|
||||||
let new_node = self.pending_node_type.take().construct(node_text);
|
let new_node = self.pending_node_type.take().construct(node_text);
|
||||||
let last_cluster = self.nodes.last_mut().expect("empty cluster list??");
|
let last_cluster = self.nodes.last_mut().expect("empty cluster list??");
|
||||||
last_cluster.push(new_node);
|
last_cluster.push(new_node);
|
||||||
|
if self.nested_list_level.is_none() {
|
||||||
last_cluster.extend(self.pending_other.drain(..));
|
last_cluster.extend(self.pending_other.drain(..));
|
||||||
|
}
|
||||||
|
|
||||||
self.pending_node_content = String::new();
|
self.pending_node_content = String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_text(&mut self, text: &str) {
|
fn add_text(&mut self, text: &str) {
|
||||||
if self.building_table {
|
if self.nested_list_level.is_some() {
|
||||||
|
if let Some(last) = self.list_items.last_mut() {
|
||||||
|
last.push_str(text);
|
||||||
|
}
|
||||||
|
} else if self.building_table {
|
||||||
if let Some(last_row) = self.table.last_mut() {
|
if let Some(last_row) = self.table.last_mut() {
|
||||||
if let Some(last_cell) = last_row.last_mut() {
|
if let Some(last_cell) = last_row.last_mut() {
|
||||||
last_cell.push_str(&text.split("<br>").collect::<Vec<&str>>().join("\n"));
|
last_cell.push_str(&text.split("<br>").collect::<Vec<&str>>().join("\n"));
|
||||||
|
@ -282,6 +328,12 @@ impl State {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if self.nested_list_level.is_some() {
|
||||||
|
if let Some(last) = self.list_items.last_mut() {
|
||||||
|
last.push_str("`");
|
||||||
|
last.push_str(code);
|
||||||
|
last.push_str("`");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.pending_node_content += "`";
|
self.pending_node_content += "`";
|
||||||
self.pending_node_content += code;
|
self.pending_node_content += code;
|
||||||
|
@ -431,3 +483,28 @@ fn test_multi_tables() {
|
||||||
"#;
|
"#;
|
||||||
assert_eq!(convert(markdown).trim(), gemtext.trim());
|
assert_eq!(convert(markdown).trim(), gemtext.trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[test]
|
||||||
|
fn test_nested_list() {
|
||||||
|
let markdown = r#"
|
||||||
|
- item 1
|
||||||
|
- item 2
|
||||||
|
- subitem 2.1
|
||||||
|
- subitem [2.2](https://example.com)
|
||||||
|
- subitem [2.3](https://example.com)
|
||||||
|
- item 3
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let gemtext = r#"
|
||||||
|
* item 1
|
||||||
|
* item 2
|
||||||
|
* subitem 2.1
|
||||||
|
* subitem 2.2
|
||||||
|
* subitem 2.3
|
||||||
|
* item 3
|
||||||
|
=> https://example.com https://example.com
|
||||||
|
=> https://example.com https://example.com
|
||||||
|
"#;
|
||||||
|
assert_eq!(convert(markdown).trim(), gemtext.trim());
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue