Merge pull request #232 from nushell/add_help_flag

Add help flag
This commit is contained in:
JT 2021-10-14 07:07:30 +13:00 committed by GitHub
commit 87d57108e6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 127 additions and 47 deletions

View file

@ -260,11 +260,28 @@ fn get_flags_section(signature: &Signature) -> String {
if let Some(short) = flag.short { if let Some(short) = flag.short {
if flag.required { if flag.required {
format!( format!(
" -{}, --{} (required parameter){:?} {}\n", " -{}{} (required parameter){:?} {}\n",
short, flag.long, arg, flag.desc short,
if !flag.long.is_empty() {
format!(", --{}", flag.long)
} else {
"".into()
},
arg,
flag.desc
) )
} else { } else {
format!(" -{}, --{} {:?} {}\n", short, flag.long, arg, flag.desc) format!(
" -{}{} {:?} {}\n",
short,
if !flag.long.is_empty() {
format!(", --{}", flag.long)
} else {
"".into()
},
arg,
flag.desc
)
} }
} else if flag.required { } else if flag.required {
format!( format!(
@ -277,11 +294,26 @@ fn get_flags_section(signature: &Signature) -> String {
} else if let Some(short) = flag.short { } else if let Some(short) = flag.short {
if flag.required { if flag.required {
format!( format!(
" -{}, --{} (required parameter) {}\n", " -{}{} (required parameter) {}\n",
short, flag.long, flag.desc short,
if !flag.long.is_empty() {
format!(", --{}", flag.long)
} else {
"".into()
},
flag.desc
) )
} else { } else {
format!(" -{}, --{} {}\n", short, flag.long, flag.desc) format!(
" -{}{} {}\n",
short,
if !flag.long.is_empty() {
format!(", --{}", flag.long)
} else {
"".into()
},
flag.desc
)
} }
} else if flag.required { } else if flag.required {
format!(" --{} (required parameter) {}\n", flag.long, flag.desc) format!(" --{} (required parameter) {}\n", flag.long, flag.desc)

View file

@ -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();

View file

@ -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) => {

View file

@ -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);

View file

@ -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);

View file

@ -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,
} }

View file

@ -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 {

View file

@ -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!(

View file

@ -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")
}