#![warn(anonymous_parameters, bad_style, missing_docs)]
#![warn(
unused,
unused_extern_crates,
unused_import_braces,
unused_qualifications
)]
#![warn(unsafe_code)]
#[macro_use]
extern crate failure;
use failure::{Error, Fail, Fallible};
use getopts::Options;
use std::env;
use std::fs::File;
use std::path::{Path, PathBuf};
use std::process;
#[derive(Debug, Fail)]
#[fail(display = "{}", message)]
struct UsageError {
message: String,
}
impl UsageError {
fn new<T: Into<String>>(message: T) -> Self {
Self {
message: message.into(),
}
}
}
fn flatten_causes(err: &Error) -> String {
err.iter_chain().fold(String::new(), |flattened, cause| {
let flattened = if flattened.is_empty() {
flattened
} else {
flattened + ": "
};
flattened + &format!("{}", cause)
})
}
fn program_name(mut args: env::Args, default_name: &'static str) -> (String, env::Args) {
let name = match args.next() {
Some(arg0) => match Path::new(&arg0).file_stem() {
Some(basename) => match basename.to_str() {
Some(s) => s.to_owned(),
None => default_name.to_owned(),
},
None => default_name.to_owned(),
},
None => default_name.to_owned(),
};
(name, args)
}
fn help(name: &str, opts: &Options) -> Fallible<()> {
let brief = format!("Usage: {} [options] [program-file]", name);
println!("{}", opts.usage(&brief));
println!("Report bugs to: https://github.com/jmmv/endbasic/issues");
println!("EndBASIC home page: https://github.com/jmmv/endbasic");
Ok(())
}
fn version() -> Fallible<()> {
println!("EndBASIC {}", env!("CARGO_PKG_VERSION"));
println!("Copyright 2020 Julio Merino");
println!("License Apache Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>");
Ok(())
}
fn get_programs_dir(flag: Option<String>) -> Fallible<PathBuf> {
let dir = flag.map(PathBuf::from).or_else(|| {
dirs::document_dir()
.map(|d| d.join("endbasic"))
.or_else(|| {
dirs::home_dir().map(|h| h.join("Documents/endbasic"))
})
});
ensure!(
dir.is_some(),
"Cannot compute default path to the Documents folder"
);
Ok(dir.unwrap())
}
#[derive(Default)]
struct StdioConsole {}
fn run<P: AsRef<Path>>(path: P) -> Fallible<()> {
let console = endbasic::repl::new_console();
let mut machine = endbasic::exec::MachineBuilder::default()
.add_builtins(endbasic::console::all_commands(console.clone()))
.build();
let mut input = File::open(path)?;
machine.exec(&mut input)
}
fn safe_main(name: &str, args: env::Args) -> Fallible<()> {
let args: Vec<String> = args.collect();
let mut opts = Options::new();
opts.optflag("h", "help", "show command-line usage information and exit");
opts.optopt(
"",
"programs-dir",
"directory where user programs are stored",
"PATH",
);
opts.optflag("", "version", "show version information and exit");
let matches = opts.parse(args)?;
if matches.opt_present("help") {
return help(name, &opts);
}
if matches.opt_present("version") {
return version();
}
match matches.free.as_slice() {
[] => {
let programs_dir = get_programs_dir(matches.opt_str("programs-dir"))?;
Ok(endbasic::repl::run_repl_loop(&programs_dir)?)
}
[file] => run(file),
[_, ..] => Err(UsageError::new("Too many arguments").into()),
}
}
fn main() {
let (name, args) = program_name(env::args(), "endbasic");
if let Err(e) = safe_main(&name, args) {
if let Some(e) = e.downcast_ref::<UsageError>() {
eprintln!("Usage error: {}", e);
eprintln!("Type {} --help for more information", name);
process::exit(2);
} else if let Some(e) = e.downcast_ref::<getopts::Fail>() {
eprintln!("Usage error: {}", e);
eprintln!("Type {} --help for more information", name);
process::exit(2);
} else {
eprintln!("{}: {}", name, flatten_causes(&e));
process::exit(1);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flatten_causes_one() {
assert_eq!("the error", flatten_causes(&format_err!("the error")));
}
#[test]
fn flatten_causes_several() {
let err = Error::from(format_err!("first").context("second").context("and last"));
assert_eq!("and last: second: first", flatten_causes(&err));
}
}