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.
2017-08-12 14:48:28 -05:00
#[ macro_use ]
extern crate clap ;
2017-05-05 22:33:25 -05:00
extern crate ucglib ;
2018-08-13 23:11:35 -05:00
use std ::cell ::RefCell ;
2018-08-17 12:59:26 -05:00
use std ::error ::Error ;
2017-11-15 22:41:55 -06:00
use std ::fs ::File ;
use std ::io ;
2018-08-17 12:59:26 -05:00
use std ::path ::{ Path , PathBuf } ;
2017-11-15 22:41:55 -06:00
use std ::process ;
2018-05-14 21:34:38 -05:00
use std ::rc ::Rc ;
2017-11-15 22:41:55 -06:00
2017-08-12 14:48:28 -05:00
use ucglib ::build ;
2018-08-17 12:59:26 -05:00
use ucglib ::build ::assets ::{ Cache , MemoryCache } ;
2018-05-14 21:34:38 -05:00
use ucglib ::build ::Val ;
2018-06-18 22:07:18 -05:00
use ucglib ::convert ::traits ;
2018-08-22 00:13:11 -05:00
use ucglib ::convert ::ConverterRegistry ;
2017-08-12 14:48:28 -05:00
2017-11-15 22:41:55 -06:00
// TODO(jwall): List the target output types automatically.
2018-08-26 15:46:52 -05:00
fn do_flags < ' a , ' b > ( ) -> clap ::App < ' a , ' b > {
2017-08-12 14:48:28 -05:00
clap_app! (
ucg = >
( version : crate_version ! ( ) )
( author : crate_authors ! ( ) )
( about : " Universal Configuration Grammar compiler. " )
2018-11-26 21:38:00 -06:00
( @ arg nostrict : - - ( " no-strict " ) " Turn off strict checking. " )
2018-08-15 18:22:05 -05:00
( @ 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. " )
)
2017-08-12 14:48:28 -05:00
( @ subcommand build = >
2018-08-15 18:54:08 -05:00
( about : " Build a list of ucg files. " )
2018-11-27 17:48:41 -06:00
( @ arg recurse : - r + required " Whether we should recurse in directories or not. " )
2018-08-17 12:59:26 -05:00
( @ arg INPUT : .. . " Input ucg files or directories to build. If not provided then build the contents of the current directory. " )
2017-08-12 14:48:28 -05:00
)
2018-08-24 19:47:15 -05:00
( @ subcommand test = >
( about : " Check a list of ucg files for errors and run test assertions. " )
2018-11-27 17:48:41 -06:00
( @ arg recurse : - r + required " 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 " )
2018-08-17 12:59:26 -05:00
)
( @ subcommand converters = >
( about : " list the available converters " )
2017-08-12 14:48:28 -05:00
)
2018-08-26 15:46:52 -05:00
)
2017-08-12 14:48:28 -05:00
}
2018-08-22 00:13:11 -05:00
fn run_converter ( c : & traits ::Converter , v : Rc < Val > , f : Option < & str > ) -> traits ::Result {
let mut file : Box < std ::io ::Write > = match f {
2018-08-15 18:22:05 -05:00
Some ( f ) = > {
let mut path_buf = PathBuf ::from ( f ) ;
2018-08-22 00:13:11 -05:00
path_buf . set_extension ( c . file_ext ( ) ) ;
2018-08-15 18:22:05 -05:00
let new_path = path_buf . to_str ( ) . unwrap ( ) ;
Box ::new ( try ! ( File ::create ( & new_path ) ) )
}
2018-03-11 08:53:09 -05:00
None = > Box ::new ( io ::stdout ( ) ) ,
} ;
2018-08-22 00:13:11 -05:00
c . convert ( v , file . as_mut ( ) )
2017-11-15 22:41:55 -06:00
}
2018-08-17 12:59:26 -05:00
fn build_file (
file : & str ,
validate : bool ,
2018-11-26 21:38:00 -06:00
strict : bool ,
2018-08-17 12:59:26 -05:00
cache : Rc < RefCell < Cache > > ,
) -> Result < build ::Builder , Box < Error > > {
2018-11-23 12:50:47 -06:00
let mut root = PathBuf ::from ( file ) ;
if root . is_relative ( ) {
root = std ::env ::current_dir ( ) . unwrap ( ) . join ( root ) ;
}
2018-08-17 12:59:26 -05:00
let mut builder = build ::Builder ::new ( root . parent ( ) . unwrap ( ) , cache ) ;
2018-11-26 21:38:00 -06:00
builder . set_strict ( strict ) ;
2018-08-17 12:59:26 -05:00
if validate {
builder . enable_validate_mode ( ) ;
}
try ! ( builder . build_file ( file ) ) ;
if validate {
println! ( " {} " , builder . assert_collector . summary ) ;
}
Ok ( builder )
}
2018-11-26 21:38:00 -06:00
fn do_validate ( file : & str , strict : bool , cache : Rc < RefCell < Cache > > ) -> bool {
2018-08-17 22:05:06 -05:00
println! ( " Validating {} " , file ) ;
2018-11-26 21:38:00 -06:00
match build_file ( file , true , strict , cache ) {
2018-08-22 18:59:40 -05:00
Ok ( b ) = > {
if b . assert_collector . success {
println! ( " File {} Pass \n " , file ) ;
} else {
println! ( " File {} Fail \n " , file ) ;
return false ;
}
}
2018-08-17 12:59:26 -05:00
Err ( msg ) = > {
2018-08-17 22:05:06 -05:00
eprintln! ( " Err: {} " , msg ) ;
2018-08-17 12:59:26 -05:00
return false ;
}
}
return true ;
}
2018-11-26 21:38:00 -06:00
fn do_compile (
file : & str ,
strict : bool ,
cache : Rc < RefCell < Cache > > ,
registry : & ConverterRegistry ,
) -> bool {
2018-08-17 12:59:26 -05:00
println! ( " Building {} " , file ) ;
2018-11-26 21:38:00 -06:00
let builder = match build_file ( file , false , strict , cache . clone ( ) ) {
2018-08-17 12:59:26 -05:00
Ok ( builder ) = > builder ,
Err ( err ) = > {
2018-11-06 18:45:03 -06:00
eprintln! ( " {} " , err ) ;
2018-08-17 12:59:26 -05:00
return false ;
}
} ;
let ( typ , val ) = match builder . out_lock {
Some ( ( ref typ , ref val ) ) = > ( typ , val . clone ( ) ) ,
None = > {
2018-08-17 22:05:06 -05:00
eprintln! ( " Build results in no artifacts. " ) ;
2018-08-17 12:59:26 -05:00
return false ;
}
} ;
2018-08-22 00:13:11 -05:00
match registry . get_converter ( typ ) {
Some ( converter ) = > {
2018-08-17 12:59:26 -05:00
run_converter ( converter , val , Some ( file ) ) . unwrap ( ) ;
eprintln! ( " Build successful " ) ;
2018-08-25 18:37:58 -05:00
return true ;
2018-08-17 12:59:26 -05:00
}
2018-08-22 00:13:11 -05:00
None = > {
eprintln! ( " No such converter {} " , typ ) ;
2018-08-17 12:59:26 -05:00
return false ;
}
}
}
fn visit_ucg_files (
path : & Path ,
recurse : bool ,
validate : bool ,
2018-11-26 21:38:00 -06:00
strict : bool ,
2018-08-17 12:59:26 -05:00
cache : Rc < RefCell < Cache > > ,
2018-08-22 00:13:11 -05:00
registry : & ConverterRegistry ,
2018-08-17 12:59:26 -05:00
) -> Result < bool , Box < Error > > {
let our_path = String ::from ( path . to_string_lossy ( ) ) ;
let mut result = true ;
2018-08-20 22:06:52 -05:00
// TODO(jwall): Report the failing files at the bottom.
let mut summary = String ::new ( ) ;
2018-08-17 12:59:26 -05:00
if path . is_dir ( ) {
2018-08-25 18:37:58 -05:00
let mut dir_iter = try ! ( std ::fs ::read_dir ( path ) ) . peekable ( ) ;
loop {
let entry = match dir_iter . next ( ) {
Some ( e ) = > e ,
None = > {
break ;
}
} ;
2018-08-17 12:59:26 -05:00
let next_item = 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 {
2018-11-26 21:38:00 -06:00
if let Err ( e ) = visit_ucg_files (
& next_path ,
recurse ,
validate ,
strict ,
cache . clone ( ) ,
registry ,
) {
2018-11-06 18:45:03 -06:00
eprintln! ( " {} " , e ) ;
2018-08-17 12:59:26 -05:00
result = false ;
}
} else {
if validate & & path_as_string . ends_with ( " _test.ucg " ) {
2018-11-26 21:38:00 -06:00
if ! do_validate ( & path_as_string , strict , cache . clone ( ) ) {
2018-08-17 12:59:26 -05:00
result = false ;
2018-08-20 22:06:52 -05:00
summary . push_str ( format! ( " {} - FAIL \n " , path_as_string ) . as_str ( ) )
} else {
summary . push_str ( format! ( " {} - PASS \n " , path_as_string ) . as_str ( ) )
2018-08-17 12:59:26 -05:00
}
2018-08-25 18:37:58 -05:00
} else if ! validate & & path_as_string . ends_with ( " .ucg " ) {
2018-11-26 21:38:00 -06:00
if ! do_compile ( & path_as_string , strict , cache . clone ( ) , registry ) {
2018-08-17 12:59:26 -05:00
result = false ;
}
}
}
}
} else if validate & & our_path . ends_with ( " _test.ucg " ) {
2018-11-26 21:38:00 -06:00
if ! do_validate ( & our_path , strict , cache ) {
2018-08-17 12:59:26 -05:00
result = false ;
2018-08-25 18:37:58 -05:00
summary . push_str ( format! ( " {} - FAIL \n " , our_path ) . as_str ( ) ) ;
2018-08-20 22:06:52 -05:00
} else {
2018-08-25 18:37:58 -05:00
summary . push_str ( format! ( " {} - PASS \n " , & our_path ) . as_str ( ) ) ;
2018-08-17 12:59:26 -05:00
}
2018-08-20 22:06:52 -05:00
} else if ! validate {
2018-11-26 21:38:00 -06:00
if ! do_compile ( & our_path , strict , cache , registry ) {
2018-08-17 12:59:26 -05:00
result = false ;
}
}
2018-11-15 16:57:24 -06:00
if validate & & ! summary . is_empty ( ) {
2018-08-20 22:06:52 -05:00
println! ( " RESULTS: " ) ;
println! ( " {} " , summary ) ;
}
2018-08-17 12:59:26 -05:00
Ok ( result )
}
2018-08-26 15:46:52 -05:00
fn inspect_command (
matches : & clap ::ArgMatches ,
cache : Rc < RefCell < Cache > > ,
registry : & ConverterRegistry ,
2018-11-26 21:38:00 -06:00
strict : bool ,
2018-08-26 15:46:52 -05:00
) {
let file = matches . value_of ( " INPUT " ) . unwrap ( ) ;
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 ) ;
2018-11-26 21:38:00 -06:00
builder . set_strict ( strict ) ;
2018-08-26 15:46:52 -05:00
match registry . get_converter ( target ) {
Some ( converter ) = > {
// TODO(jwall): We should warn if this is a test file.
let result = builder . build_file ( file ) ;
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 ) ;
2017-11-15 22:41:55 -06:00
}
2018-08-26 15:46:52 -05:00
None = > {
eprintln! ( " Build results in no value. " ) ;
process ::exit ( 1 ) ;
2017-11-15 22:41:55 -06:00
}
}
2017-11-29 18:42:33 -06:00
}
2018-08-26 15:46:52 -05:00
None = > {
eprintln! ( " No such converter {} " , target ) ;
process ::exit ( 1 ) ;
}
}
}
fn build_command (
matches : & clap ::ArgMatches ,
cache : Rc < RefCell < Cache > > ,
registry : & ConverterRegistry ,
2018-11-26 21:38:00 -06:00
strict : bool ,
2018-08-26 15:46:52 -05:00
) {
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 ( ) ;
2018-11-26 21:38:00 -06:00
let ok = visit_ucg_files (
curr_dir . as_path ( ) ,
recurse ,
false ,
strict ,
cache . clone ( ) ,
& registry ,
) ;
2018-08-26 15:46:52 -05:00
if let Ok ( false ) = ok {
process ::exit ( 1 )
2018-08-17 12:59:26 -05:00
}
2018-11-15 16:57:24 -06:00
process ::exit ( 0 ) ;
2018-08-26 15:46:52 -05:00
}
for file in files . unwrap ( ) {
let pb = PathBuf ::from ( file ) ;
2018-11-26 21:38:00 -06:00
if let Ok ( false ) = visit_ucg_files ( & pb , recurse , false , strict , cache . clone ( ) , & registry ) {
2018-08-26 15:46:52 -05:00
ok = false ;
}
}
if ! ok {
process ::exit ( 1 )
}
}
fn test_command (
matches : & clap ::ArgMatches ,
cache : Rc < RefCell < Cache > > ,
registry : & ConverterRegistry ,
2018-11-26 21:38:00 -06:00
strict : bool ,
2018-08-26 15:46:52 -05:00
) {
let files = matches . values_of ( " INPUT " ) ;
let recurse = matches . is_present ( " recurse " ) ;
if files . is_none ( ) {
let curr_dir = std ::env ::current_dir ( ) . unwrap ( ) ;
2018-11-26 21:38:00 -06:00
let ok = visit_ucg_files (
curr_dir . as_path ( ) ,
recurse ,
true ,
strict ,
cache . clone ( ) ,
& registry ,
) ;
2018-08-26 15:46:52 -05:00
if let Ok ( false ) = ok {
process ::exit ( 1 )
}
} else {
let mut ok = true ;
2018-08-17 12:59:26 -05:00
for file in files . unwrap ( ) {
let pb = PathBuf ::from ( file ) ;
2018-08-26 15:46:52 -05:00
//if pb.is_dir() {
2018-11-26 21:38:00 -06:00
if let Ok ( false ) = visit_ucg_files (
pb . as_path ( ) ,
recurse ,
true ,
strict ,
cache . clone ( ) ,
& registry ,
) {
2018-08-17 12:59:26 -05:00
ok = false ;
2018-08-15 18:22:05 -05:00
}
}
2018-08-17 12:59:26 -05:00
if ! ok {
process ::exit ( 1 )
}
2018-08-26 15:46:52 -05:00
}
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 ( ) ;
2018-11-26 21:38:00 -06:00
let strict = if app_matches . is_present ( " nostrict " ) {
false
} else {
true
} ;
2018-08-26 15:46:52 -05:00
if let Some ( matches ) = app_matches . subcommand_matches ( " inspect " ) {
2018-11-26 21:38:00 -06:00
inspect_command ( matches , cache , & registry , strict ) ;
2018-08-26 15:46:52 -05:00
} else if let Some ( matches ) = app_matches . subcommand_matches ( " build " ) {
2018-11-26 21:38:00 -06:00
build_command ( matches , cache , & registry , strict ) ;
2018-08-26 15:46:52 -05:00
} else if let Some ( matches ) = app_matches . subcommand_matches ( " test " ) {
2018-11-26 21:38:00 -06:00
test_command ( matches , cache , & registry , strict ) ;
2018-08-26 15:46:52 -05:00
} else if let Some ( _ ) = app_matches . subcommand_matches ( " converters " ) {
converters_command ( & registry )
} else {
app . print_help ( ) . unwrap ( ) ;
2018-08-22 00:13:11 -05:00
println! ( " " ) ;
2017-08-12 14:48:28 -05:00
}
2017-05-05 22:33:25 -05:00
}