mirror of
https://github.com/nushell/nushell
synced 2024-12-27 21:43:09 +00:00
Add the default help flag
This commit is contained in:
parent
ff6cc2cbdd
commit
fdd2c35fd9
8 changed files with 89 additions and 41 deletions
|
@ -2,6 +2,8 @@ use nu_protocol::ast::{Block, Call, Expr, Expression, Operator, Statement};
|
||||||
use nu_protocol::engine::EvaluationContext;
|
use nu_protocol::engine::EvaluationContext;
|
||||||
use nu_protocol::{Range, ShellError, Span, Spanned, Type, Unit, Value};
|
use nu_protocol::{Range, ShellError, Span, Spanned, Type, Unit, Value};
|
||||||
|
|
||||||
|
use crate::get_full_help;
|
||||||
|
|
||||||
pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
|
pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
|
||||||
match op {
|
match op {
|
||||||
Expression {
|
Expression {
|
||||||
|
@ -17,7 +19,13 @@ pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
|
||||||
fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result<Value, ShellError> {
|
fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result<Value, ShellError> {
|
||||||
let engine_state = context.engine_state.borrow();
|
let engine_state = context.engine_state.borrow();
|
||||||
let decl = engine_state.get_decl(call.decl_id);
|
let decl = engine_state.get_decl(call.decl_id);
|
||||||
if let Some(block_id) = decl.get_block_id() {
|
if call.named.iter().any(|(flag, _)| flag.item == "help") {
|
||||||
|
let full_help = get_full_help(&decl.signature(), &decl.examples(), context);
|
||||||
|
Ok(Value::String {
|
||||||
|
val: full_help,
|
||||||
|
span: call.head,
|
||||||
|
})
|
||||||
|
} else if let Some(block_id) = decl.get_block_id() {
|
||||||
let state = context.enter_scope();
|
let state = context.enter_scope();
|
||||||
for (arg, param) in call.positional.iter().zip(
|
for (arg, param) in call.positional.iter().zip(
|
||||||
decl.signature()
|
decl.signature()
|
||||||
|
@ -62,38 +70,36 @@ fn eval_call(context: &EvaluationContext, call: &Call, input: Value) -> Result<V
|
||||||
}
|
}
|
||||||
|
|
||||||
for named in decl.signature().named {
|
for named in decl.signature().named {
|
||||||
let var_id = named
|
if let Some(var_id) = named.var_id {
|
||||||
.var_id
|
let mut found = false;
|
||||||
.expect("internal error: all custom parameters must have var_ids");
|
for call_named in &call.named {
|
||||||
|
if call_named.0.item == named.long {
|
||||||
|
if let Some(arg) = &call_named.1 {
|
||||||
|
let result = eval_expression(&state, arg)?;
|
||||||
|
|
||||||
let mut found = false;
|
state.add_var(var_id, result);
|
||||||
for call_named in &call.named {
|
} else {
|
||||||
if call_named.0.item == named.long {
|
state.add_var(
|
||||||
if let Some(arg) = &call_named.1 {
|
var_id,
|
||||||
let result = eval_expression(&state, arg)?;
|
Value::Bool {
|
||||||
|
val: true,
|
||||||
state.add_var(var_id, result);
|
span: call.head,
|
||||||
} else {
|
},
|
||||||
state.add_var(
|
)
|
||||||
var_id,
|
}
|
||||||
Value::Bool {
|
found = true;
|
||||||
val: true,
|
|
||||||
span: call.head,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
found = true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !found && named.arg.is_none() {
|
if !found && named.arg.is_none() {
|
||||||
state.add_var(
|
state.add_var(
|
||||||
var_id,
|
var_id,
|
||||||
Value::Bool {
|
Value::Bool {
|
||||||
val: false,
|
val: false,
|
||||||
span: call.head,
|
span: call.head,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let engine_state = state.engine_state.borrow();
|
let engine_state = state.engine_state.borrow();
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use nu_protocol::ast::{Block, Expr, Expression, PathMember, Pipeline, Statement};
|
use nu_protocol::ast::{Block, Expr, Expression, PathMember, Pipeline, Statement};
|
||||||
use nu_protocol::{engine::StateWorkingSet, Span};
|
use nu_protocol::{engine::StateWorkingSet, Span};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
pub enum FlatShape {
|
pub enum FlatShape {
|
||||||
Garbage,
|
Garbage,
|
||||||
Bool,
|
Bool,
|
||||||
|
@ -59,15 +59,21 @@ pub fn flatten_expression(
|
||||||
Expr::Block(block_id) => flatten_block(working_set, working_set.get_block(*block_id)),
|
Expr::Block(block_id) => flatten_block(working_set, working_set.get_block(*block_id)),
|
||||||
Expr::Call(call) => {
|
Expr::Call(call) => {
|
||||||
let mut output = vec![(call.head, FlatShape::InternalCall)];
|
let mut output = vec![(call.head, FlatShape::InternalCall)];
|
||||||
|
|
||||||
|
let mut args = vec![];
|
||||||
for positional in &call.positional {
|
for positional in &call.positional {
|
||||||
output.extend(flatten_expression(working_set, positional));
|
args.extend(flatten_expression(working_set, positional));
|
||||||
}
|
}
|
||||||
for named in &call.named {
|
for named in &call.named {
|
||||||
output.push((named.0.span, FlatShape::Flag));
|
args.push((named.0.span, FlatShape::Flag));
|
||||||
if let Some(expr) = &named.1 {
|
if let Some(expr) = &named.1 {
|
||||||
output.extend(flatten_expression(working_set, expr));
|
args.extend(flatten_expression(working_set, expr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// sort these since flags and positional args can be intermixed
|
||||||
|
args.sort();
|
||||||
|
|
||||||
|
output.extend(args);
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
Expr::ExternalCall(_, name_span, args) => {
|
Expr::ExternalCall(_, name_span, args) => {
|
||||||
|
|
|
@ -52,6 +52,11 @@ fn is_variable(bytes: &[u8]) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_call(command: Span, sig: &Signature, call: &Call) -> Option<ParseError> {
|
fn check_call(command: Span, sig: &Signature, call: &Call) -> Option<ParseError> {
|
||||||
|
// Allow the call to pass if they pass in the help flag
|
||||||
|
if call.named.iter().any(|(n, _)| n.item == "help") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
if call.positional.len() < sig.required_positional.len() {
|
if call.positional.len() < sig.required_positional.len() {
|
||||||
let missing = &sig.required_positional[call.positional.len()];
|
let missing = &sig.required_positional[call.positional.len()];
|
||||||
Some(ParseError::MissingPositional(missing.name.clone(), command))
|
Some(ParseError::MissingPositional(missing.name.clone(), command))
|
||||||
|
@ -153,6 +158,7 @@ fn parse_long_flag(
|
||||||
let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect();
|
let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect();
|
||||||
let long_name = String::from_utf8(split[0].into());
|
let long_name = String::from_utf8(split[0].into());
|
||||||
if let Ok(long_name) = long_name {
|
if let Ok(long_name) = long_name {
|
||||||
|
let long_name = long_name[2..].to_string();
|
||||||
if let Some(flag) = sig.get_long_flag(&long_name) {
|
if let Some(flag) = sig.get_long_flag(&long_name) {
|
||||||
if let Some(arg_shape) = &flag.arg {
|
if let Some(arg_shape) = &flag.arg {
|
||||||
if split.len() > 1 {
|
if split.len() > 1 {
|
||||||
|
@ -1974,7 +1980,7 @@ pub fn parse_signature_helper(
|
||||||
let flags: Vec<_> =
|
let flags: Vec<_> =
|
||||||
contents.split(|x| x == &b'(').map(|x| x.to_vec()).collect();
|
contents.split(|x| x == &b'(').map(|x| x.to_vec()).collect();
|
||||||
|
|
||||||
let long = String::from_utf8_lossy(&flags[0]).to_string();
|
let long = String::from_utf8_lossy(&flags[0][2..]).to_string();
|
||||||
let variable_name = flags[0][2..].to_vec();
|
let variable_name = flags[0][2..].to_vec();
|
||||||
let var_id = working_set.add_variable(variable_name, Type::Unknown);
|
let var_id = working_set.add_variable(variable_name, Type::Unknown);
|
||||||
|
|
||||||
|
@ -2003,7 +2009,7 @@ pub fn parse_signature_helper(
|
||||||
let short_flag =
|
let short_flag =
|
||||||
String::from_utf8_lossy(short_flag).to_string();
|
String::from_utf8_lossy(short_flag).to_string();
|
||||||
let chars: Vec<char> = short_flag.chars().collect();
|
let chars: Vec<char> = short_flag.chars().collect();
|
||||||
let long = String::from_utf8_lossy(&flags[0]).to_string();
|
let long = String::from_utf8_lossy(&flags[0][2..]).to_string();
|
||||||
let variable_name = flags[0][2..].to_vec();
|
let variable_name = flags[0][2..].to_vec();
|
||||||
let var_id =
|
let var_id =
|
||||||
working_set.add_variable(variable_name, Type::Unknown);
|
working_set.add_variable(variable_name, Type::Unknown);
|
||||||
|
|
|
@ -97,7 +97,7 @@ pub fn parse_call_missing_flag_arg() {
|
||||||
let engine_state = EngineState::new();
|
let engine_state = EngineState::new();
|
||||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||||
|
|
||||||
let sig = Signature::build("foo").named("--jazz", SyntaxShape::Int, "jazz!!", Some('j'));
|
let sig = Signature::build("foo").named("jazz", SyntaxShape::Int, "jazz!!", Some('j'));
|
||||||
working_set.add_decl(sig.predeclare());
|
working_set.add_decl(sig.predeclare());
|
||||||
|
|
||||||
let (_, err) = parse(&mut working_set, None, b"foo --jazz", true);
|
let (_, err) = parse(&mut working_set, None, b"foo --jazz", true);
|
||||||
|
|
|
@ -54,6 +54,16 @@ impl Eq for Signature {}
|
||||||
|
|
||||||
impl Signature {
|
impl Signature {
|
||||||
pub fn new(name: impl Into<String>) -> Signature {
|
pub fn new(name: impl Into<String>) -> Signature {
|
||||||
|
// default help flag
|
||||||
|
let flag = Flag {
|
||||||
|
long: "help".into(),
|
||||||
|
short: Some('h'),
|
||||||
|
arg: None,
|
||||||
|
desc: "Display this help message".into(),
|
||||||
|
required: false,
|
||||||
|
var_id: None,
|
||||||
|
};
|
||||||
|
|
||||||
Signature {
|
Signature {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
usage: String::new(),
|
usage: String::new(),
|
||||||
|
@ -61,7 +71,7 @@ impl Signature {
|
||||||
required_positional: vec![],
|
required_positional: vec![],
|
||||||
optional_positional: vec![],
|
optional_positional: vec![],
|
||||||
rest_positional: None,
|
rest_positional: None,
|
||||||
named: vec![],
|
named: vec![flag],
|
||||||
is_filter: false,
|
is_filter: false,
|
||||||
creates_scope: false,
|
creates_scope: false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ where
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub struct Span {
|
pub struct Span {
|
||||||
pub start: usize,
|
pub start: usize,
|
||||||
pub end: usize,
|
pub end: usize,
|
||||||
|
@ -37,6 +37,10 @@ impl Span {
|
||||||
end: self.end - offset,
|
end: self.end - offset,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, pos: usize) -> bool {
|
||||||
|
pos >= self.start && pos < self.end
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn span(spans: &[Span]) -> Span {
|
pub fn span(spans: &[Span]) -> Span {
|
||||||
|
|
|
@ -31,10 +31,13 @@ fn test_signature_chained() {
|
||||||
|
|
||||||
assert_eq!(signature.required_positional.len(), 1);
|
assert_eq!(signature.required_positional.len(), 1);
|
||||||
assert_eq!(signature.optional_positional.len(), 1);
|
assert_eq!(signature.optional_positional.len(), 1);
|
||||||
assert_eq!(signature.named.len(), 3);
|
assert_eq!(signature.named.len(), 4); // The 3 above + help
|
||||||
assert!(signature.rest_positional.is_some());
|
assert!(signature.rest_positional.is_some());
|
||||||
assert_eq!(signature.get_shorts(), vec!['r', 'n']);
|
assert_eq!(signature.get_shorts(), vec!['h', 'r', 'n']);
|
||||||
assert_eq!(signature.get_names(), vec!["req_named", "named", "switch"]);
|
assert_eq!(
|
||||||
|
signature.get_names(),
|
||||||
|
vec!["help", "req_named", "named", "switch"]
|
||||||
|
);
|
||||||
assert_eq!(signature.num_positionals(), 2);
|
assert_eq!(signature.num_positionals(), 2);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
13
src/tests.rs
13
src/tests.rs
|
@ -764,3 +764,16 @@ fn custom_switch4() -> TestResult {
|
||||||
fn bad_var_name() -> TestResult {
|
fn bad_var_name() -> TestResult {
|
||||||
fail_test(r#"let $"foo bar" = 4"#, "can't contain")
|
fail_test(r#"let $"foo bar" = 4"#, "can't contain")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn long_flag() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"([a, b, c] | each --numbered { if $it.index == 1 { 100 } else { 0 } }).1"#,
|
||||||
|
"100",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_works_with_missing_requirements() -> TestResult {
|
||||||
|
run_test(r#"each --help | lines | length"#, "10")
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue