mirror of
https://github.com/zaphar/ucg.git
synced 2025-07-22 18:19:54 -04:00
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.
This commit is contained in:
parent
1c464083fb
commit
6418533562
@ -39,8 +39,9 @@ FLAGS:
|
|||||||
-V, --version Prints version information
|
-V, --version Prints version information
|
||||||
|
|
||||||
SUBCOMMANDS:
|
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)
|
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.
|
validate Check a specific ucg file for errors.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -40,3 +40,5 @@ let server_config = {
|
|||||||
},
|
},
|
||||||
l = ["foo", "bar"]
|
l = ["foo", "bar"]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
out json server_config;
|
@ -319,8 +319,7 @@ pub struct Builder {
|
|||||||
build_output: ValueMap,
|
build_output: ValueMap,
|
||||||
/// last is the result of the last statement.
|
/// last is the result of the last statement.
|
||||||
pub last: Option<Rc<Val>>,
|
pub last: Option<Rc<Val>>,
|
||||||
// FIXME(jwall): This should be a per file mapping.
|
pub out_lock: Option<(String, Rc<Val>)>,
|
||||||
out_lock: Option<(String, Rc<Val>)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! eval_binary_expr {
|
macro_rules! eval_binary_expr {
|
||||||
@ -541,8 +540,8 @@ impl Builder {
|
|||||||
&Statement::Let(ref def) => self.build_let(def),
|
&Statement::Let(ref def) => self.build_let(def),
|
||||||
&Statement::Import(ref def) => self.build_import(def),
|
&Statement::Import(ref def) => self.build_import(def),
|
||||||
&Statement::Expression(ref expr) => self.eval_expr(expr),
|
&Statement::Expression(ref expr) => self.eval_expr(expr),
|
||||||
// FIXME(jwall): Stash this into an output slot.
|
// Only one output can be used per file. Right now we enforce this by
|
||||||
// Only one output can be used per file.
|
// having a single builder per file.
|
||||||
&Statement::Output(ref typ, ref expr) => {
|
&Statement::Output(ref typ, ref expr) => {
|
||||||
if let None = self.out_lock {
|
if let None = self.out_lock {
|
||||||
let val = try!(self.eval_expr(expr));
|
let val = try!(self.eval_expr(expr));
|
||||||
|
@ -87,4 +87,8 @@ impl Converter for EnvConverter {
|
|||||||
fn convert(&self, v: Rc<Val>, mut w: &mut Write) -> Result {
|
fn convert(&self, v: Rc<Val>, mut w: &mut Write) -> Result {
|
||||||
self.write(&v, &mut w)
|
self.write(&v, &mut w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn file_ext(&self) -> String {
|
||||||
|
String::from(".env")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,6 +185,10 @@ impl Converter for ExecConverter {
|
|||||||
fn convert(&self, v: Rc<Val>, mut w: &mut Write) -> Result {
|
fn convert(&self, v: Rc<Val>, mut w: &mut Write) -> Result {
|
||||||
self.write(&v, &mut w)
|
self.write(&v, &mut w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn file_ext(&self) -> String {
|
||||||
|
String::from("sh")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -107,6 +107,10 @@ impl Converter for FlagConverter {
|
|||||||
fn convert(&self, v: Rc<Val>, mut w: &mut Write) -> Result {
|
fn convert(&self, v: Rc<Val>, mut w: &mut Write) -> Result {
|
||||||
self.write("", &v, &mut w)
|
self.write("", &v, &mut w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn file_ext(&self) -> String {
|
||||||
|
String::from("txt")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need some unit tests for this now :D
|
// We need some unit tests for this now :D
|
||||||
|
@ -88,4 +88,8 @@ impl Converter for JsonConverter {
|
|||||||
fn convert(&self, v: Rc<Val>, mut w: &mut Write) -> Result {
|
fn convert(&self, v: Rc<Val>, mut w: &mut Write) -> Result {
|
||||||
self.write(&v, &mut w)
|
self.write(&v, &mut w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn file_ext(&self) -> String {
|
||||||
|
String::from("json")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,4 +64,9 @@ impl ConverterRunner {
|
|||||||
pub fn convert(&self, v: Rc<Val>, mut w: Box<Write>) -> traits::Result {
|
pub fn convert(&self, v: Rc<Val>, mut w: Box<Write>) -> traits::Result {
|
||||||
self.converter.convert(v, &mut w)
|
self.converter.convert(v, &mut w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ext returns the expected file extension for this conversion.
|
||||||
|
pub fn ext(&self) -> String {
|
||||||
|
self.converter.file_ext()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,4 +26,5 @@ pub type Result = result::Result<(), Box<Error>>;
|
|||||||
/// final conversion stage of the ucg compiler.
|
/// final conversion stage of the ucg compiler.
|
||||||
pub trait Converter {
|
pub trait Converter {
|
||||||
fn convert(&self, vs: Rc<Val>, w: &mut Write) -> Result;
|
fn convert(&self, vs: Rc<Val>, w: &mut Write) -> Result;
|
||||||
|
fn file_ext(&self) -> String;
|
||||||
}
|
}
|
||||||
|
65
src/main.rs
65
src/main.rs
@ -35,23 +35,32 @@ fn do_flags<'a>() -> clap::ArgMatches<'a> {
|
|||||||
(version: crate_version!())
|
(version: crate_version!())
|
||||||
(author: crate_authors!())
|
(author: crate_authors!())
|
||||||
(about: "Universal Configuration Grammar compiler.")
|
(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 =>
|
(@subcommand build =>
|
||||||
(about: "Compile a specific ucg file.")
|
(about: "Build 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)")
|
|
||||||
(@arg out: --out -o +takes_value "Output file to write to.")
|
(@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 =>
|
(@subcommand validate =>
|
||||||
(about: "Check a specific ucg file for errors.")
|
(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()
|
).get_matches()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_converter(c: ConverterRunner, v: Rc<Val>, f: Option<&str>) -> traits::Result {
|
fn run_converter(c: ConverterRunner, v: Rc<Val>, f: Option<&str>) -> traits::Result {
|
||||||
let file: Box<std::io::Write> = match f {
|
let file: Box<std::io::Write> = 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()),
|
None => Box::new(io::stdout()),
|
||||||
};
|
};
|
||||||
c.convert(v, file)
|
c.convert(v, file)
|
||||||
@ -60,16 +69,15 @@ fn run_converter(c: ConverterRunner, v: Rc<Val>, f: Option<&str>) -> traits::Res
|
|||||||
fn main() {
|
fn main() {
|
||||||
let app = do_flags();
|
let app = do_flags();
|
||||||
let cache = Rc::new(RefCell::new(MemoryCache::new()));
|
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 file = matches.value_of("INPUT").unwrap();
|
||||||
let out = matches.value_of("out");
|
|
||||||
let sym = matches.value_of("sym");
|
let sym = matches.value_of("sym");
|
||||||
let target = matches.value_of("target").unwrap();
|
let target = matches.value_of("target").unwrap();
|
||||||
let root = PathBuf::from(file);
|
let root = PathBuf::from(file);
|
||||||
let mut builder = build::Builder::new(root.parent().unwrap(), cache);
|
let mut builder = build::Builder::new(root.parent().unwrap(), cache);
|
||||||
match ConverterRunner::new(target) {
|
match ConverterRunner::new(target) {
|
||||||
Ok(converter) => {
|
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.
|
// 1. We could automatically go into validate mode.
|
||||||
// 2. We could warn that this is a test file.
|
// 2. We could warn that this is a test file.
|
||||||
let result = builder.build_file(file);
|
let result = builder.build_file(file);
|
||||||
@ -83,7 +91,8 @@ fn main() {
|
|||||||
};
|
};
|
||||||
match val {
|
match val {
|
||||||
Some(value) => {
|
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");
|
eprintln!("Build successful");
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
@ -98,12 +107,46 @@ fn main() {
|
|||||||
process::exit(1);
|
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") {
|
} 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);
|
let mut builder = build::Builder::new(std::env::current_dir().unwrap(), cache);
|
||||||
builder.enable_validate_mode();
|
builder.enable_validate_mode();
|
||||||
|
for file in files {
|
||||||
builder.build_file(file).unwrap();
|
builder.build_file(file).unwrap();
|
||||||
println!("File Validates");
|
println!("File Validates");
|
||||||
|
}
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -500,7 +500,8 @@ named!(copy_expression<TokenIter, Expression, error::Error>,
|
|||||||
fn tuple_to_macro(mut t: (Position, Vec<Value>, Value)) -> ParseResult<Expression> {
|
fn tuple_to_macro(mut t: (Position, Vec<Value>, Value)) -> ParseResult<Expression> {
|
||||||
match t.2 {
|
match t.2 {
|
||||||
Value::Tuple(v) => Ok(Expression::Macro(MacroDef {
|
Value::Tuple(v) => Ok(Expression::Macro(MacroDef {
|
||||||
argdefs: t.1
|
argdefs: t
|
||||||
|
.1
|
||||||
.drain(0..)
|
.drain(0..)
|
||||||
.map(|s| Positioned {
|
.map(|s| Positioned {
|
||||||
pos: s.pos().clone(),
|
pos: s.pos().clone(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user