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 ;
2019-01-13 15:00:26 -06:00
extern crate dirs ;
2019-05-26 09:32:56 -05:00
extern crate rustyline ;
2017-05-05 22:33:25 -05:00
extern crate ucglib ;
2018-08-13 23:11:35 -05:00
use std ::cell ::RefCell ;
2019-05-24 15:37:37 -05:00
use std ::collections ::BTreeMap ;
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 ;
2019-05-24 15:37:37 -05:00
use std ::io ::Read ;
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 ;
2019-01-05 14:27:49 -06:00
use ucglib ::convert ::{ ConverterRegistry , ImporterRegistry } ;
2019-05-24 15:37:37 -05:00
use ucglib ::iter ::OffsetStrIter ;
use ucglib ::parse ::parse ;
2017-08-12 14:48:28 -05:00
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. " )
2019-01-30 21:00:01 -06:00
( @ subcommand eval = >
( about : " Evaluate an expression with an optional ucg file as context. " )
( @ arg expr : - - expr - e + takes_value + required " Expression to evaluate. " )
( @ arg target : - - format + takes_value " Output type. (flags, json, env, exec) defaults to json. " )
( @ arg INPUT : " ucg file to use as context for the expression. " )
2018-08-15 18:22:05 -05:00
)
2019-05-26 09:32:56 -05:00
( @ subcommand repl = >
( about : " Start the ucg repl for interactive evaluation. " )
)
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-28 21:15:03 -06:00
( @ arg recurse : - r " 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-28 21:15:03 -06:00
( @ 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 " )
2018-08-17 12:59:26 -05:00
)
2019-05-24 15:37:37 -05:00
( @ subcommand fmt = >
( about : " Format ucg files automatically. " )
( @ arg recurse : - r " Whether we should recurse or not. " )
( @ arg indent : - i - - indent " How many spaces to indent by. Defaults to 4 " )
( @ arg INPUT : .. . " Input ucg files or directories to format " )
)
2018-08-17 12:59:26 -05:00
( @ subcommand converters = >
( about : " list the available converters " )
2019-03-25 20:23:50 -04:00
( @ arg converter : " Converter name to get help for. " )
2017-08-12 14:48:28 -05:00
)
2019-01-05 14:27:49 -06:00
( @ subcommand importers = >
( about : " list the available importers for includes " )
)
2018-12-14 16:43:43 -06:00
( @ subcommand env = >
( about : " Describe the environment variables ucg uses. " )
)
2018-08-26 15:46:52 -05:00
)
2017-08-12 14:48:28 -05:00
}
2019-02-19 14:50:55 -06:00
fn run_converter ( c : & traits ::Converter , v : Rc < Val > , f : Option < & str > ) -> traits ::ConvertResult {
2018-08-22 00:13:11 -05:00
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 ( ) ;
2018-12-06 12:46:47 -06:00
Box ::new ( File ::create ( & new_path ) ? )
2018-08-15 18:22:05 -05:00
}
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
}
2019-05-28 18:24:02 -05:00
fn build_file < ' a , C : Cache > (
2018-12-13 19:03:22 -06:00
file : & ' a str ,
2018-08-17 12:59:26 -05:00
validate : bool ,
2018-11-26 21:38:00 -06:00
strict : bool ,
2018-12-13 19:03:22 -06:00
import_paths : & ' a Vec < PathBuf > ,
2019-05-28 18:24:02 -05:00
cache : Rc < RefCell < C > > ,
) -> Result < build ::FileBuilder < ' a , C > , Box < dyn Error > > {
2018-11-28 20:11:34 -06:00
let mut file_path_buf = PathBuf ::from ( file ) ;
if file_path_buf . is_relative ( ) {
2019-01-23 20:56:59 -06:00
file_path_buf = std ::env ::current_dir ( ) ? . join ( file_path_buf ) ;
2018-11-23 12:50:47 -06:00
}
2019-01-23 20:56:59 -06:00
let mut builder = build ::FileBuilder ::new ( std ::env ::current_dir ( ) ? , import_paths , 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 ( ) ;
}
2019-01-23 20:56:59 -06:00
builder . build ( file_path_buf ) ? ;
2018-08-17 12:59:26 -05:00
if validate {
println! ( " {} " , builder . assert_collector . summary ) ;
}
Ok ( builder )
}
2019-05-28 18:24:02 -05:00
fn do_validate < C : Cache > (
2018-12-13 19:03:22 -06:00
file : & str ,
strict : bool ,
import_paths : & Vec < PathBuf > ,
2019-05-28 18:24:02 -05:00
cache : Rc < RefCell < C > > ,
2018-12-13 19:03:22 -06:00
) -> bool {
2018-08-17 22:05:06 -05:00
println! ( " Validating {} " , file ) ;
2018-12-13 19:03:22 -06:00
match build_file ( file , true , strict , import_paths , 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 ;
}
2019-05-28 18:24:02 -05:00
fn do_compile < C : Cache > (
2018-11-26 21:38:00 -06:00
file : & str ,
strict : bool ,
2018-12-13 19:03:22 -06:00
import_paths : & Vec < PathBuf > ,
2019-05-28 18:24:02 -05:00
cache : Rc < RefCell < C > > ,
2018-11-26 21:38:00 -06:00
registry : & ConverterRegistry ,
) -> bool {
2018-08-17 12:59:26 -05:00
println! ( " Building {} " , file ) ;
2018-12-13 19:03:22 -06:00
let builder = match build_file ( file , false , strict , import_paths , 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 ;
}
}
}
2019-05-28 18:24:02 -05:00
fn visit_ucg_files < C : Cache > (
2018-08-17 12:59:26 -05:00
path : & Path ,
recurse : bool ,
validate : bool ,
2018-11-26 21:38:00 -06:00
strict : bool ,
2018-12-13 19:03:22 -06:00
import_paths : & Vec < PathBuf > ,
2019-05-28 18:24:02 -05:00
cache : Rc < RefCell < C > > ,
2018-08-22 00:13:11 -05:00
registry : & ConverterRegistry ,
2019-01-05 13:27:51 -06:00
) -> Result < bool , Box < dyn Error > > {
2018-08-17 12:59:26 -05:00
let our_path = String ::from ( path . to_string_lossy ( ) ) ;
let mut result = true ;
2018-08-20 22:06:52 -05:00
let mut summary = String ::new ( ) ;
2018-08-17 12:59:26 -05:00
if path . is_dir ( ) {
2018-12-06 12:46:47 -06:00
let mut dir_iter = std ::fs ::read_dir ( path ) ? . peekable ( ) ;
2018-08-25 18:37:58 -05:00
loop {
let entry = match dir_iter . next ( ) {
Some ( e ) = > e ,
None = > {
break ;
}
} ;
2018-12-06 12:46:47 -06:00
let next_item = entry ? ;
2018-08-17 12:59:26 -05:00
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 ,
2018-12-13 19:03:22 -06:00
import_paths ,
2018-11-26 21:38:00 -06:00
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-12-13 19:03:22 -06:00
if ! do_validate ( & path_as_string , strict , import_paths , 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-12-13 19:03:22 -06:00
if ! do_compile (
& path_as_string ,
strict ,
import_paths ,
cache . clone ( ) ,
registry ,
) {
2018-08-17 12:59:26 -05:00
result = false ;
}
}
}
}
} else if validate & & our_path . ends_with ( " _test.ucg " ) {
2018-12-13 19:03:22 -06:00
if ! do_validate ( & our_path , strict , import_paths , 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-12-13 19:03:22 -06:00
if ! do_compile ( & our_path , strict , import_paths , 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 )
}
2019-05-28 18:24:02 -05:00
fn inspect_command < C : Cache > (
2018-08-26 15:46:52 -05:00
matches : & clap ::ArgMatches ,
2018-12-13 19:03:22 -06:00
import_paths : & Vec < PathBuf > ,
2019-05-28 18:24:02 -05:00
cache : Rc < RefCell < C > > ,
2018-08-26 15:46:52 -05:00
registry : & ConverterRegistry ,
2018-11-26 21:38:00 -06:00
strict : bool ,
2018-08-26 15:46:52 -05:00
) {
2019-01-30 21:00:01 -06:00
let file = matches . value_of ( " INPUT " ) . unwrap_or ( " std/functional.ucg " ) ;
2019-01-07 19:50:15 -06:00
let sym = matches . value_of ( " expr " ) ;
2019-01-30 21:00:01 -06:00
let target = matches . value_of ( " target " ) . unwrap_or ( " json " ) ;
2019-01-23 20:56:59 -06:00
let mut builder =
build ::FileBuilder ::new ( std ::env ::current_dir ( ) . unwrap ( ) , import_paths , 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 ) = > {
2019-01-23 20:56:59 -06:00
let result = builder . build ( file ) ;
2018-08-26 15:46:52 -05:00
if ! result . is_ok ( ) {
eprintln! ( " {:?} " , result . err ( ) . unwrap ( ) ) ;
process ::exit ( 1 ) ;
}
let val = match sym {
2019-01-07 19:50:15 -06:00
Some ( sym_name ) = > {
let normalized = if ! sym_name . ends_with ( " ; " ) {
let mut temp = sym_name . to_owned ( ) ;
temp . push_str ( " ; " ) ;
temp
} else {
sym_name . to_owned ( )
} ;
2019-01-23 20:56:59 -06:00
let mut builder = builder . clone_builder ( ) ;
2019-01-07 19:50:15 -06:00
match builder . eval_string ( & normalized ) {
Ok ( v ) = > Some ( v . clone ( ) ) ,
Err ( e ) = > {
2019-02-20 20:29:32 -06:00
eprintln! ( " {} " , e ) ;
2019-01-07 19:50:15 -06:00
process ::exit ( 1 ) ;
}
}
}
2018-08-26 15:46:52 -05:00
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 ( ) ;
2019-01-30 21:00:01 -06:00
println! ( " " ) ;
2018-08-26 15:46:52 -05:00
process ::exit ( 0 ) ;
2017-11-15 22:41:55 -06:00
}
2018-08-26 15:46:52 -05:00
None = > {
2019-01-30 21:00:01 -06:00
eprintln! ( " No value. " ) ;
2018-08-26 15:46:52 -05:00
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 = > {
2019-01-30 21:00:01 -06:00
eprintln! (
" No such format {} \n run `ucg converters` to see available formats. " ,
target
) ;
2018-08-26 15:46:52 -05:00
process ::exit ( 1 ) ;
}
}
}
2019-05-28 18:24:02 -05:00
fn build_command < C : Cache > (
2018-08-26 15:46:52 -05:00
matches : & clap ::ArgMatches ,
2018-12-13 19:03:22 -06:00
import_paths : & Vec < PathBuf > ,
2019-05-28 18:24:02 -05:00
cache : Rc < RefCell < C > > ,
2018-08-26 15:46:52 -05:00
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 ,
2018-12-13 19:03:22 -06:00
import_paths ,
2018-11-26 21:38:00 -06:00
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-12-13 19:03:22 -06:00
if let Ok ( false ) = visit_ucg_files (
& pb ,
recurse ,
false ,
strict ,
import_paths ,
cache . clone ( ) ,
& registry ,
) {
2018-08-26 15:46:52 -05:00
ok = false ;
}
}
if ! ok {
process ::exit ( 1 )
}
}
2019-05-24 15:37:37 -05:00
fn fmt_file ( p : & Path , indent : usize ) -> std ::result ::Result < ( ) , Box < dyn Error > > {
let mut f = File ::open ( p ) ? ;
let mut contents = String ::new ( ) ;
f . read_to_string ( & mut contents ) ? ;
let mut comment_map = BTreeMap ::new ( ) ;
let stmts = parse ( OffsetStrIter ::new ( & contents ) , Some ( & mut comment_map ) ) ? ;
let mut printer = ucglib ::ast ::printer ::AstPrinter ::new ( indent , std ::io ::stdout ( ) )
. with_comment_map ( & comment_map ) ;
printer . render ( & stmts ) ? ;
Ok ( ( ) )
}
fn fmt_dir ( p : & Path , recurse : bool , indent : usize ) -> std ::result ::Result < ( ) , Box < dyn Error > > {
// TODO(jwall): We should handle this error more gracefully
// for the user here.
let dir_iter = std ::fs ::read_dir ( p ) ? . peekable ( ) ;
for entry in dir_iter {
let next_item = entry . unwrap ( ) ;
let path = next_item . path ( ) ;
if path . is_dir ( ) & & recurse {
fmt_dir ( & path , recurse , indent ) ? ;
} else {
fmt_file ( & path , indent ) ? ;
}
}
Ok ( ( ) )
}
fn fmt_command ( matches : & clap ::ArgMatches ) -> std ::result ::Result < ( ) , Box < dyn Error > > {
let files = matches . values_of ( " INPUT " ) ;
let recurse = matches . is_present ( " recurse " ) ;
let indent = match matches . value_of ( " indent " ) {
Some ( s ) = > s . parse ::< usize > ( ) ? ,
None = > 4 ,
} ;
let mut paths = Vec ::new ( ) ;
if files . is_none ( ) {
paths . push ( std ::env ::current_dir ( ) ? ) ;
} else {
for f in files . unwrap ( ) {
paths . push ( PathBuf ::from ( f ) ) ;
}
}
for p in paths {
if p . is_dir ( ) {
fmt_dir ( & p , recurse , indent ) ? ;
} else {
fmt_file ( & p , indent ) ? ;
}
}
Ok ( ( ) )
}
2019-05-28 18:24:02 -05:00
fn test_command < C : Cache > (
2018-08-26 15:46:52 -05:00
matches : & clap ::ArgMatches ,
2018-12-13 19:03:22 -06:00
import_paths : & Vec < PathBuf > ,
2019-05-28 18:24:02 -05:00
cache : Rc < RefCell < C > > ,
2018-08-26 15:46:52 -05:00
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 ,
2018-12-13 19:03:22 -06:00
import_paths ,
2018-11-26 21:38:00 -06:00
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 ,
2018-12-13 19:03:22 -06:00
import_paths ,
2018-11-26 21:38:00 -06:00
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 ) ;
}
2019-03-25 20:23:50 -04:00
fn converters_command ( matches : & clap ::ArgMatches , registry : & ConverterRegistry ) {
if let Some ( ref cname ) = matches . value_of ( " converter " ) {
let mut found = false ;
for ( name , c ) in registry . get_converter_list ( ) . iter ( ) {
if cname = = name {
println! ( " * {} " , name ) ;
println! ( " Description: {} " , c . description ( ) ) ;
println! ( " Output Extension: `. {} ` " , c . file_ext ( ) ) ;
println! ( " " ) ;
println! ( " {} " , c . help ( ) ) ;
found = true ;
}
}
if ! found {
println! ( " No such converter {} " , cname ) ;
process ::exit ( 1 ) ;
}
} else {
println! ( " Available converters: " ) ;
2018-08-26 15:46:52 -05:00
println! ( " " ) ;
2019-03-25 20:23:50 -04:00
for ( name , c ) in registry . get_converter_list ( ) . iter ( ) {
println! ( " * {} " , name ) ;
println! ( " Description: {} " , c . description ( ) ) ;
println! ( " Output Extension: `. {} ` " , c . file_ext ( ) ) ;
println! ( " " ) ;
}
2018-08-26 15:46:52 -05:00
}
}
2019-01-05 14:27:49 -06:00
fn importers_command ( registry : & ImporterRegistry ) {
println! ( " Available importers " ) ;
println! ( " " ) ;
for ( name , _importer ) in registry . get_importer_list ( ) . iter ( ) {
println! ( " - {} " , name ) ;
}
}
2018-12-14 16:43:43 -06:00
fn env_help ( ) {
println! ( " Universal Configuration Grammar compiler. " ) ;
println! ( " " ) ;
println! ( " ENVIRONMENT VARIABLES: " ) ;
println! ( " " ) ;
println! (
"
UCG_IMPORT_PATH = \ " {} \"
A list of paths to search for imports from . Uses the same syntax
as your platforms $PATH environment variable .
" ,
std ::env ::var ( " UCG_IMPORT_PATH " ) . unwrap_or ( String ::new ( ) )
) ;
}
2019-05-28 20:28:40 -05:00
fn print_repl_help ( ) {
println! ( include_str! ( " help/repl.txt " ) ) ;
}
2019-05-28 18:24:02 -05:00
fn do_repl < C : Cache > (
2019-05-26 09:32:56 -05:00
import_paths : & Vec < PathBuf > ,
2019-05-28 18:24:02 -05:00
cache : Rc < RefCell < C > > ,
2019-05-26 09:32:56 -05:00
) -> std ::result ::Result < ( ) , Box < dyn Error > > {
let config = rustyline ::Config ::builder ( ) ;
let mut editor = rustyline ::Editor ::< ( ) > ::with_config (
config
. history_ignore_space ( true )
. history_ignore_dups ( false )
. build ( ) ,
) ;
let path_home = dirs ::home_dir ( ) . unwrap_or ( std ::env ::temp_dir ( ) ) ;
let config_home = std ::env ::var ( " XDG_CACHE_HOME " )
. unwrap_or_else ( | _ | format! ( " {} /.cache " , path_home . to_string_lossy ( ) ) ) ;
let mut config_home = PathBuf ::from ( config_home ) ;
config_home . push ( " ucg " ) ;
config_home . push ( " line_hist " ) ;
if editor . load_history ( & config_home ) . is_err ( ) {
eprintln! (
" No history file {} Continuing without history. " ,
config_home . to_string_lossy ( )
) ;
// introduce a scope so the file will get automatically closed after
{
let base_dir = config_home . parent ( ) . unwrap ( ) ;
if ! base_dir . exists ( ) {
if let Err ( e ) = std ::fs ::create_dir_all ( base_dir ) {
eprintln! ( " {} " , e ) ;
}
}
if let Err ( e ) = std ::fs ::File ::create ( & config_home ) {
eprintln! ( " {} " , e ) ;
}
}
}
let mut builder = build ::FileBuilder ::new ( std ::env ::current_dir ( ) ? , import_paths , cache ) ;
// loop
let mut lines = ucglib ::io ::StatementAccumulator ::new ( ) ;
2019-05-28 18:06:23 -05:00
println! ( " Welcome to the UCG repl. Ctrl-D to exit " ) ;
2019-05-28 20:28:40 -05:00
println! ( " Type '#help' for help. " ) ;
2019-05-28 18:06:23 -05:00
println! ( " " ) ;
2019-05-26 09:32:56 -05:00
loop {
// print prompt
2019-05-28 20:28:40 -05:00
let line = editor . readline ( & format! ( " {} > " , lines . next_line ( ) ) ) ? ;
// TODO check for a repl command.
// repl commands are only valid while not accumulating a statement;
let trimmed = line . trim ( ) ;
if trimmed . starts_with ( " # " ) {
// handle the various commands.
if trimmed . starts_with ( " #help " ) {
print_repl_help ( ) ;
} else if trimmed . starts_with ( " #del " ) {
// remove a named binding from the builder output.
let args : Vec < & str > = trimmed . split ( " " ) . skip ( 1 ) . collect ( ) ;
if args . len ( ) ! = 1 {
// print usage of the #del command
eprintln! ( " The '#del' command expects a single argument specifying \n the binding to delete. " ) ;
} else {
let key = ucglib ::ast ::PositionedItem {
pos : ucglib ::ast ::Position ::new ( 0 , 0 , 0 ) ,
val : args [ 0 ] . to_string ( ) ,
} ;
if let None = builder . scope_mut ( ) . build_output . remove ( & key ) {
eprintln! ( " No such binding {} " , key . val ) ;
}
}
} else {
eprintln! ( " Invalid repl command... " ) ;
eprintln! ( " " ) ;
print_repl_help ( ) ;
}
continue ;
}
lines . push ( line ) ;
2019-05-26 09:32:56 -05:00
// check to see if that line is a statement
loop {
// read a statement
if let Some ( stmt ) = lines . get_statement ( ) {
// if it is then
// eval statement
match builder . eval_string ( & stmt ) {
// print the result
Err ( e ) = > eprintln! ( " {} " , e ) ,
Ok ( v ) = > {
println! ( " {} " , v ) ;
editor . history_mut ( ) . add ( stmt ) ;
editor . save_history ( & config_home ) ? ;
}
}
// start loop over at prompt.
break ;
}
// if not then keep accumulating lines without a prompt
2019-05-28 18:06:23 -05:00
lines . push ( editor . readline ( & format! ( " {} > " , lines . next_line ( ) ) ) ? ) ;
2019-05-26 09:32:56 -05:00
}
}
}
2019-05-28 18:24:02 -05:00
fn repl < C : Cache > ( import_paths : & Vec < PathBuf > , cache : Rc < RefCell < C > > ) {
2019-05-26 09:32:56 -05:00
if let Err ( e ) = do_repl ( import_paths , cache ) {
eprintln! ( " {} " , e ) ;
process ::exit ( 1 ) ;
}
}
2018-08-26 15:46:52 -05:00
fn main ( ) {
let mut app = do_flags ( ) ;
let app_matches = app . clone ( ) . get_matches ( ) ;
2019-05-28 18:24:02 -05:00
let cache = Rc ::new ( RefCell ::new ( MemoryCache ::new ( ) ) ) ;
2018-08-26 15:46:52 -05:00
let registry = ConverterRegistry ::make_registry ( ) ;
2018-12-13 19:03:22 -06:00
let mut import_paths = Vec ::new ( ) ;
2019-01-13 15:00:26 -06:00
if let Some ( mut p ) = dirs ::home_dir ( ) {
p . push ( " .ucg " ) ;
// Attempt to create directory if it doesn't exist.
if ! p . exists ( ) {
std ::fs ::create_dir ( & p ) . unwrap ( ) ;
}
import_paths . push ( p ) ;
}
2018-12-13 19:03:22 -06:00
if let Ok ( path_list_str ) = std ::env ::var ( " UCG_IMPORT_PATH " ) {
for p in std ::env ::split_paths ( & path_list_str ) {
import_paths . push ( p ) ;
}
}
2018-11-26 21:38:00 -06:00
let strict = if app_matches . is_present ( " nostrict " ) {
false
} else {
true
} ;
2019-01-30 21:00:01 -06:00
if let Some ( matches ) = app_matches . subcommand_matches ( " eval " ) {
2018-12-13 19:03:22 -06:00
inspect_command ( matches , & import_paths , cache , & registry , strict ) ;
2018-08-26 15:46:52 -05:00
} else if let Some ( matches ) = app_matches . subcommand_matches ( " build " ) {
2018-12-13 19:03:22 -06:00
build_command ( matches , & import_paths , cache , & registry , strict ) ;
2018-08-26 15:46:52 -05:00
} else if let Some ( matches ) = app_matches . subcommand_matches ( " test " ) {
2018-12-13 19:03:22 -06:00
test_command ( matches , & import_paths , cache , & registry , strict ) ;
2019-03-25 20:23:50 -04:00
} else if let Some ( matches ) = app_matches . subcommand_matches ( " converters " ) {
converters_command ( matches , & registry )
2019-01-05 14:27:49 -06:00
} else if let Some ( _ ) = app_matches . subcommand_matches ( " importers " ) {
let registry = ImporterRegistry ::make_registry ( ) ;
importers_command ( & registry )
2018-12-14 16:43:43 -06:00
} else if let Some ( _ ) = app_matches . subcommand_matches ( " env " ) {
env_help ( )
2019-05-26 09:32:56 -05:00
} else if let Some ( _ ) = app_matches . subcommand_matches ( " repl " ) {
repl ( & import_paths , cache )
2019-05-24 15:37:37 -05:00
} else if let Some ( matches ) = app_matches . subcommand_matches ( " fmt " ) {
if let Err ( e ) = fmt_command ( matches ) {
eprintln! ( " {} " , e ) ;
}
2018-08-26 15:46:52 -05:00
} 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
}