ucg/src/main.rs

376 lines
12 KiB
Rust
Raw Normal View History

2017-07-11 20:29:54 -05:00
// Copyright 2017 Jeremy Wall <jeremy@marzhillstudios.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#[macro_use]
extern crate clap;
extern crate ucglib;
use std::cell::RefCell;
use std::error::Error;
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
use std::process;
2018-05-14 21:34:38 -05:00
use std::rc::Rc;
use ucglib::build;
use ucglib::build::assets::{Cache, MemoryCache};
2018-05-14 21:34:38 -05:00
use ucglib::build::Val;
use ucglib::convert::traits;
use ucglib::convert::ConverterRegistry;
// TODO(jwall): List the target output types automatically.
fn do_flags<'a, 'b>() -> clap::App<'a, 'b> {
clap_app!(
ucg =>
(version: crate_version!())
(author: crate_authors!())
(about: "Universal Configuration Grammar compiler.")
(@arg nostrict: --("no-strict") "Turn off strict checking.")
(@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 =>
2018-08-15 18:54:08 -05:00
(about: "Build a list of ucg files.")
(@arg recurse: -r "Whether we should recurse in directories or not.")
(@arg INPUT: ... "Input ucg files or directories to build. If not provided then build the contents of the current directory.")
)
2018-08-24 19:47:15 -05:00
(@subcommand test =>
(about: "Check a list of ucg files for errors and run test assertions.")
(@arg recurse: -r "Whether we should recurse or not.")
2018-08-24 19:47:15 -05:00
(@arg INPUT: ... "Input ucg files or directories to run test assertions for. If not provided it will scan the current directory for files with _test.ucg")
)
(@subcommand converters =>
(about: "list the available converters")
)
)
}
fn run_converter(c: &traits::Converter, v: Rc<Val>, f: Option<&str>) -> traits::Result {
let mut file: Box<std::io::Write> = match f {
Some(f) => {
let mut path_buf = PathBuf::from(f);
path_buf.set_extension(c.file_ext());
let new_path = path_buf.to_str().unwrap();
2018-12-06 12:23:52 -06:00
Box::new(r#try!(File::create(&new_path)))
}
2018-03-11 08:53:09 -05:00
None => Box::new(io::stdout()),
};
c.convert(v, file.as_mut())
}
fn build_file(
file: &str,
validate: bool,
strict: bool,
cache: Rc<RefCell<Cache>>,
) -> Result<build::Builder, Box<Error>> {
let mut file_path_buf = PathBuf::from(file);
if file_path_buf.is_relative() {
file_path_buf = std::env::current_dir().unwrap().join(file_path_buf);
}
let mut builder = build::Builder::new(file_path_buf, cache);
builder.set_strict(strict);
if validate {
builder.enable_validate_mode();
}
2018-12-06 12:23:52 -06:00
r#try!(builder.build());
if validate {
println!("{}", builder.assert_collector.summary);
}
Ok(builder)
}
fn do_validate(file: &str, strict: bool, cache: Rc<RefCell<Cache>>) -> bool {
println!("Validating {}", file);
match build_file(file, true, strict, cache) {
Ok(b) => {
if b.assert_collector.success {
println!("File {} Pass\n", file);
} else {
println!("File {} Fail\n", file);
return false;
}
}
Err(msg) => {
eprintln!("Err: {}", msg);
return false;
}
}
return true;
}
fn do_compile(
file: &str,
strict: bool,
cache: Rc<RefCell<Cache>>,
registry: &ConverterRegistry,
) -> bool {
println!("Building {}", file);
let builder = match build_file(file, false, strict, cache.clone()) {
Ok(builder) => builder,
Err(err) => {
eprintln!("{}", err);
return false;
}
};
let (typ, val) = match builder.out_lock {
Some((ref typ, ref val)) => (typ, val.clone()),
None => {
eprintln!("Build results in no artifacts.");
return false;
}
};
match registry.get_converter(typ) {
Some(converter) => {
run_converter(converter, val, Some(file)).unwrap();
eprintln!("Build successful");
return true;
}
None => {
eprintln!("No such converter {}", typ);
return false;
}
}
}
fn visit_ucg_files(
path: &Path,
recurse: bool,
validate: bool,
strict: bool,
cache: Rc<RefCell<Cache>>,
registry: &ConverterRegistry,
) -> Result<bool, Box<Error>> {
let our_path = String::from(path.to_string_lossy());
let mut result = true;
// TODO(jwall): Report the failing files at the bottom.
let mut summary = String::new();
if path.is_dir() {
2018-12-06 12:23:52 -06:00
let mut dir_iter = r#try!(std::fs::read_dir(path)).peekable();
loop {
let entry = match dir_iter.next() {
Some(e) => e,
None => {
break;
}
};
2018-12-06 12:23:52 -06:00
let next_item = r#try!(entry);
let next_path = next_item.path();
let path_as_string = String::from(next_path.to_string_lossy());
if next_path.is_dir() && recurse {
if let Err(e) = visit_ucg_files(
&next_path,
recurse,
validate,
strict,
cache.clone(),
registry,
) {
eprintln!("{}", e);
result = false;
}
} else {
if validate && path_as_string.ends_with("_test.ucg") {
if !do_validate(&path_as_string, strict, cache.clone()) {
result = false;
summary.push_str(format!("{} - FAIL\n", path_as_string).as_str())
} else {
summary.push_str(format!("{} - PASS\n", path_as_string).as_str())
}
} else if !validate && path_as_string.ends_with(".ucg") {
if !do_compile(&path_as_string, strict, cache.clone(), registry) {
result = false;
}
}
}
}
} else if validate && our_path.ends_with("_test.ucg") {
if !do_validate(&our_path, strict, cache) {
result = false;
summary.push_str(format!("{} - FAIL\n", our_path).as_str());
} else {
summary.push_str(format!("{} - PASS\n", &our_path).as_str());
}
} else if !validate {
if !do_compile(&our_path, strict, cache, registry) {
result = false;
}
}
if validate && !summary.is_empty() {
println!("RESULTS:");
println!("{}", summary);
}
Ok(result)
}
fn inspect_command(
matches: &clap::ArgMatches,
cache: Rc<RefCell<Cache>>,
registry: &ConverterRegistry,
strict: bool,
) {
let file = matches.value_of("INPUT").unwrap();
let sym = matches.value_of("sym");
let target = matches.value_of("target").unwrap();
let mut builder = build::Builder::new(file, cache);
builder.set_strict(strict);
match registry.get_converter(target) {
Some(converter) => {
// TODO(jwall): We should warn if this is a test file.
let result = builder.build();
if !result.is_ok() {
eprintln!("{:?}", result.err().unwrap());
process::exit(1);
}
let val = match sym {
Some(sym_name) => builder.get_out_by_name(sym_name),
None => builder.last,
};
match val {
Some(value) => {
// 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);
}
None => {
eprintln!("Build results in no value.");
process::exit(1);
}
}
}
None => {
eprintln!("No such converter {}", target);
process::exit(1);
}
}
}
fn build_command(
matches: &clap::ArgMatches,
cache: Rc<RefCell<Cache>>,
registry: &ConverterRegistry,
strict: bool,
) {
let files = matches.values_of("INPUT");
let recurse = matches.is_present("recurse");
let mut ok = true;
if files.is_none() {
let curr_dir = std::env::current_dir().unwrap();
let ok = visit_ucg_files(
curr_dir.as_path(),
recurse,
false,
strict,
cache.clone(),
&registry,
);
if let Ok(false) = ok {
process::exit(1)
}
process::exit(0);
}
for file in files.unwrap() {
let pb = PathBuf::from(file);
if let Ok(false) = visit_ucg_files(&pb, recurse, false, strict, cache.clone(), &registry) {
ok = false;
}
}
if !ok {
process::exit(1)
}
}
fn test_command(
matches: &clap::ArgMatches,
cache: Rc<RefCell<Cache>>,
registry: &ConverterRegistry,
strict: bool,
) {
let files = matches.values_of("INPUT");
let recurse = matches.is_present("recurse");
if files.is_none() {
let curr_dir = std::env::current_dir().unwrap();
let ok = visit_ucg_files(
curr_dir.as_path(),
recurse,
true,
strict,
cache.clone(),
&registry,
);
if let Ok(false) = ok {
process::exit(1)
}
} else {
let mut ok = true;
for file in files.unwrap() {
let pb = PathBuf::from(file);
//if pb.is_dir() {
if let Ok(false) = visit_ucg_files(
pb.as_path(),
recurse,
true,
strict,
cache.clone(),
&registry,
) {
ok = false;
}
}
if !ok {
process::exit(1)
}
}
process::exit(0);
}
fn converters_command(registry: &ConverterRegistry) {
println!("Available converters:");
println!("");
for (name, c) in registry.get_converter_list().iter() {
println!("- {}", name);
println!(" Description: {}", c.description());
println!(" Output Extension: `.{}`", c.file_ext());
println!("");
}
}
fn main() {
let mut app = do_flags();
let app_matches = app.clone().get_matches();
let cache: Rc<RefCell<Cache>> = Rc::new(RefCell::new(MemoryCache::new()));
let registry = ConverterRegistry::make_registry();
let strict = if app_matches.is_present("nostrict") {
false
} else {
true
};
if let Some(matches) = app_matches.subcommand_matches("inspect") {
inspect_command(matches, cache, &registry, strict);
} else if let Some(matches) = app_matches.subcommand_matches("build") {
build_command(matches, cache, &registry, strict);
} else if let Some(matches) = app_matches.subcommand_matches("test") {
test_command(matches, cache, &registry, strict);
} else if let Some(_) = app_matches.subcommand_matches("converters") {
converters_command(&registry)
} else {
app.print_help().unwrap();
println!("");
}
}