use std::io::{self, BufRead, Write};
use super::encode_to_url;
#[derive(Debug)]
pub struct Converter {
output_state: State,
blank_line_count: usize,
buffered_code: String,
meta_note: Option<String>,
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
enum State { MarkdownFirstLine, MarkdownLines, Rust, }
impl Converter {
pub fn new() -> Converter {
Converter { output_state: State::MarkdownFirstLine,
blank_line_count: 0,
buffered_code: String::new(),
meta_note: None, }
}
}
#[derive(Debug)]
enum Effect<'a> {
BlankLn,
WriteLn(&'a str),
StartCodeBlock,
FinisCodeBlock,
BlankLitComment,
}
#[derive(Debug)]
enum EffectContext<'a> {
Finalize,
NonblankLine(&'a str),
Transition(State),
}
impl Converter {
pub fn convert<R:io::Read, W:io::Write>(&mut self, r:R, mut w:W) -> io::Result<()> {
let source = io::BufReader::new(r);
for line in source.lines() {
let line = (line)?;
(self.handle(&line, &mut w))?;
}
self.finalize(&mut w)
}
pub fn finalize(&mut self, w: &mut dyn Write) -> io::Result<()> {
match self.output_state {
State::Rust =>
self.effect(EffectContext::Finalize, Effect::FinisCodeBlock, w),
State::MarkdownFirstLine |
State::MarkdownLines =>
Ok(())
}
}
pub fn handle(&mut self, line: &str, w: &mut dyn Write) -> io::Result<()> {
let line_right = line.trim_start();
if line_right.is_empty() {
self.blank_line(w)
} else if line_right.starts_with("//@ ") {
let line = &line_right[4..];
if line.trim().is_empty() {
(self.blank_line(w))?
}
match self.output_state {
State::Rust =>
(self.transition(w, State::MarkdownFirstLine))?,
State::MarkdownFirstLine =>
(self.transition(w, State::MarkdownLines))?,
State::MarkdownLines =>
{}
}
if line.trim().is_empty() {
Ok(())
} else {
self.nonblank_line(line, w)
}
} else if line_right.starts_with("//@@@") {
let line = &line_right[5..];
if !line.trim().is_empty() {
match self.output_state {
State::Rust => {
(self.transition(w, State::MarkdownFirstLine))?;
(self.emit_named_code(line.trim(), w))?;
}
State::MarkdownFirstLine => {
(self.transition(w, State::MarkdownLines))?;
(self.emit_named_code(line.trim(), w))?;
}
State::MarkdownLines => {
(self.emit_named_code(line.trim(), w))?;
}
}
}
Ok(())
} else if line_right.starts_with("//@@") {
let line = &line_right[4..];
if !line.trim().is_empty() {
self.set_meta_note(line.trim());
}
Ok(())
} else if line_right.starts_with("//@") {
let line = &line_right[3..];
match self.output_state {
State::Rust =>
(self.transition(w, State::MarkdownFirstLine))?,
State::MarkdownFirstLine =>
(self.transition(w, State::MarkdownLines))?,
State::MarkdownLines =>
{}
}
if line.trim().is_empty() {
self.blank_line(w)
} else {
self.nonblank_line(line, w)
}
} else {
match self.output_state {
State::MarkdownFirstLine |
State::MarkdownLines =>
(self.transition(w, State::Rust))?,
_ => {}
}
self.nonblank_line(line, w)
}
}
fn emit_named_code(&mut self, name: &str, w: &mut dyn Write) -> io::Result<()> {
writeln!(w, "[{}]: {}", name, encode_to_url(&self.buffered_code))
}
fn set_meta_note(&mut self, note: &str) {
if let Some(ref prev_note) = self.meta_note {
println!("warning: discarding meta note {} for {}", prev_note, note);
}
self.meta_note = Some(note.to_string());
}
fn effect(&mut self, _c: EffectContext, e: Effect, w: &mut dyn Write) -> io::Result<()> {
match e {
Effect::BlankLn => writeln!(w, ""),
Effect::WriteLn(line) => writeln!(w, "{}", line),
Effect::StartCodeBlock => {
if let Some(ref note) = self.meta_note {
assert_eq!(note.chars().next(), Some('{'));
(writeln!(w, "```{{.rust{}", ¬e[1..]))?;
} else {
(writeln!(w, "```rust"))?;
}
self.meta_note = None;
self.buffered_code = String::new();
Ok(())
}
Effect::FinisCodeBlock => {
(writeln!(w, "```"))?;
Ok(())
}
Effect::BlankLitComment => writeln!(w, ""),
}
}
fn nonblank_line(&mut self, line: &str, w: &mut dyn Write) -> io::Result<()> {
for _ in 0..self.blank_line_count {
(self.effect(EffectContext::NonblankLine(line), Effect::BlankLn, w))?;
}
if State::Rust == self.output_state {
self.buffered_code = format!("{}\n{}", self.buffered_code, line);
}
self.blank_line_count = 0;
self.effect(EffectContext::NonblankLine(line), Effect::WriteLn(line), w)
}
fn blank_line(&mut self, _w: &mut dyn Write) -> io::Result<()> {
self.blank_line_count += 1;
if State::Rust == self.output_state {
self.buffered_code = format!("{}\n", self.buffered_code);
}
Ok(())
}
fn finish_section(&mut self, _w: &mut dyn Write) -> io::Result<()> {
Ok(())
}
fn transition(&mut self, w: &mut dyn Write, s: State) -> io::Result<()> {
match s {
State::MarkdownFirstLine => {
assert_eq!(self.output_state, State::Rust);
(self.effect(EffectContext::Transition(s), Effect::FinisCodeBlock, w))?;
for _ in 0..self.blank_line_count {
(self.effect(EffectContext::Transition(s), Effect::BlankLn, w))?;
}
self.blank_line_count = 0;
}
State::MarkdownLines => {
assert_eq!(self.output_state, State::MarkdownFirstLine);
for _ in 0..self.blank_line_count {
(self.effect(EffectContext::Transition(s), Effect::BlankLitComment, w))?;
}
self.blank_line_count = 0;
}
State::Rust => {
assert!(self.output_state != State::Rust);
(self.finish_section(w))?;
for _ in 0..self.blank_line_count {
(self.effect(EffectContext::Transition(s), Effect::BlankLn, w))?;
}
self.blank_line_count = 0;
(self.effect(EffectContext::Transition(s), Effect::StartCodeBlock, w))?;
}
}
self.output_state = s;
Ok(())
}
}