From 64185335629af81558ab36bd684ce7d58d685699 Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 15 Aug 2018 18:22:05 -0500 Subject: [PATCH] UI: Command line subcommands and arguments are more usable. The build and validate commands now take a list of files to process. The outputs for files that specify the are given the same name as the containing file with the extension changed to the extension that the converter specifies. --- README.md | 3 +- examples/test.ucg | 4 ++- src/build/mod.rs | 7 ++--- src/convert/env.rs | 4 +++ src/convert/exec.rs | 4 +++ src/convert/flags.rs | 4 +++ src/convert/json.rs | 4 +++ src/convert/mod.rs | 5 ++++ src/convert/traits.rs | 1 + src/main.rs | 69 +++++++++++++++++++++++++++++++++++-------- src/parse/mod.rs | 3 +- 11 files changed, 88 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 7210a21..95b7315 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,9 @@ FLAGS: -V, --version Prints version information SUBCOMMANDS: - build Compile a specific ucg file. + build Build a specific ucg file. help Prints this message or the help of the given subcommand(s) + inspect Inspect a specific symbol in a ucg file. validate Check a specific ucg file for errors. ``` diff --git a/examples/test.ucg b/examples/test.ucg index e944055..a509c5a 100644 --- a/examples/test.ucg +++ b/examples/test.ucg @@ -39,4 +39,6 @@ let server_config = { foo = "bar" }, l = ["foo", "bar"] -}; \ No newline at end of file +}; + +out json server_config; \ No newline at end of file diff --git a/src/build/mod.rs b/src/build/mod.rs index 49b75c1..18d4fff 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -319,8 +319,7 @@ pub struct Builder { build_output: ValueMap, /// last is the result of the last statement. pub last: Option>, - // FIXME(jwall): This should be a per file mapping. - out_lock: Option<(String, Rc)>, + pub out_lock: Option<(String, Rc)>, } macro_rules! eval_binary_expr { @@ -541,8 +540,8 @@ impl Builder { &Statement::Let(ref def) => self.build_let(def), &Statement::Import(ref def) => self.build_import(def), &Statement::Expression(ref expr) => self.eval_expr(expr), - // FIXME(jwall): Stash this into an output slot. - // Only one output can be used per file. + // Only one output can be used per file. Right now we enforce this by + // having a single builder per file. &Statement::Output(ref typ, ref expr) => { if let None = self.out_lock { let val = try!(self.eval_expr(expr)); diff --git a/src/convert/env.rs b/src/convert/env.rs index 5086fc1..4f5c5db 100644 --- a/src/convert/env.rs +++ b/src/convert/env.rs @@ -87,4 +87,8 @@ impl Converter for EnvConverter { fn convert(&self, v: Rc, mut w: &mut Write) -> Result { self.write(&v, &mut w) } + + fn file_ext(&self) -> String { + String::from(".env") + } } diff --git a/src/convert/exec.rs b/src/convert/exec.rs index a58db92..98e1a76 100644 --- a/src/convert/exec.rs +++ b/src/convert/exec.rs @@ -185,6 +185,10 @@ impl Converter for ExecConverter { fn convert(&self, v: Rc, mut w: &mut Write) -> Result { self.write(&v, &mut w) } + + fn file_ext(&self) -> String { + String::from("sh") + } } #[cfg(test)] diff --git a/src/convert/flags.rs b/src/convert/flags.rs index 19206a3..52d2793 100644 --- a/src/convert/flags.rs +++ b/src/convert/flags.rs @@ -107,6 +107,10 @@ impl Converter for FlagConverter { fn convert(&self, v: Rc, mut w: &mut Write) -> Result { self.write("", &v, &mut w) } + + fn file_ext(&self) -> String { + String::from("txt") + } } // We need some unit tests for this now :D diff --git a/src/convert/json.rs b/src/convert/json.rs index 7a4bb49..d4a966f 100644 --- a/src/convert/json.rs +++ b/src/convert/json.rs @@ -88,4 +88,8 @@ impl Converter for JsonConverter { fn convert(&self, v: Rc, mut w: &mut Write) -> Result { self.write(&v, &mut w) } + + fn file_ext(&self) -> String { + String::from("json") + } } diff --git a/src/convert/mod.rs b/src/convert/mod.rs index d90140c..78433ab 100644 --- a/src/convert/mod.rs +++ b/src/convert/mod.rs @@ -64,4 +64,9 @@ impl ConverterRunner { pub fn convert(&self, v: Rc, mut w: Box) -> traits::Result { self.converter.convert(v, &mut w) } + + /// ext returns the expected file extension for this conversion. + pub fn ext(&self) -> String { + self.converter.file_ext() + } } diff --git a/src/convert/traits.rs b/src/convert/traits.rs index ade8e69..f873bfa 100644 --- a/src/convert/traits.rs +++ b/src/convert/traits.rs @@ -26,4 +26,5 @@ pub type Result = result::Result<(), Box>; /// final conversion stage of the ucg compiler. pub trait Converter { fn convert(&self, vs: Rc, w: &mut Write) -> Result; + fn file_ext(&self) -> String; } diff --git a/src/main.rs b/src/main.rs index 732e34e..3148fa4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,23 +35,32 @@ fn do_flags<'a>() -> clap::ArgMatches<'a> { (version: crate_version!()) (author: crate_authors!()) (about: "Universal Configuration Grammar compiler.") + (@subcommand inspect => + (about: "Inspect a specific symbol in a ucg file.") + (@arg sym: --sym +takes_value +required "Specify a specific binding in the ucg file to output.") + (@arg target: --format +required +takes_value "Inspect output type. (flags, json, env, exec)") + (@arg INPUT: +required "Input ucg file to inspect symbol from.") + ) (@subcommand build => - (about: "Compile a specific ucg file.") - (@arg sym: --sym +takes_value +required "Specify a specific let binding in the ucg file to output.") - (@arg target: --target -t +required +takes_value "Target output type. (flags, json, env, exec)") + (about: "Build a specific ucg file.") (@arg out: --out -o +takes_value "Output file to write to.") - (@arg INPUT: +required "Input ucg file to build.") + (@arg INPUT: ... +required "Input ucg files to build.") ) (@subcommand validate => (about: "Check a specific ucg file for errors.") - (@arg INPUT: +required "Input ucg file to validate.") + (@arg INPUT: ... +required "Input ucg files to validate.") ) ).get_matches() } fn run_converter(c: ConverterRunner, v: Rc, f: Option<&str>) -> traits::Result { let file: Box = match f { - Some(f) => Box::new(try!(File::create(f))), + Some(f) => { + let mut path_buf = PathBuf::from(f); + path_buf.set_extension(c.ext()); + let new_path = path_buf.to_str().unwrap(); + Box::new(try!(File::create(&new_path))) + } None => Box::new(io::stdout()), }; c.convert(v, file) @@ -60,16 +69,15 @@ fn run_converter(c: ConverterRunner, v: Rc, f: Option<&str>) -> traits::Res fn main() { let app = do_flags(); let cache = Rc::new(RefCell::new(MemoryCache::new())); - if let Some(matches) = app.subcommand_matches("build") { + if let Some(matches) = app.subcommand_matches("inspect") { let file = matches.value_of("INPUT").unwrap(); - let out = matches.value_of("out"); let sym = matches.value_of("sym"); let target = matches.value_of("target").unwrap(); let root = PathBuf::from(file); let mut builder = build::Builder::new(root.parent().unwrap(), cache); match ConverterRunner::new(target) { Ok(converter) => { - // FIXME(jwall): What should happen if they requested we build a _test.ucg file. + // TODO(jwall): What should happen if they requested we build a _test.ucg file. // 1. We could automatically go into validate mode. // 2. We could warn that this is a test file. let result = builder.build_file(file); @@ -83,7 +91,8 @@ fn main() { }; match val { Some(value) => { - run_converter(converter, value, out).unwrap(); + // We use None here because we always output to stdout for an inspect. + run_converter(converter, value, None).unwrap(); eprintln!("Build successful"); process::exit(0); } @@ -98,12 +107,46 @@ fn main() { process::exit(1); } } + } else if let Some(matches) = app.subcommand_matches("build") { + // TODO(jwall): This should take multiple files I think. + let files = matches.values_of("INPUT").unwrap(); + // TODO(jwall): We should default to the file name with appropriate + // extension if this is not set. + for file in files { + let root = PathBuf::from(file); + let mut builder = build::Builder::new(root.parent().unwrap(), cache); + let result = builder.build_file(file); + if !result.is_ok() { + eprintln!("{:?}", result.err().unwrap()); + process::exit(1); + } + let (typ, val) = match builder.out_lock { + Some((ref typ, ref val)) => (typ, val.clone()), + None => { + eprintln!("Build results in no value."); + process::exit(1); + } + }; + match ConverterRunner::new(typ) { + Ok(converter) => { + run_converter(converter, val, Some(file)).unwrap(); + eprintln!("Build successful"); + process::exit(0); + } + Err(msg) => { + eprintln!("{}", msg); + process::exit(1); + } + } + } } else if let Some(matches) = app.subcommand_matches("validate") { - let file = matches.value_of("INPUT").unwrap(); + let files = matches.values_of("INPUT").unwrap(); let mut builder = build::Builder::new(std::env::current_dir().unwrap(), cache); builder.enable_validate_mode(); - builder.build_file(file).unwrap(); - println!("File Validates"); + for file in files { + builder.build_file(file).unwrap(); + println!("File Validates"); + } process::exit(0); } } diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 93bc70f..8cd4ade 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -500,7 +500,8 @@ named!(copy_expression, fn tuple_to_macro(mut t: (Position, Vec, Value)) -> ParseResult { match t.2 { Value::Tuple(v) => Ok(Expression::Macro(MacroDef { - argdefs: t.1 + argdefs: t + .1 .drain(0..) .map(|s| Positioned { pos: s.pos().clone(),