use anyhow::{Context, bail};
use clap::Parser;
use duct::cmd;
use geno::GenoAstBuilder;
use std::{
fs::{self, File},
io::{Write, stdout},
path::PathBuf,
process::exit,
};
#[derive(Parser)]
#[command(
name = "geno",
version,
about = "Geno schema compiler",
long_about = "Geno is a schema compiler for generating source code from a schema definition."
)]
struct Cli {
#[arg(value_name = "INPUT_FILE")]
input_path: PathBuf,
#[arg(value_name = "OUTPUT_FILE", short = 'o', long)]
output_path: Option<PathBuf>,
#[arg(value_name = "AST_FILE", short = 't', long)]
ast_path: Option<PathBuf>,
#[arg(value_name = "FORMAT", short = 'f', long)]
format: Option<String>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
extra_args: Vec<String>,
}
fn main() {
match run() {
Ok(code) => exit(code),
Err(root_err) => {
for err in root_err.chain() {
eprintln!("error: {}", err);
}
exit(1);
}
}
}
fn run() -> anyhow::Result<i32> {
let cli = match Cli::try_parse() {
Ok(cli) => cli,
Err(err) => {
eprintln!("{}", err);
return Ok(0);
}
};
let ast_builder = GenoAstBuilder::new(cli.input_path)?;
let reader = |path: &_| std::fs::read_to_string(path);
let ast = ast_builder.build(&reader)?;
ast.validate()?;
if let Some(ast_path) = cli.ast_path {
let mut file = File::create(&ast_path).context(format!(
"Could not create AST file '{}'",
ast_path.to_string_lossy()
))?;
rmp_serde::encode::write(&mut file, &ast)
.context("Failed to serialize AST to MessagePack")?;
return Ok(0);
}
let format = match cli.format {
Some(s) => s,
None => bail!("No output format specified"),
};
let extra_args: Vec<&str> = cli.extra_args.iter().map(|s| s.as_str()).collect();
let cmd_expr = if std::env::var("GENO_DEBUG").is_ok() {
cmd(
"cargo",
itertools::concat(vec![
vec!["run", "--bin", &format!("geno-{}", format), "--"],
extra_args,
]),
)
} else {
cmd(format!("geno-{}", format), extra_args)
};
let ast_bytes = rmp_serde::to_vec(&ast).context("Failed to serialize AST to MessagePack")?;
let output = cmd_expr
.stdin_bytes(ast_bytes)
.stdout_capture()
.read()
.with_context(|| format!("Failed to run AST formatter '{:?}'", cmd_expr))?;
match cli.output_path {
Some(path) => {
fs::write(path, output)?;
}
None => {
stdout().write(output.as_bytes())?;
}
};
Ok(0)
}