markdown_edit/
lib.rs

1#![cfg_attr(feature = "nightly", deny(missing_docs))]
2#![cfg_attr(feature = "nightly", feature(external_doc))]
3#![cfg_attr(feature = "nightly", doc(include = "../README.md"))]
4#![cfg_attr(test, deny(warnings))]
5
6extern crate pulldown_cmark;
7extern crate pulldown_cmark_to_cmark;
8#[macro_use]
9extern crate failure;
10
11use pulldown_cmark::{Event, Parser, Tag};
12use pulldown_cmark_to_cmark::fmt::cmark;
13use std::{fs, path};
14
15/// The Markdown object.
16pub struct Markdown<'p> {
17  parser: Parser<'p>,
18}
19
20impl<'p> Markdown<'p> {
21  /// Create a new instance.
22  #[inline]
23  pub fn new(raw_md: &'p str) -> Self {
24    Self {
25      parser: Parser::new(raw_md),
26    }
27  }
28
29  /// Replace the body for a header.
30  pub fn replace_body(
31    self,
32    header: &str,
33    section: Vec<Event>,
34  ) -> Result<String, failure::Error> {
35    // Define the internal state machine.
36    let mut inspect_header = false;
37    let mut header_found = false;
38    let mut should_replace_section = false;
39    let mut sections_replaced = false;
40
41    let events: Vec<Event> = {
42      // Remove the unused text.
43      self
44        .parser
45        .into_iter()
46        .flat_map(move |event| {
47          // Find all headers.
48          if let Event::Start(tag) = &event {
49            if let Tag::Header(_text) = tag {
50              inspect_header = true;
51            }
52          }
53
54          // Read the header text.
55          if inspect_header {
56            inspect_header = false;
57            if let Event::Text(text) = &event {
58              if text == header {
59                header_found = true;
60              }
61            }
62          }
63
64          // Edit the body.
65          if should_replace_section {
66            let mut should_continue = true;
67            if let Event::Start(tag) = &event {
68              if let Tag::Header(_) = tag {
69                should_replace_section = false;
70                should_continue = false;
71              }
72            }
73
74            if should_continue {
75              return if !sections_replaced {
76                sections_replaced = true;
77                // FIXME: heh, this is inefficient.
78                section.clone()
79              } else {
80                vec![]
81              };
82            }
83          }
84
85          // Unless specified otherwise, return the event.
86          vec![event]
87        })
88        .collect()
89    };
90
91    if sections_replaced {
92      let mut buf = String::from("");
93      cmark(events.iter(), &mut buf, None)?;
94      Ok(buf)
95    } else if header_found {
96      bail!("No header body found to replace.");
97    } else {
98      bail!("Could not find header");
99    }
100  }
101}
102
103/// Replace
104pub fn replace_body(
105  path: path::PathBuf,
106  header: &str,
107  body: String,
108) -> Result<(), failure::Error> {
109  let target = fs::read_to_string(&path)?;
110  let body: Vec<Event> = Parser::new(&body).into_iter().collect();
111  let parser = Markdown::new(&target);
112  let res = parser.replace_body(header, body)?;
113  fs::write(&path, res)?;
114  Ok(())
115}