use std::io::{self, Read, BufRead, Write};
pub struct Converter {
state: State,
blank_line_count: usize,
buffered_lines: String,
warnings: Vec<Warning>,
}
use super::Warning;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum State { MarkdownBlank, MarkdownText, MarkdownMeta, Rust, }
impl Converter {
pub fn new() -> Converter {
Converter {
state: State::MarkdownBlank,
blank_line_count: 0,
buffered_lines: String::new(),
warnings: vec![],
}
}
}
pub enum Exception {
IoError(io::Error),
Warnings(Vec<Warning>),
}
impl From<io::Error> for Exception {
fn from(e: io::Error) -> Self {
Exception::IoError(e)
}
}
impl Converter {
pub fn convert<R:io::Read, W:io::Write>(mut self, r:R, mut w:W) -> Result<(), Exception> {
let source = io::BufReader::new(r);
for line in source.lines() {
let line = try!(line);
try!(self.handle(&line, &mut w));
}
if self.warnings.len() == 0 {
Ok(())
} else {
Err(Exception::Warnings(self.warnings))
}
}
pub fn handle(&mut self, line: &str, w: &mut Write) -> io::Result<()> {
match (self.state, &line.chars().take(7).collect::<String>()[..]) {
(State::MarkdownBlank, "```rust") |
(State::MarkdownText, "```rust") => {
self.buffered_lines = String::new();
let rest = &line.chars().skip(7).collect::<String>();
if rest != "" {
try!(self.transition(w, State::MarkdownMeta));
try!(self.meta_note(&rest, w));
}
self.transition(w, State::Rust)
}
(State::Rust, "```") => {
self.transition(w, State::MarkdownBlank)
}
(_, "") => {
self.blank_line(w)
}
_ => {
let open_pat = "[";
let close_pat = "]: https://play.rust-lang.org/?code=";
if let (Some(open), Some(close)) = (line.find(open_pat), line.find(close_pat)) {
let expect = super::encode_to_url(&self.buffered_lines);
let actual = &line[(close+3)..];
if expect != actual {
self.warnings.push(Warning::EncodedUrlMismatch {
actual: actual.to_string(),
expect: expect
})
}
self.name_block(line, &line[open+1..close], w)
} else {
self.nonblank_line(line, w)
}
}
}
}
pub fn meta_note(&mut self, note: &str, w: &mut Write) -> io::Result<()> {
assert!(note != "");
self.nonblank_line(note, w)
}
pub fn name_block(&mut self, _line: &str, name: &str, w: &mut Write) -> io::Result<()> {
assert!(name != "");
writeln!(w, "//@@@ {}", name)
}
pub fn nonblank_line(&mut self, line: &str, w: &mut Write) -> io::Result<()> {
let (blank_prefix, line_prefix) = match self.state {
State::MarkdownBlank => ("", "//@ "),
State::MarkdownText => ("//@", "//@ "),
State::MarkdownMeta => ("//@", "//@@"),
State::Rust => ("", ""),
};
for _ in 0..self.blank_line_count {
try!(writeln!(w, "{}", blank_prefix));
}
self.blank_line_count = 0;
match self.state {
State::MarkdownBlank =>
try!(self.transition(w, State::MarkdownText)),
State::MarkdownMeta => {}
State::MarkdownText => {}
State::Rust => {
self.buffered_lines.push_str("\n");
self.buffered_lines.push_str(line);
}
}
writeln!(w, "{}{}", line_prefix, line)
}
fn blank_line(&mut self, _w: &mut Write) -> io::Result<()> {
self.buffered_lines.push_str("\n");
self.blank_line_count += 1;
Ok(())
}
fn finish_section(&mut self, w: &mut Write) -> io::Result<()> {
for _ in 0..self.blank_line_count {
try!(writeln!(w, ""));
}
self.blank_line_count = 0;
Ok(())
}
fn transition(&mut self, w: &mut Write, s: State) -> io::Result<()> {
match s {
State::MarkdownMeta => {
assert!(self.state != State::Rust);
try!(self.finish_section(w));
}
State::Rust => {
assert!(self.state != State::Rust);
self.buffered_lines = String::new();
}
State::MarkdownText => {
assert_eq!(self.state, State::MarkdownBlank);
try!(self.finish_section(w));
}
State::MarkdownBlank => {
assert_eq!(self.state, State::Rust);
try!(self.finish_section(w));
}
}
self.state = s;
Ok(())
}
}