cargo-mdparse 0.1.0

Split out Rust code from Markdown

#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate serde;
extern crate pulldown_cmark;
extern crate clap;

use std::fs::File;
use std::io::{Read, Write};
use std::path::*;
use std::process::Command;

use pulldown_cmark::*;
use clap::{Arg, App, SubCommand};

type Result<T> = ::std::result::Result<T, Box<::std::error::Error>>;

#[derive(Deserialize)]
struct LocateProject {
    root: String,
}

fn get_output_dir() -> Result<PathBuf> {
    let cmd = Command::new("cargo").arg("locate-project").output()?;
    let output = String::from_utf8(cmd.stdout)?;
    let lp: LocateProject = serde_json::from_str(&output)?;
    let cargo_toml = Path::new(&lp.root);
    let root = cargo_toml.parent().unwrap();
    let examples = root.join("examples");
    ::std::fs::create_dir_all(&examples)?;
    Ok(examples)
}

struct Args {
    path: PathBuf,
    save_anonymous_block: bool,
}

fn parse_args() -> Args {
    let matches = App::new("cargo-mdparse")
        .bin_name("cargo")
        .subcommand(
            SubCommand::with_name("mdparse")
                .arg(
                    Arg::with_name("markdown_path")
                        .required(true)
                        .takes_value(true)
                        .help("Path to Markdown file"),
                )
                .arg(
                    Arg::with_name("anonymous")
                        .help("Parse anonymous code block")
                        .short("a")
                        .long("anonymous"),
                ),
        )
        .get_matches();
    let matches = matches.subcommand_matches("mdparse").unwrap();
    let path = matches.value_of("markdown_path").unwrap();
    let save_anonymous_block = matches.is_present("anonymous");
    Args {
        path: PathBuf::from(path),
        save_anonymous_block,
    }
}

fn main() {
    let args = parse_args();
    let output = get_output_dir().expect("Faild to create examples/");

    let mut f = File::open(args.path).unwrap();
    let mut md = String::new();
    f.read_to_string(&mut md).unwrap();
    let parser = Parser::new(&md);

    let mut filename: Option<String> = None;
    let mut current = Vec::new();
    let mut anonymous_count = 0;
    for event in parser {
        match event {
            Event::Start(ty) => {
                match ty {
                    Tag::CodeBlock(langtype) => {
                        if !langtype.starts_with("rust") {
                            continue;
                        }
                        let sp: Vec<_> = langtype.split(':').collect();
                        filename = if sp.len() == 2 {
                            let filename = sp.last().unwrap();
                            if filename.ends_with(".rs") {
                                Some(filename.to_string())
                            } else {
                                eprintln!("Rust snippet is named without `.rs`. Skip it");
                                None
                            }
                        } else {
                            if args.save_anonymous_block {
                                anonymous_count += 1;
                                Some(format!("mdparse{}.rs", anonymous_count))
                            } else {
                                None
                            }
                        };
                    }
                    _ => {}
                }
            }
            Event::End(_) => {
                if let Some(ref filename) = filename {
                    let contents = current.join("");
                    current.clear();
                    write_to_file(&output, filename, &contents).expect("Failed to save examples");
                }
                filename = None;
            }
            Event::Text(text) => {
                if filename.is_some() {
                    current.push(text);
                }
            }
            _ => {}
        }
    }
}

fn write_to_file(output: &Path, filename: &str, contents: &str) -> Result<()> {
    let filename = output.join(filename);
    let mut f = File::create(filename)?;
    f.write_all(&contents.as_bytes())?;
    Ok(())
}