diff --git a/Cargo.lock b/Cargo.lock index dee94e86ddca21caf9ed253b262a99f22d83c38e..3653001e19bb9d22949a028d28a412719d5915bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -340,6 +340,14 @@ dependencies = [ "scopeguard 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ion-ranges" version = "0.1.0" @@ -375,6 +383,7 @@ dependencies = [ "serial_test_derive 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "small 0.1.0 (git+https://gitlab.redox-os.org/redox-os/small)", "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -943,6 +952,26 @@ name = "strsim" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "structopt" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "structopt-derive" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" version = "0.15.34" @@ -1137,6 +1166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" "checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" "checksum hashbrown 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3bae29b6653b3412c2e71e9d486db9f9df5d701941d86683005efb9f2d28e3da" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" @@ -1201,6 +1231,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum stackvector 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c049c77bf85fbc036484c97b008276d539d9ebff9dfbde37b632ebcd5b8746b6" "checksum static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum structopt 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "c767a8971f53d7324583085deee2e230903be09e52fb27df9af94c5cb2b43c31" +"checksum structopt-derive 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "c57a30c87454ced2186f62f940e981746e8cbbe026d52090c8c4352b636f8235" "checksum syn 0.15.34 (registry+https://github.com/rust-lang/crates.io-index)" = "a1393e4a97a19c01e900df2aec855a29f71cf02c402e2f443b8d2747c25c5dbe" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum termion 1.5.2 (git+https://gitlab.redox-os.org/redox-os/termion)" = "<none>" diff --git a/Cargo.toml b/Cargo.toml index b044334c693c18d7a194eb756c77241a530d5376..b182580ee998a1b6a46a22816f7e5e407c4d232b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ repository = "https://gitlab.redox-os.org/redox-os/ion" version = "1.0.0-alpha" edition = "2018" +[features] +advanced_arg_parsing = [] + [workspace] members = [ "members/braces", "members/builtins", "members/lexers", "members/sys", @@ -74,6 +77,7 @@ itertools = "0.8" lexical = "2.0" object-pool = "0.3.1" auto_enums = "0.5.5" +structopt = "^0.2" [lib] path = "src/lib/lib.rs" diff --git a/examples/help.out b/examples/help.out index 247edcde7998134a66cecc3fe91c12410943fc38..0f702d30d236d85bac966c29ea8f73b653475215 100644 --- a/examples/help.out +++ b/examples/help.out @@ -1,22 +1,21 @@ -NAME - Ion - The Ion shell +Ion - The Ion Shell 1.0.0-alpha +Ion is a commandline shell created to be a faster and easier to use alternative to the currently available shells. It is +not POSIX compliant. -SYNOPSIS - ion [options] [args...] +USAGE: + ion [FLAGS] [OPTIONS] [args]... -DESCRIPTION - Ion is a commandline shell created to be a faster and easier to use alternative to the - currently available shells. It is not POSIX compliant. +FLAGS: + -h, --help Prints help information + -i, --interactive Force interactive mode + -n, --no-execute Do not execute any commands, perform only syntax checking + -x Print commands before execution + -v, --version Print the version, platform and revision of Ion then exit OPTIONS: - -c <command> evaluates given commands instead of reading from the commandline. - - -n or --no-execute - do not execute any commands, just do syntax checking. - - -v or --version - prints the version, platform and revision of ion then exits. + -c <command> Evaluate given commands instead of reading from the commandline + -o <key_bindings> Shortcut layout. Valid options: "vi", "emacs" ARGS: - <args>... Script arguments (@args). If the -c option is not specified, the first - parameter is taken as a filename to execute + <args>... Script arguments (@args). If the -c option is not specified, the first parameter is taken as a + filename to execute diff --git a/examples/run_examples.sh b/examples/run_examples.sh index c7ca529393082af7a11cd45a0f7f383d23aa43a7..17f85eac0e34d179e7b792e5b36291f54f3c18e0 100755 --- a/examples/run_examples.sh +++ b/examples/run_examples.sh @@ -76,24 +76,32 @@ function check_return_value { test $1 $1 1 } +function perform_testing { + set +e + # Iterate over every Ion script in examples directory + for i in $EXAMPLES_DIR/*.ion; do + check_return_value $i; + if [[ $? -ne 0 ]]; then + EXIT_VAL=1; + fi + done + + # Iterate over every parameter set + for i in $EXAMPLES_DIR/*.params; do + test_cli $i; + if [[ $? -ne 0 ]]; then + EXIT_VAL=1; + fi + done +} + # Build debug binary cargo +$TOOLCHAIN build +perform_testing -set +e -# Iterate over every Ion script in examples directory -for i in $EXAMPLES_DIR/*.ion; do - check_return_value $i; - if [[ $? -ne 0 ]]; then - EXIT_VAL=1; - fi -done - -# Iterate over every parameter set -for i in $EXAMPLES_DIR/*.params; do - test_cli $i; - if [[ $? -ne 0 ]]; then - EXIT_VAL=1; - fi -done +set -e +# Build debug binary for testing structopt argument parsing +cargo +$TOOLCHAIN build --features=advanced_arg_parsing +perform_testing exit $EXIT_VAL diff --git a/src/binary/mod.rs b/src/binary/mod.rs index 14f7b8a4c9324bdb02fa6da17a818ee0901328cd..b9eb36c7279938dcadf232b2258d15a221be5db3 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -18,28 +18,28 @@ use liner::{Buffer, Context, KeyBindings}; use std::{cell::RefCell, fs::OpenOptions, io, path::Path, rc::Rc}; use xdg::BaseDirectories; -pub const MAN_ION: &str = "NAME - Ion - The Ion shell +#[cfg(not(feature = "advanced_arg_parsing"))] +pub const MAN_ION: &str = r#"Ion - The Ion Shell 1.0.0-alpha +Ion is a commandline shell created to be a faster and easier to use alternative to the currently available shells. It is +not POSIX compliant. -SYNOPSIS - ion [options] [args...] +USAGE: + ion [FLAGS] [OPTIONS] [args]... -DESCRIPTION - Ion is a commandline shell created to be a faster and easier to use alternative to the - currently available shells. It is not POSIX compliant. +FLAGS: + -h, --help Prints help information + -i, --interactive Force interactive mode + -n, --no-execute Do not execute any commands, perform only syntax checking + -x Print commands before execution + -v, --version Print the version, platform and revision of Ion then exit OPTIONS: - -c <command> evaluates given commands instead of reading from the commandline. - - -n or --no-execute - do not execute any commands, just do syntax checking. - - -v or --version - prints the version, platform and revision of ion then exits. + -c <command> Evaluate given commands instead of reading from the commandline + -o <key_bindings> Shortcut layout. Valid options: "vi", "emacs" ARGS: - <args>... Script arguments (@args). If the -c option is not specified, the first - parameter is taken as a filename to execute"; + <args>... Script arguments (@args). If the -c option is not specified, the first parameter is taken as a + filename to execute"#; pub(crate) const MAN_HISTORY: &str = r#"NAME history - print command history diff --git a/src/main.rs b/src/main.rs index ff62e8e66905820b80f83c23599fee6435186ec4..9da3f1c2cb0a2fb2082118f6afc2a3f45c37d380 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,98 +1,180 @@ -mod binary; - -use self::binary::{builtins, InteractiveBinary, MAN_ION}; +use self::binary::{builtins, InteractiveBinary}; use ion_shell::{BuiltinMap, IonError, PipelineError, Shell, Value}; use ion_sys as sys; use liner::KeyBindings; use std::{ alloc::System, - env, io::{self, stdin, BufReader}, process, }; +#[cfg(not(feature = "advanced_arg_parsing"))] +use crate::binary::MAN_ION; +#[cfg(not(feature = "advanced_arg_parsing"))] +use std::env; +#[cfg(feature = "advanced_arg_parsing")] +use std::str::FromStr; +#[cfg(feature = "advanced_arg_parsing")] +use structopt::StructOpt; + +mod binary; + #[global_allocator] static A: System = System; +struct KeyBindingsWrapper(KeyBindings); + +#[cfg(feature = "advanced_arg_parsing")] +impl FromStr for KeyBindingsWrapper { + type Err = String; + + fn from_str(input: &str) -> Result<Self, Self::Err> { + match input { + "vi" => Ok(KeyBindingsWrapper(KeyBindings::Vi)), + "emacs" => Ok(KeyBindingsWrapper(KeyBindings::Emacs)), + _ => Err("unknown key bindings".to_string()), + } + } +} + fn set_unique_pid() -> io::Result<()> { let pid = sys::getpid()?; sys::setpgid(0, pid)?; sys::tcsetpgrp(0, pid) } -fn main() { - let mut builtins = BuiltinMap::default().with_shell_unsafe(); - builtins.add("exec", &builtins::exec, "Replace the shell with the given command."); - builtins.add("exit", &builtins::exit, "Exits the current session"); +/// Ion is a commandline shell created to be a faster and easier to use alternative to the +/// currently available shells. It is not POSIX compliant. +#[cfg_attr(feature = "advanced_arg_parsing", derive(StructOpt))] +#[cfg_attr( + feature = "advanced_arg_parsing", + structopt( + name = "Ion - The Ion Shell", + author = "", + raw(setting = "structopt::clap::AppSettings::ColoredHelp") + ) +)] +struct CommandLineArgs { + /// Shortcut layout. Valid options: "vi", "emacs" + #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-o"))] + key_bindings: Option<KeyBindingsWrapper>, + /// Print commands before execution + #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-x"))] + print_commands: bool, + /// Force interactive mode + #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-i", long = "--interactive"))] + interactive: bool, + /// Do not execute any commands, perform only syntax checking + #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-n", long = "--no-execute"))] + no_execute: bool, + /// Evaluate given commands instead of reading from the commandline + #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-c"))] + command: Option<String>, + /// Print the version, platform and revision of Ion then exit + #[cfg_attr(feature = "advanced_arg_parsing", structopt(short = "-v", long = "--version"))] + version: bool, + /// Script arguments (@args). If the -c option is not specified, + /// the first parameter is taken as a filename to execute + #[cfg_attr(feature = "advanced_arg_parsing", structopt())] + args: Vec<String>, +} - let stdin_is_a_tty = sys::isatty(sys::STDIN_FILENO); - let mut shell = Shell::with_builtins(builtins, false); +fn version() -> String { include!(concat!(env!("OUT_DIR"), "/version_string")).to_string() } - if stdin_is_a_tty { - if let Err(why) = set_unique_pid() { - eprintln!("ion: could not assign a pid to the shell: {}", why); - } - } +#[cfg(feature = "advanced_arg_parsing")] +fn parse_args() -> CommandLineArgs { CommandLineArgs::from_args() } - let mut command = None; +#[cfg(not(feature = "advanced_arg_parsing"))] +fn parse_args() -> CommandLineArgs { let mut args = env::args().skip(1); - let mut script_path = None; + let mut command = None; let mut key_bindings = None; - let mut force_interactive = false; + let mut no_execute = false; + let mut print_commands = false; + let mut interactive = false; + let mut version = false; + let mut additional_arguments = Vec::new(); while let Some(arg) = args.next() { match arg.as_str() { - "-o" => match args.next().as_ref().map(|s| s.as_str()) { - Some("vi") => key_bindings = Some(KeyBindings::Vi), - Some("emacs") => key_bindings = Some(KeyBindings::Emacs), - Some(_) => { - eprintln!("ion: invalid option for option -o"); - process::exit(1); + "-o" => { + key_bindings = match args.next().as_ref().map(|s| s.as_str()) { + Some("vi") => Some(KeyBindingsWrapper(KeyBindings::Vi)), + Some("emacs") => Some(KeyBindingsWrapper(KeyBindings::Emacs)), + Some(_) => { + eprintln!("ion: invalid option for option -o"); + process::exit(1); + } + None => { + eprintln!("ion: no option given for option -o"); + process::exit(1); + } } - None => { - eprintln!("ion: no option given for option -o"); - process::exit(1); - } - }, - "-x" => shell.opts_mut().print_comms = true, - "-n" | "--no-execute" => shell.opts_mut().no_exec = true, - "-c" => command = args.next(), - "-v" | "--version" => { - println!(include!(concat!(env!("OUT_DIR"), "/version_string"))); - return; } + "-x" => print_commands = true, + "-n" | "--no-execute" => no_execute = true, + "-c" => command = args.next(), + "-v" | "--version" => version = true, "-h" | "--help" => { println!("{}", MAN_ION); - return; + process::exit(0); } - "-i" | "--interactive" => force_interactive = true, + "-i" | "--interactive" => interactive = true, _ => { - script_path = Some(arg); - break; + additional_arguments.push(arg); } } } + CommandLineArgs { + key_bindings, + print_commands, + interactive, + no_execute, + command, + version, + args: additional_arguments, + } +} + +fn main() { + let command_line_args = parse_args(); + + if command_line_args.version { + println!("{}", version()); + return; + } + + let mut builtins = BuiltinMap::default().with_shell_unsafe(); + builtins.add("exec", &builtins::exec, "Replace the shell with the given command."); + builtins.add("exit", &builtins::exit, "Exits the current session"); + + let stdin_is_a_tty = sys::isatty(sys::STDIN_FILENO); + let mut shell = Shell::with_builtins(builtins, false); + + if stdin_is_a_tty { + if let Err(why) = set_unique_pid() { + eprintln!("ion: could not assign a pid to the shell: {}", why); + } + } + + shell.opts_mut().print_comms = command_line_args.print_commands; + shell.opts_mut().no_exec = command_line_args.no_execute; + let script_path = command_line_args.args.get(0).cloned(); shell.variables_mut().set( "args", Value::Array( - script_path - .clone() - .or_else(|| env::args().next()) - .into_iter() - .chain(args) - .map(|arg| Value::Str(arg.into())) - .collect(), + command_line_args.args.into_iter().map(|arg| Value::Str(arg.into())).collect(), ), ); - let err = if let Some(command) = command { + let err = if let Some(command) = command_line_args.command { shell.execute_command(command.as_bytes()) } else if let Some(path) = script_path { - shell.execute_file(&path.as_str()) - } else if stdin_is_a_tty || force_interactive { + shell.execute_file(path) + } else if stdin_is_a_tty || command_line_args.interactive { let mut interactive = InteractiveBinary::new(shell); - if let Some(key_bindings) = key_bindings { - interactive.set_keybindings(key_bindings); + if let Some(key_bindings) = command_line_args.key_bindings { + interactive.set_keybindings(key_bindings.0); } interactive.add_callbacks(); interactive.execute_interactive();