#![deny(missing_docs)]
#![deny(unsafe_code)]
use std::ffi::OsString;
use std::io;
use std::path::{Path, PathBuf};
use std::process;
use log::info;
pub type Error = io::Error;
pub type Result<T> = io::Result<T>;
fn err_other<E>(error: E) -> Error
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
Error::new(io::ErrorKind::Other, error)
}
#[derive(Debug, Clone, Copy)]
pub struct Args<'a> {
pub lang: &'a str,
pub inputs: &'a [&'a Path],
pub out_dir: &'a Path,
pub includes: &'a [&'a Path],
pub binary: bool,
pub schema: bool,
pub json: bool,
pub extra: &'a [&'a str],
}
impl Default for Args<'_> {
fn default() -> Self {
Self {
lang: "rust",
out_dir: Path::new(""),
includes: &[],
inputs: &[],
binary: false,
schema: false,
json: false,
extra: &[],
}
}
}
pub struct Flatc {
exec: PathBuf,
}
impl Flatc {
pub fn from_env_path() -> Flatc {
Flatc {
exec: PathBuf::from("flatc"),
}
}
pub fn from_path<P: std::convert::Into<PathBuf>>(path: P) -> Flatc {
Flatc { exec: path.into() }
}
pub fn check(&self) -> Result<()> {
self.version().map(|_| ())
}
fn spawn(&self, cmd: &mut process::Command) -> io::Result<process::Child> {
info!("spawning command {:?}", cmd);
cmd.spawn()
.map_err(|e| Error::new(e.kind(), format!("failed to spawn `{:?}`: {}", cmd, e)))
}
pub fn version(&self) -> Result<Version> {
let child = self.spawn(
process::Command::new(&self.exec)
.stdin(process::Stdio::null())
.stdout(process::Stdio::piped())
.stderr(process::Stdio::piped())
.args(&["--version"]),
)?;
let output = child.wait_with_output()?;
if !output.status.success() {
return Err(err_other("flatc failed with error"));
}
let output = String::from_utf8(output.stdout).map_err(err_other)?;
let output = output
.lines()
.next()
.ok_or_else(|| err_other("output is empty"))?;
let prefix = "flatc version ";
if !output.starts_with(prefix) {
return Err(err_other("output does not start with prefix"));
}
let output = &output[prefix.len()..];
let first_char = output
.chars()
.next()
.ok_or_else(|| err_other("version is empty"))?;
if !first_char.is_digit(10) {
return Err(err_other("version does not start with digit"));
}
Ok(Version {
version: output.to_owned(),
})
}
fn run_with_args(&self, args: Vec<OsString>) -> Result<()> {
let mut cmd = process::Command::new(&self.exec);
cmd.stdin(process::Stdio::null());
cmd.args(args);
let mut child = self.spawn(&mut cmd)?;
if !child.wait()?.success() {
return Err(err_other(format!(
"flatc ({:?}) exited with non-zero exit code",
cmd
)));
}
Ok(())
}
pub fn run(&self, args: Args) -> Result<()> {
let mut cmd_args: Vec<OsString> = Vec::new();
if args.out_dir.as_os_str().is_empty() {
return Err(err_other("out_dir is empty"));
}
cmd_args.push({
let mut arg = OsString::with_capacity(args.lang.len() + 3);
arg.push("--");
arg.push(args.lang);
arg
});
if args.binary {
cmd_args.push("--binary".into());
}
if args.schema {
cmd_args.push("--schema".into());
}
if args.json {
cmd_args.push("--json".into());
}
for extra_arg in args.extra {
cmd_args.push(extra_arg.into());
}
if args.lang.is_empty() {
return Err(err_other("lang is empty"));
}
for include in args.includes.iter() {
cmd_args.push("-I".into());
cmd_args.push(include.into());
}
cmd_args.push("-o".into());
cmd_args.push(
args.out_dir
.to_str()
.ok_or_else(|| {
Error::new(
io::ErrorKind::Other,
"only UTF-8 convertable paths are supported",
)
})?
.into(),
);
if args.inputs.is_empty() {
return Err(err_other("input is empty"));
}
cmd_args.extend(args.inputs.iter().map(|input| input.into()));
self.run_with_args(cmd_args)
}
}
pub fn run(args: Args) -> Result<()> {
let flatc = Flatc::from_env_path();
flatc.check()?;
flatc.run(args)
}
pub struct Version {
version: String,
}
impl Version {
pub fn version(&self) -> &str {
&self.version
}
}
#[cfg(test)]
mod test {
use tempfile;
use super::*;
#[test]
fn version() {
Flatc::from_env_path().version().expect("version");
}
#[test]
fn run_can_produce_output() -> io::Result<()> {
let temp_dir = tempfile::Builder::new().prefix("flatc-rust").tempdir()?;
let input_path = temp_dir.path().join("test.fbs");
std::fs::write(&input_path, "table Test { text: string; } root_type Test;")
.expect("test input fbs file could not be written");
run(Args {
lang: "rust",
inputs: &[&input_path],
out_dir: temp_dir.path(),
..Default::default()
})
.expect("run");
let output_path = input_path.with_file_name("test_generated.rs");
assert!(output_path.exists());
assert_ne!(output_path.metadata().unwrap().len(), 0);
Ok(())
}
}