#[macro_use]
extern crate log;
use std::fs::File;
use std::io::{self, prelude::*};
use std::mem;
use std::path::PathBuf;
use failure::{format_err, Error};
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt(
name = "parser",
about = "Parse a PDL format file and generate JSON or Markdown file"
)]
struct Opt {
#[structopt(short, long)]
pdl: bool,
#[structopt(short, long)]
json: bool,
#[structopt(short, long)]
markdown: bool,
#[structopt(short, long, parse(from_os_str))]
output: Option<PathBuf>,
#[structopt(long)]
open: bool,
#[structopt(parse(from_os_str))]
file: PathBuf,
}
impl Opt {
fn dump(&self, proto: &pdl::Protocol) -> Result<(), Error> {
match self.output {
Some(ref filename) if filename.to_str() != Some("-") => {
let mut f = File::create(filename)?;
self.dump_to(&mut f, proto)?;
mem::drop(f);
if self.open {
open::that(filename)?;
}
Ok(())
}
_ => {
let stdout = io::stdout();
let mut h = stdout.lock();
self.dump_to(&mut h, proto)
}
}
}
fn dump_to<W: Write>(&self, w: &mut W, proto: &pdl::Protocol) -> Result<(), Error> {
if self.json {
write!(w, "{}", proto.to_json_pretty()?)?;
} else if self.pdl {
write!(w, "{}", proto.to_string())?;
} else if self.markdown {
markdown::render(w, &self.file.file_name().unwrap().to_string_lossy(), proto)?;
}
Ok(())
}
}
mod markdown {
use std::io;
use failure::Error;
fn write_params<W: io::Write>(w: &mut W, name: &str, params: &[pdl::Param]) -> io::Result<()> {
if !params.is_empty() {
writeln!(
w,
r"_{}_
| Name | Type | Description |
| ----:|:---- | :---------- |",
name.to_uppercase(),
)?;
for param in params {
writeln!(
w,
"| `{}`{} | **{}** | {}{}{} |",
param.name,
if param.optional {
" <br/> _optional_ "
} else {
""
},
param.ty,
if param.description.is_empty() {
"".to_string()
} else {
format!("_{}_", param.description.join(" "))
},
if param.experimental {
" **_EXPERIMENTAL_**"
} else {
""
},
if param.deprecated {
" **_DEPRECATED_**"
} else {
""
}
)?;
}
writeln!(w, "")
} else {
Ok(())
}
}
pub fn render<W: io::Write>(w: &mut W, name: &str, proto: &pdl::Protocol) -> Result<(), Error> {
writeln!(
w,
"# {}\n\nversion: {}.{}\n",
name, proto.version.major, proto.version.minor
)?;
for domain in &proto.domains {
writeln!(w, "## {} Domain\n", domain.name)?;
writeln!(w, "> {}", domain.description.join(" "))?;
writeln!(
w,
">{}{}\n",
if domain.experimental {
" **_EXPERIMENTAL_**"
} else {
""
},
if domain.deprecated {
" **_DEPRECATED_**"
} else {
""
}
)?;
if !domain.commands.is_empty() {
writeln!(w, "### Methods\n")?;
for cmd in &domain.commands {
writeln!(w, "_{}._**{}**\n", domain.name, cmd.name)?;
writeln!(w, "> {}", cmd.description.join(" "))?;
writeln!(
w,
">{}{}\n",
if cmd.experimental {
" **_EXPERIMENTAL_**"
} else {
""
},
if cmd.deprecated {
" **_DEPRECATED_**"
} else {
""
}
)?;
write_params(w, "Parameters", &cmd.parameters)?;
write_params(w, "Return Object", &cmd.returns)?;
writeln!(w, "---\n")?;
}
}
if !domain.types.is_empty() {
writeln!(w, "### Types\n")?;
for ty in &domain.types {
writeln!(w, "_{}._**{}**\n", domain.name, ty.id)?;
writeln!(w, "> {}", ty.description.join(" "))?;
writeln!(
w,
">{}{}\n",
if ty.experimental {
" **_EXPERIMENTAL_**"
} else {
""
},
if ty.deprecated {
" **_DEPRECATED_**"
} else {
""
}
)?;
writeln!(w, "\nType: **{}**\n", ty.extends)?;
match ty.item {
Some(pdl::Item::Enum(ref variants)) => {
writeln!(
w,
r"_ALLOWED VALUES_
| Name | Description |
| ----:|:----------- |"
)?;
for variant in variants {
writeln!(
w,
"| `{}` | {} |",
variant.name,
if variant.description.is_empty() {
"".to_string()
} else {
format!("_{}_", variant.description.join(" "))
},
)?;
}
}
Some(pdl::Item::Properties(ref props)) => {
write_params(w, "Properties", props)?;
}
None => {}
}
writeln!(w, "---\n")?;
}
writeln!(w, "")?;
}
if !domain.events.is_empty() {
writeln!(w, "### Events\n")?;
for evt in &domain.events {
writeln!(w, "_{}._**{}**\n", domain.name, evt.name)?;
writeln!(w, "> {}", evt.description.join(" "))?;
writeln!(
w,
">{}{}\n",
if evt.experimental {
" **_EXPERIMENTAL_**"
} else {
""
},
if evt.deprecated {
" **_DEPRECATED_**"
} else {
""
}
)?;
write_params(w, "Parameters", &evt.parameters)?;
}
writeln!(w, "---\n")?;
}
}
Ok(())
}
}
fn main() -> Result<(), Error> {
pretty_env_logger::init();
let opt = Opt::from_args();
debug!("opt: {:#?}", opt);
let mut f = File::open(&opt.file)?;
let mut s = String::new();
f.read_to_string(&mut s)?;
let (rest, protocol) = pdl::parse(&s).map_err(|err| {
format_err!(
"fail to parse PDL file, {}",
match err {
nom::Err::Incomplete(_) => format!("incomplete input"),
nom::Err::Error((_, err)) | nom::Err::Failure((_, err)) => {
format!("{}", err.description())
}
}
)
})?;
if !rest.is_empty() {
warn!("unexpected: {}", &rest[..1000]);
}
trace!("protocol: {:#?}", protocol);
opt.dump(&protocol)
}