mirror of
https://github.com/uutils/coreutils
synced 2024-12-14 15:22:38 +00:00
expr: update to clap 4 and remove custom argument parsing
This commit is contained in:
parent
6cc6f35155
commit
86d24176e4
5 changed files with 107 additions and 108 deletions
|
@ -15,7 +15,7 @@ edition = "2021"
|
|||
path = "src/expr.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "3.2", features = ["wrap_help", "cargo"] }
|
||||
clap = { version = "4.0", features = ["wrap_help", "cargo"] }
|
||||
num-bigint = "0.4.0"
|
||||
num-traits = "0.2.15"
|
||||
onig = { version = "~6.4", default-features = false }
|
||||
|
|
59
src/uu/expr/expr.md
Normal file
59
src/uu/expr/expr.md
Normal file
|
@ -0,0 +1,59 @@
|
|||
# expr
|
||||
|
||||
## About
|
||||
|
||||
Print the value of EXPRESSION to standard output
|
||||
|
||||
## Usage
|
||||
```
|
||||
expr [EXPRESSION]
|
||||
expr [OPTIONS]
|
||||
```
|
||||
|
||||
## After help
|
||||
|
||||
Print the value of EXPRESSION to standard output. A blank line below
|
||||
separates increasing precedence groups. EXPRESSION may be:
|
||||
|
||||
ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2
|
||||
|
||||
ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0
|
||||
|
||||
ARG1 < ARG2 ARG1 is less than ARG2
|
||||
ARG1 <= ARG2 ARG1 is less than or equal to ARG2
|
||||
ARG1 = ARG2 ARG1 is equal to ARG2
|
||||
ARG1 != ARG2 ARG1 is unequal to ARG2
|
||||
ARG1 >= ARG2 ARG1 is greater than or equal to ARG2
|
||||
ARG1 > ARG2 ARG1 is greater than ARG2
|
||||
|
||||
ARG1 + ARG2 arithmetic sum of ARG1 and ARG2
|
||||
ARG1 - ARG2 arithmetic difference of ARG1 and ARG2
|
||||
|
||||
ARG1 * ARG2 arithmetic product of ARG1 and ARG2
|
||||
ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2
|
||||
ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2
|
||||
|
||||
STRING : REGEXP anchored pattern match of REGEXP in STRING
|
||||
|
||||
match STRING REGEXP same as STRING : REGEXP
|
||||
substr STRING POS LENGTH substring of STRING, POS counted from 1
|
||||
index STRING CHARS index in STRING where any CHARS is found, or 0
|
||||
length STRING length of STRING
|
||||
+ TOKEN interpret TOKEN as a string, even if it is a
|
||||
keyword like 'match' or an operator like '/'
|
||||
|
||||
( EXPRESSION ) value of EXPRESSION
|
||||
|
||||
Beware that many operators need to be escaped or quoted for shells.
|
||||
Comparisons are arithmetic if both ARGs are numbers, else lexicographical.
|
||||
Pattern matches return the string matched between \( and \) or null; if
|
||||
\( and \) are not used, they return the number of characters matched or 0.
|
||||
|
||||
Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null
|
||||
or 0, 2 if EXPRESSION is syntactically invalid, and 3 if an error occurred.
|
||||
|
||||
Environment variables:
|
||||
* EXPR_DEBUG_TOKENS=1 dump expression's tokens
|
||||
* EXPR_DEBUG_RPN=1 dump expression represented in reverse polish notation
|
||||
* EXPR_DEBUG_SYA_STEP=1 dump each parser step
|
||||
* EXPR_DEBUG_AST=1 dump expression represented abstract syntax tree"
|
|
@ -5,29 +5,47 @@
|
|||
//* For the full copyright and license information, please view the LICENSE
|
||||
//* file that was distributed with this source code.
|
||||
|
||||
use clap::{crate_version, Arg, Command};
|
||||
use uucore::error::{UResult, USimpleError};
|
||||
use clap::{crate_version, Arg, ArgAction, Command};
|
||||
use uucore::{
|
||||
error::{UResult, USimpleError},
|
||||
format_usage, help_section, help_usage,
|
||||
};
|
||||
|
||||
mod syntax_tree;
|
||||
mod tokens;
|
||||
|
||||
const VERSION: &str = "version";
|
||||
const HELP: &str = "help";
|
||||
static USAGE: &str = r#"Print the value of EXPRESSION to standard output
|
||||
expr [EXPRESSION]
|
||||
expr [OPTIONS]"#;
|
||||
mod options {
|
||||
pub const VERSION: &str = "version";
|
||||
pub const HELP: &str = "help";
|
||||
pub const EXPRESSION: &str = "expression";
|
||||
}
|
||||
|
||||
pub fn uu_app<'a>() -> Command<'a> {
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.override_usage(USAGE)
|
||||
.about(help_section!("about", "expr.md"))
|
||||
.override_usage(format_usage(help_usage!("expr.md")))
|
||||
.after_help(help_section!("after help", "expr.md"))
|
||||
.infer_long_args(true)
|
||||
.disable_help_flag(true)
|
||||
.disable_version_flag(true)
|
||||
.arg(
|
||||
Arg::new(VERSION)
|
||||
.long(VERSION)
|
||||
.help("output version information and exit"),
|
||||
Arg::new(options::VERSION)
|
||||
.long(options::VERSION)
|
||||
.help("output version information and exit")
|
||||
.action(ArgAction::Version),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::HELP)
|
||||
.long(options::HELP)
|
||||
.help("display this help and exit")
|
||||
.action(ArgAction::Help),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::EXPRESSION)
|
||||
.action(ArgAction::Append)
|
||||
.allow_hyphen_values(true),
|
||||
)
|
||||
.arg(Arg::new(HELP).long(HELP).help("display this help and exit"))
|
||||
}
|
||||
|
||||
#[uucore::main]
|
||||
|
@ -36,20 +54,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
|
||||
// For expr utility we do not want getopts.
|
||||
// The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)`
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
let token_strings = matches
|
||||
.get_many::<String>(options::EXPRESSION)
|
||||
.map(|v| v.into_iter().map(|s| s.as_ref()).collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
|
||||
if maybe_handle_help_or_version(&args) {
|
||||
Ok(())
|
||||
} else {
|
||||
let token_strings = args[1..].to_vec();
|
||||
|
||||
match process_expr(&token_strings) {
|
||||
Ok(expr_result) => print_expr_ok(&expr_result),
|
||||
Err(expr_error) => Err(USimpleError::new(2, &expr_error)),
|
||||
}
|
||||
match process_expr(&token_strings[..]) {
|
||||
Ok(expr_result) => print_expr_ok(&expr_result),
|
||||
Err(expr_error) => Err(USimpleError::new(2, &expr_error)),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_expr(token_strings: &[String]) -> Result<String, String> {
|
||||
fn process_expr(token_strings: &[&str]) -> Result<String, String> {
|
||||
let maybe_tokens = tokens::strings_to_tokens(token_strings);
|
||||
let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens);
|
||||
evaluate_ast(maybe_ast)
|
||||
|
@ -67,80 +84,3 @@ fn print_expr_ok(expr_result: &str) -> UResult<()> {
|
|||
fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
|
||||
maybe_ast.and_then(|ast| ast.evaluate())
|
||||
}
|
||||
|
||||
fn maybe_handle_help_or_version(args: &[String]) -> bool {
|
||||
if args.len() == 2 {
|
||||
if args[1] == "--help" {
|
||||
print_help();
|
||||
true
|
||||
} else if args[1] == "--version" {
|
||||
print_version();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
//! The following is taken from GNU coreutils' "expr --help" output.
|
||||
println!(
|
||||
r#"Usage: expr EXPRESSION
|
||||
or: expr OPTION
|
||||
|
||||
--help display this help and exit
|
||||
--version output version information and exit
|
||||
|
||||
Print the value of EXPRESSION to standard output. A blank line below
|
||||
separates increasing precedence groups. EXPRESSION may be:
|
||||
|
||||
ARG1 | ARG2 ARG1 if it is neither null nor 0, otherwise ARG2
|
||||
|
||||
ARG1 & ARG2 ARG1 if neither argument is null or 0, otherwise 0
|
||||
|
||||
ARG1 < ARG2 ARG1 is less than ARG2
|
||||
ARG1 <= ARG2 ARG1 is less than or equal to ARG2
|
||||
ARG1 = ARG2 ARG1 is equal to ARG2
|
||||
ARG1 != ARG2 ARG1 is unequal to ARG2
|
||||
ARG1 >= ARG2 ARG1 is greater than or equal to ARG2
|
||||
ARG1 > ARG2 ARG1 is greater than ARG2
|
||||
|
||||
ARG1 + ARG2 arithmetic sum of ARG1 and ARG2
|
||||
ARG1 - ARG2 arithmetic difference of ARG1 and ARG2
|
||||
|
||||
ARG1 * ARG2 arithmetic product of ARG1 and ARG2
|
||||
ARG1 / ARG2 arithmetic quotient of ARG1 divided by ARG2
|
||||
ARG1 % ARG2 arithmetic remainder of ARG1 divided by ARG2
|
||||
|
||||
STRING : REGEXP anchored pattern match of REGEXP in STRING
|
||||
|
||||
match STRING REGEXP same as STRING : REGEXP
|
||||
substr STRING POS LENGTH substring of STRING, POS counted from 1
|
||||
index STRING CHARS index in STRING where any CHARS is found, or 0
|
||||
length STRING length of STRING
|
||||
+ TOKEN interpret TOKEN as a string, even if it is a
|
||||
keyword like 'match' or an operator like '/'
|
||||
|
||||
( EXPRESSION ) value of EXPRESSION
|
||||
|
||||
Beware that many operators need to be escaped or quoted for shells.
|
||||
Comparisons are arithmetic if both ARGs are numbers, else lexicographical.
|
||||
Pattern matches return the string matched between \( and \) or null; if
|
||||
\( and \) are not used, they return the number of characters matched or 0.
|
||||
|
||||
Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null
|
||||
or 0, 2 if EXPRESSION is syntactically invalid, and 3 if an error occurred.
|
||||
|
||||
Environment variables:
|
||||
* EXPR_DEBUG_TOKENS=1 dump expression's tokens
|
||||
* EXPR_DEBUG_RPN=1 dump expression represented in reverse polish notation
|
||||
* EXPR_DEBUG_SYA_STEP=1 dump each parser step
|
||||
* EXPR_DEBUG_AST=1 dump expression represented abstract syntax tree"#
|
||||
);
|
||||
}
|
||||
|
||||
fn print_version() {
|
||||
println!("{} {}", uucore::util_name(), crate_version!());
|
||||
}
|
||||
|
|
|
@ -69,12 +69,12 @@ impl Token {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn strings_to_tokens(strings: &[String]) -> Result<Vec<(usize, Token)>, String> {
|
||||
pub fn strings_to_tokens(strings: &[&str]) -> Result<Vec<(usize, Token)>, String> {
|
||||
let mut tokens_acc = Vec::with_capacity(strings.len());
|
||||
let mut tok_idx = 1;
|
||||
|
||||
for s in strings {
|
||||
let token_if_not_escaped = match s.as_ref() {
|
||||
let token_if_not_escaped = match *s {
|
||||
"(" => Token::ParOpen,
|
||||
")" => Token::ParClose,
|
||||
|
||||
|
@ -94,15 +94,15 @@ pub fn strings_to_tokens(strings: &[String]) -> Result<Vec<(usize, Token)>, Stri
|
|||
|
||||
"match" | "index" => Token::PrefixOp {
|
||||
arity: 2,
|
||||
value: s.clone(),
|
||||
value: s.to_string(),
|
||||
},
|
||||
"substr" => Token::PrefixOp {
|
||||
arity: 3,
|
||||
value: s.clone(),
|
||||
value: s.to_string(),
|
||||
},
|
||||
"length" => Token::PrefixOp {
|
||||
arity: 1,
|
||||
value: s.clone(),
|
||||
value: s.to_string(),
|
||||
},
|
||||
|
||||
_ => Token::new_value(s),
|
||||
|
|
|
@ -168,9 +168,9 @@ fn parse_usage(content: &str) -> String {
|
|||
// Replace the util name (assumed to be the first word) with "{}"
|
||||
// to be replaced with the runtime value later.
|
||||
if let Some((_util, args)) = l.split_once(' ') {
|
||||
format!("{{}} {}", args)
|
||||
format!("{{}} {}\n", args)
|
||||
} else {
|
||||
"{}".to_string()
|
||||
"{}\n".to_string()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
|
Loading…
Reference in a new issue