mirror of
https://github.com/uutils/coreutils
synced 2024-12-14 07:12:44 +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"
|
path = "src/expr.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "3.2", features = ["wrap_help", "cargo"] }
|
clap = { version = "4.0", features = ["wrap_help", "cargo"] }
|
||||||
num-bigint = "0.4.0"
|
num-bigint = "0.4.0"
|
||||||
num-traits = "0.2.15"
|
num-traits = "0.2.15"
|
||||||
onig = { version = "~6.4", default-features = false }
|
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
|
//* For the full copyright and license information, please view the LICENSE
|
||||||
//* file that was distributed with this source code.
|
//* file that was distributed with this source code.
|
||||||
|
|
||||||
use clap::{crate_version, Arg, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::{
|
||||||
|
error::{UResult, USimpleError},
|
||||||
|
format_usage, help_section, help_usage,
|
||||||
|
};
|
||||||
|
|
||||||
mod syntax_tree;
|
mod syntax_tree;
|
||||||
mod tokens;
|
mod tokens;
|
||||||
|
|
||||||
const VERSION: &str = "version";
|
mod options {
|
||||||
const HELP: &str = "help";
|
pub const VERSION: &str = "version";
|
||||||
static USAGE: &str = r#"Print the value of EXPRESSION to standard output
|
pub const HELP: &str = "help";
|
||||||
expr [EXPRESSION]
|
pub const EXPRESSION: &str = "expression";
|
||||||
expr [OPTIONS]"#;
|
}
|
||||||
|
|
||||||
pub fn uu_app<'a>() -> Command<'a> {
|
pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
.version(crate_version!())
|
.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)
|
.infer_long_args(true)
|
||||||
|
.disable_help_flag(true)
|
||||||
|
.disable_version_flag(true)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(VERSION)
|
Arg::new(options::VERSION)
|
||||||
.long(VERSION)
|
.long(options::VERSION)
|
||||||
.help("output version information and exit"),
|
.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]
|
#[uucore::main]
|
||||||
|
@ -36,20 +54,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
|
||||||
// For expr utility we do not want getopts.
|
// For expr utility we do not want getopts.
|
||||||
// The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)`
|
// 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) {
|
match process_expr(&token_strings[..]) {
|
||||||
Ok(())
|
Ok(expr_result) => print_expr_ok(&expr_result),
|
||||||
} else {
|
Err(expr_error) => Err(USimpleError::new(2, &expr_error)),
|
||||||
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)),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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_tokens = tokens::strings_to_tokens(token_strings);
|
||||||
let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens);
|
let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens);
|
||||||
evaluate_ast(maybe_ast)
|
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> {
|
fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
|
||||||
maybe_ast.and_then(|ast| ast.evaluate())
|
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 tokens_acc = Vec::with_capacity(strings.len());
|
||||||
let mut tok_idx = 1;
|
let mut tok_idx = 1;
|
||||||
|
|
||||||
for s in strings {
|
for s in strings {
|
||||||
let token_if_not_escaped = match s.as_ref() {
|
let token_if_not_escaped = match *s {
|
||||||
"(" => Token::ParOpen,
|
"(" => Token::ParOpen,
|
||||||
")" => Token::ParClose,
|
")" => Token::ParClose,
|
||||||
|
|
||||||
|
@ -94,15 +94,15 @@ pub fn strings_to_tokens(strings: &[String]) -> Result<Vec<(usize, Token)>, Stri
|
||||||
|
|
||||||
"match" | "index" => Token::PrefixOp {
|
"match" | "index" => Token::PrefixOp {
|
||||||
arity: 2,
|
arity: 2,
|
||||||
value: s.clone(),
|
value: s.to_string(),
|
||||||
},
|
},
|
||||||
"substr" => Token::PrefixOp {
|
"substr" => Token::PrefixOp {
|
||||||
arity: 3,
|
arity: 3,
|
||||||
value: s.clone(),
|
value: s.to_string(),
|
||||||
},
|
},
|
||||||
"length" => Token::PrefixOp {
|
"length" => Token::PrefixOp {
|
||||||
arity: 1,
|
arity: 1,
|
||||||
value: s.clone(),
|
value: s.to_string(),
|
||||||
},
|
},
|
||||||
|
|
||||||
_ => Token::new_value(s),
|
_ => 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 "{}"
|
// Replace the util name (assumed to be the first word) with "{}"
|
||||||
// to be replaced with the runtime value later.
|
// to be replaced with the runtime value later.
|
||||||
if let Some((_util, args)) = l.split_once(' ') {
|
if let Some((_util, args)) = l.split_once(' ') {
|
||||||
format!("{{}} {}", args)
|
format!("{{}} {}\n", args)
|
||||||
} else {
|
} else {
|
||||||
"{}".to_string()
|
"{}\n".to_string()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
Loading…
Reference in a new issue