Skip to content

Commit

Permalink
feat(builtin): add basic clap argument parsing for all builtins
Browse files Browse the repository at this point in the history
  • Loading branch information
lthoerner committed Sep 12, 2023
1 parent cb47695 commit 7effeea
Show file tree
Hide file tree
Showing 11 changed files with 344 additions and 202 deletions.
2 changes: 1 addition & 1 deletion config/config.rush
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
truncation-factor: false
multi-line-prompt: true
multiline-prompt: true
history-limit: false
show-errors: true
75 changes: 23 additions & 52 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,49 +165,25 @@ pub enum DispatchError {
#[derive(Debug)]
pub enum BuiltinError {
/// OVERVIEW
/// This error occurs when a builtin command is called with an incorrect number of arguments.
/// This error occurs when a builtin command is provided with invalid argument(s).
///
/// CAUSE
/// - The number of arguments supplied does not match the number of arguments expected.
///
/// SOLUTION
/// - Check the builtin's documentation and adjust arguments accordingly.
///
/// TECHNICAL DETAILS
/// When executing a builtin command, it checks the number of arguments provided by the user,
/// and if it does not match the expected count, this error is returned in order to prevent the
/// builtin from executing with malformed input.
WrongArgCount(usize, usize),

/// OVERVIEW
/// This error occurs when a builtin command receives an argument it does not recognize.
///
/// CAUSE
/// - The builtin command is provided with an argument that it is unable to process.
///
/// SOLUTION
/// - Check the builtin's documentation and adjust arguments accordingly.
///
/// TECHNICAL DETAILS
/// When executing a builtin command, if it encounters an argument it does not recognize, this
/// error is returned in order to prevent the builtin from executing with malformed input.
InvalidArg(String),

/// OVERVIEW
/// This error occurs when a builtin command receives an invalid value for a valid argument.
/// COMMON CAUSES
/// - The builtin received a different number of arguments than it expected.
/// - An argument was misspelled or malformed.
/// - An argument which should have been escaped or enclosed in quotes, but was not.
///
/// CAUSE
/// - The builtin command receives a value that it cannot use for its associated argument.
/// RARE CAUSES
/// - A bug in the parsing logic prevented a valid argument from being parsed correctly.
///
/// SOLUTION
/// - Check the builtin's documentation and adjust arguments accordingly.
/// - File an issue on the Rush repository if an internal bug is suspected.
///
/// TECHNICAL DETAILS
/// When executing a builtin command, if it encounters a valid argument, it will typically try
/// to parse the provided value for the argument. If the value is not expected by the parsing
/// logic, this error is returned in order to prevent the builtin from executing with malformed
/// input.
InvalidValue(String),
/// When executing a builtin command, it will parse the provided arguments into values it can
/// use to perform an operation. If there is some error in parsing these arguments, it is unable
/// to run without proper input, so this error is returned.
CouldNotParseArgs,

/// OVERVIEW
/// This error occurs when a builtin is unable to interact with the terminal.
Expand Down Expand Up @@ -599,22 +575,7 @@ impl Display for BuiltinError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
use BuiltinError::*;
match self {
WrongArgCount(expected, actual) => {
write!(
f,
"Expected {} {}, found {}",
expected,
match expected {
1 => "argument",
_ => "arguments",
},
actual
)
}
InvalidArg(argument) => {
write!(f, "Argument '{}' is invalid", argument)
}
InvalidValue(value) => write!(f, "Argument value '{}' is invalid", value),
CouldNotParseArgs => write!(f, "Unable to parse the provided arguments"),
TerminalOperationFailed => write!(f, "Terminal operation failed"),
}
}
Expand Down Expand Up @@ -777,3 +738,13 @@ macro_rules! file_err {
))
}};
}

/// Shortcut for printing a `clap::Error` and returning a `BuiltinError::CouldNotParseArgs`
macro_rules! clap_handle {
($expr:expr) => {
$expr.map_err(|e| {
eprintln!("{}", e.render().ansi());
builtin_err!(CouldNotParseArgs)
})?
};
}
2 changes: 1 addition & 1 deletion src/eval/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl Dispatcher {
pub fn eval(&self, shell: &mut ShellState, line: &str) -> Result<()> {
let args = tokenize(line);
let command_name = args.get(0).unwrap().as_str();
let command_args: Vec<&str> = args.iter().skip(1).map(|a| a.as_str()).collect();
let command_args: Vec<&str> = args.iter().map(|a| a.as_str()).collect();
self.dispatch(shell, command_name, command_args)?;

Ok(())
Expand Down
189 changes: 183 additions & 6 deletions src/exec/builtins/args.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,186 @@
use clap::Parser;
use std::{path::PathBuf, str::FromStr};

use clap::{Args, Parser, Subcommand};

use crate::state::EnvVariable;

const TRUE_ARGS: [&str; 9] = [
"true", "t", "enable", "enabled", "yes", "y", "on", "some", "1",
];
const FALSE_ARGS: [&str; 9] = [
"false", "f", "disable", "disabled", "no", "n", "off", "none", "0",
];

#[derive(Parser, Debug)]
pub struct TestArgs {}

#[derive(Parser, Debug)]
pub struct ExitArgs {}

#[derive(Parser, Debug)]
pub struct WorkingDirectoryArgs {}

#[derive(Parser, Debug)]
pub struct ChangeDirectoryArgs {
pub path: PathBuf,
}

#[derive(Parser, Debug)]
pub struct ListDirectoryArgs {
#[arg(short = 'a', long = "all")]
pub show_hidden: bool,
pub path: Option<PathBuf>,
}

#[derive(Parser, Debug)]
pub struct PreviousDirectoryArgs {}

#[derive(Parser, Debug)]
pub struct NextDirectoryArgs {}

#[derive(Parser, Debug)]
pub struct ClearTerminalArgs {}

#[derive(Parser, Debug)]
pub struct MakeFileArgs {
pub path: PathBuf,
}

#[derive(Parser, Debug)]
pub struct MakeDirectoryArgs {
pub path: PathBuf,
}

#[derive(Parser, Debug)]
pub struct DeleteFileArgs {
pub path: PathBuf,
}

#[derive(Parser, Debug)]
pub struct ReadFileArgs {
pub path: PathBuf,
}

#[derive(Parser, Debug)]
pub struct RunExecutableArgs {
pub path: PathBuf,
pub arguments: Vec<String>,
}

#[derive(Parser, Debug)]
pub struct ConfigureArgs {
#[arg(long = "truncation-factor")]
pub truncation_factor: Option<MaybeUsize>,
#[arg(long = "history-limit")]
pub history_limit: Option<MaybeUsize>,
#[arg(long = "multiline-prompt")]
pub multiline_prompt: Option<FancyBool>,
#[arg(long = "show-errors")]
pub show_errors: Option<FancyBool>,
}

#[derive(Debug, Clone)]
pub enum FancyBool {
True,
False,
}

impl FromStr for FancyBool {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if TRUE_ARGS.contains(&s) {
Ok(FancyBool::True)
} else if FALSE_ARGS.contains(&s) {
Ok(FancyBool::False)
} else {
Err("invalid boolean value".to_owned())
}
}
}

impl From<FancyBool> for bool {
fn from(b: FancyBool) -> Self {
match b {
FancyBool::True => true,
FancyBool::False => false,
}
}
}

#[derive(Debug, Clone)]
pub enum MaybeUsize {
Some(usize),
None,
}

impl FromStr for MaybeUsize {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(n) = s.parse::<usize>() {
Ok(MaybeUsize::Some(n))
} else if FALSE_ARGS.contains(&s) {
Ok(MaybeUsize::None)
} else {
Err("invalid integer or boolean value".to_owned())
}
}
}

impl From<MaybeUsize> for Option<usize> {
fn from(n: MaybeUsize) -> Self {
match n {
MaybeUsize::Some(n) => Some(n),
MaybeUsize::None => None,
}
}
}

#[derive(Parser, Debug)]
pub struct EnvironmentVariableArgs {
pub variable: EnvVariable,
}

#[derive(Parser, Debug)]
#[command(no_binary_name = true)]
pub struct ListDirectoryArguments {
#[clap(short, long, default_value_t = false)]
pub all: bool,
pub path: Option<String>,
pub struct EditPathArgs {
#[clap(subcommand)]
pub subcommand: EditPathSubcommand,
}

#[derive(Subcommand, Debug, Clone)]
pub enum EditPathSubcommand {
#[clap(
about = "Add the provided path to the end of the PATH variable so it is scanned last when resolving executables"
)]
Append(AppendPathCommand),
#[clap(
about = "Add the provided path to the beginning of the PATH variable so it is scanned first when resolving executables"
)]
Prepend(PrependPathCommand),
#[clap(
about = "Insert the provided path before the path at the specified index in the PATH variable"
)]
Insert(InsertPathCommand),
#[clap(about = "Delete the path at the specified index in the PATH variable")]
Delete(DeletePathCommand),
}

#[derive(Args, Debug, Clone)]
pub struct AppendPathCommand {
pub path: PathBuf,
}

#[derive(Args, Debug, Clone)]
pub struct PrependPathCommand {
pub path: PathBuf,
}

#[derive(Args, Debug, Clone)]
pub struct InsertPathCommand {
pub index: usize,
pub path: PathBuf,
}

#[derive(Args, Debug, Clone)]
pub struct DeletePathCommand {
pub index: usize,
}

0 comments on commit 7effeea

Please sign in to comment.