Skip to content
Snippets Groups Projects
Commit 0b53e6b9 authored by Alex Tokarev's avatar Alex Tokarev Committed by Michael Aaron Murphy
Browse files

:sparkles: Optional argument parsing feature with structopt (!1020)

Supports the `advanced_arg_parsing` feature to use structopt to perform
argument parsing of the shell. Do not enable this feature if you want a
performant shell.

Additionally modifies argument parsing to store values in a new
`CommandLineArgs` structure.
parent 9bc52a5f
No related branches found
No related tags found
No related merge requests found
......@@ -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>"
......
......@@ -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"
......
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
......@@ -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
......@@ -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
......
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();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment