use std::io::{self, 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 = (line)?;
(self.handle(&line, &mut w))?;
}
if self.warnings.is_empty() {
Ok(())
} else {
Err(Exception::Warnings(self.warnings))
}
}
pub fn handle(&mut self, line: &str, w: &mut dyn Write) -> io::Result<()> {
let str9 = line.chars().take(9).collect::<String>();
let str7 = line.chars().take(7).collect::<String>();
match (self.state, &str7[..], &str9[..]) {
(State::MarkdownBlank, "```rust", _) |
(State::MarkdownText, "```rust", _) => {
self.buffered_lines = String::new();
let rest = &line.chars().skip(7).collect::<String>();
if rest != "" {
(self.transition(w, State::MarkdownMeta))?;
(self.meta_note(&rest, w))?;
}
self.transition(w, State::Rust)
}
(State::MarkdownBlank, _, "```{.rust") |
(State::MarkdownText, _, "```{.rust") => {
self.buffered_lines = String::new();
let rest = &line.chars().skip(9).collect::<String>();
if rest != "" {
(self.transition(w, State::MarkdownMeta))?;
(self.meta_note(&format!(" {{{}", 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 dyn Write) -> io::Result<()> {
assert!(note != "");
self.nonblank_line(note, w)
}
pub fn name_block(&mut self, _line: &str, name: &str, w: &mut dyn Write) -> io::Result<()> {
assert!(name != "");
writeln!(w, "//@@@ {}", name)
}
pub fn nonblank_line(&mut self, line: &str, w: &mut dyn 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 {
(writeln!(w, "{}", blank_prefix))?;
}
self.blank_line_count = 0;
match self.state {
State::MarkdownBlank =>
(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 dyn Write) -> io::Result<()> {
match self.state {
State::Rust => {
self.buffered_lines.push_str("\n");
}
State::MarkdownBlank |
State::MarkdownMeta |
State::MarkdownText => {}
}
self.blank_line_count += 1;
Ok(())
}
fn finish_section(&mut self, w: &mut dyn Write) -> io::Result<()> {
for _ in 0..self.blank_line_count {
(writeln!(w, ""))?;
}
self.blank_line_count = 0;
Ok(())
}
fn transition(&mut self, w: &mut dyn Write, s: State) -> io::Result<()> {
match s {
State::MarkdownMeta => {
assert!(self.state != State::Rust);
(self.finish_section(w))?;
}
State::Rust => {
assert!(self.state != State::Rust);
self.buffered_lines = String::new();
}
State::MarkdownText => {
assert_eq!(self.state, State::MarkdownBlank);
(self.finish_section(w))?;
}
State::MarkdownBlank => {
assert_eq!(self.state, State::Rust);
(self.finish_section(w))?;
}
}
self.state = s;
Ok(())
}
}