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(),