mirror of
https://github.com/nushell/nushell.git
synced 2025-04-30 16:14:27 +02:00
Add new operators has
and not-has
(#14841)
# Description This PR add 2 new operators, `has` and `not-has`. They are basically `in` and `not-in` with the order of operands swapped. Motivation for this was the awkward way of searching for rows that contain an item using `where` ```nushell [[name, children]; [foo, [a, b, c]], [bar [d, e, f]]] | where ("e" in $it.children) ``` vs ```nushell [[name, children]; [foo, [a, b, c]], [bar [d, e, f]]] | where children has "e" ``` # User-Facing Changes Added `has` and `not-has` operators, mirroring `in` and `not-in`. # Tests + Formatting - 🟢 toolkit fmt - 🟢 toolkit clippy - 🟢 toolkit test - 🟢 toolkit test stdlib # After Submitting
This commit is contained in:
parent
0587308684
commit
089c5221cc
@ -107,10 +107,14 @@ impl Completer for OperatorCompletion {
|
|||||||
("not-in", "Is not a member of (doesn't use regex)"),
|
("not-in", "Is not a member of (doesn't use regex)"),
|
||||||
],
|
],
|
||||||
Expr::FullCellPath(path) => match path.head.expr {
|
Expr::FullCellPath(path) => match path.head.expr {
|
||||||
Expr::List(_) => vec![(
|
Expr::List(_) => vec![
|
||||||
|
(
|
||||||
"++",
|
"++",
|
||||||
"Concatenates two lists, two strings, or two binary values",
|
"Concatenates two lists, two strings, or two binary values",
|
||||||
)],
|
),
|
||||||
|
("has", "Contains a value of (doesn't use regex)"),
|
||||||
|
("not-has", "Does not contain a value of (doesn't use regex)"),
|
||||||
|
],
|
||||||
Expr::Var(id) => get_variable_completions(id, working_set),
|
Expr::Var(id) => get_variable_completions(id, working_set),
|
||||||
_ => vec![],
|
_ => vec![],
|
||||||
},
|
},
|
||||||
|
@ -141,6 +141,12 @@ fn description(operator: &Operator) -> &'static str {
|
|||||||
Operator::Comparison(Comparison::NotIn) => {
|
Operator::Comparison(Comparison::NotIn) => {
|
||||||
"Checks if a value is not in a list, is not part of a string, or is not a key in a record."
|
"Checks if a value is not in a list, is not part of a string, or is not a key in a record."
|
||||||
}
|
}
|
||||||
|
Operator::Comparison(Comparison::Has) => {
|
||||||
|
"Checks if a list contains a value, a string contains another, or if a record has a key."
|
||||||
|
}
|
||||||
|
Operator::Comparison(Comparison::NotHas) => {
|
||||||
|
"Checks if a list does not contain a value, a string does not contain another, or if a record does not have a key."
|
||||||
|
}
|
||||||
Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.",
|
Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.",
|
||||||
Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.",
|
Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.",
|
||||||
Operator::Math(Math::Plus) => "Adds two values.",
|
Operator::Math(Math::Plus) => "Adds two values.",
|
||||||
|
@ -187,3 +187,25 @@ fn where_gt_null() {
|
|||||||
let actual = nu!("[{foo: 123} {}] | where foo? > 10 | to nuon");
|
let actual = nu!("[{foo: 123} {}] | where foo? > 10 | to nuon");
|
||||||
assert_eq!(actual.out, "[[foo]; [123]]");
|
assert_eq!(actual.out, "[[foo]; [123]]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn has_operator() {
|
||||||
|
let actual = nu!(
|
||||||
|
r#"[[name, children]; [foo, [a, b]], [bar [b, c]], [baz, [c, d]]] | where children has "a" | to nuon"#
|
||||||
|
);
|
||||||
|
assert_eq!(actual.out, r#"[[name, children]; [foo, [a, b]]]"#);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
r#"[[name, children]; [foo, [a, b]], [bar [b, c]], [baz, [c, d]]] | where children not-has "a" | to nuon"#
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
actual.out,
|
||||||
|
r#"[[name, children]; [bar, [b, c]], [baz, [c, d]]]"#
|
||||||
|
);
|
||||||
|
|
||||||
|
let actual = nu!(r#"{foo: 1} has foo"#);
|
||||||
|
assert_eq!(actual.out, "true");
|
||||||
|
|
||||||
|
let actual = nu!(r#"{foo: 1} has bar "#);
|
||||||
|
assert_eq!(actual.out, "false");
|
||||||
|
}
|
||||||
|
@ -952,6 +952,8 @@ fn binary_op(
|
|||||||
}
|
}
|
||||||
Comparison::In => lhs_val.r#in(op_span, &rhs_val, span)?,
|
Comparison::In => lhs_val.r#in(op_span, &rhs_val, span)?,
|
||||||
Comparison::NotIn => lhs_val.not_in(op_span, &rhs_val, span)?,
|
Comparison::NotIn => lhs_val.not_in(op_span, &rhs_val, span)?,
|
||||||
|
Comparison::Has => lhs_val.has(op_span, &rhs_val, span)?,
|
||||||
|
Comparison::NotHas => lhs_val.not_has(op_span, &rhs_val, span)?,
|
||||||
Comparison::StartsWith => lhs_val.starts_with(op_span, &rhs_val, span)?,
|
Comparison::StartsWith => lhs_val.starts_with(op_span, &rhs_val, span)?,
|
||||||
Comparison::EndsWith => lhs_val.ends_with(op_span, &rhs_val, span)?,
|
Comparison::EndsWith => lhs_val.ends_with(op_span, &rhs_val, span)?,
|
||||||
},
|
},
|
||||||
|
@ -5147,6 +5147,8 @@ pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expressi
|
|||||||
b"//" => Operator::Math(Math::FloorDivision),
|
b"//" => Operator::Math(Math::FloorDivision),
|
||||||
b"in" => Operator::Comparison(Comparison::In),
|
b"in" => Operator::Comparison(Comparison::In),
|
||||||
b"not-in" => Operator::Comparison(Comparison::NotIn),
|
b"not-in" => Operator::Comparison(Comparison::NotIn),
|
||||||
|
b"has" => Operator::Comparison(Comparison::Has),
|
||||||
|
b"not-has" => Operator::Comparison(Comparison::NotHas),
|
||||||
b"mod" => Operator::Math(Math::Modulo),
|
b"mod" => Operator::Math(Math::Modulo),
|
||||||
b"bit-or" => Operator::Bits(Bits::BitOr),
|
b"bit-or" => Operator::Bits(Bits::BitOr),
|
||||||
b"bit-xor" => Operator::Bits(Bits::BitXor),
|
b"bit-xor" => Operator::Bits(Bits::BitXor),
|
||||||
@ -5187,7 +5189,7 @@ pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expressi
|
|||||||
b"contains" => {
|
b"contains" => {
|
||||||
working_set.error(ParseError::UnknownOperator(
|
working_set.error(ParseError::UnknownOperator(
|
||||||
"contains",
|
"contains",
|
||||||
"Did you mean '$string =~ $pattern' or '$element in $container'?",
|
"Did you mean 'has'?",
|
||||||
span,
|
span,
|
||||||
));
|
));
|
||||||
return garbage(working_set, span);
|
return garbage(working_set, span);
|
||||||
|
@ -821,7 +821,7 @@ pub fn math_result_type(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Operator::Comparison(Comparison::In) => match (&lhs.ty, &rhs.ty) {
|
Operator::Comparison(Comparison::In | Comparison::NotIn) => match (&lhs.ty, &rhs.ty) {
|
||||||
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
|
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
|
||||||
(Type::Int | Type::Float | Type::Number, Type::Range) => (Type::Bool, None),
|
(Type::Int | Type::Float | Type::Number, Type::Range) => (Type::Bool, None),
|
||||||
(Type::String, Type::String) => (Type::Bool, None),
|
(Type::String, Type::String) => (Type::Bool, None),
|
||||||
@ -859,7 +859,10 @@ pub fn math_result_type(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Operator::Comparison(Comparison::NotIn) => match (&lhs.ty, &rhs.ty) {
|
Operator::Comparison(Comparison::Has | Comparison::NotHas) => {
|
||||||
|
let container = lhs;
|
||||||
|
let element = rhs;
|
||||||
|
match (&element.ty, &container.ty) {
|
||||||
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
|
(t, Type::List(u)) if type_compatible(t, u) => (Type::Bool, None),
|
||||||
(Type::Int | Type::Float | Type::Number, Type::Range) => (Type::Bool, None),
|
(Type::Int | Type::Float | Type::Number, Type::Range) => (Type::Bool, None),
|
||||||
(Type::String, Type::String) => (Type::Bool, None),
|
(Type::String, Type::String) => (Type::Bool, None),
|
||||||
@ -874,13 +877,11 @@ pub fn math_result_type(
|
|||||||
*op = Expression::garbage(working_set, op.span);
|
*op = Expression::garbage(working_set, op.span);
|
||||||
(
|
(
|
||||||
Type::Any,
|
Type::Any,
|
||||||
Some(ParseError::UnsupportedOperationRHS(
|
Some(ParseError::UnsupportedOperationLHS(
|
||||||
"subset comparison".into(),
|
"subset comparison".into(),
|
||||||
op.span,
|
op.span,
|
||||||
lhs.span,
|
element.span,
|
||||||
lhs.ty.clone(),
|
element.ty.clone(),
|
||||||
rhs.span,
|
|
||||||
rhs.ty.clone(),
|
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -888,15 +889,18 @@ pub fn math_result_type(
|
|||||||
*op = Expression::garbage(working_set, op.span);
|
*op = Expression::garbage(working_set, op.span);
|
||||||
(
|
(
|
||||||
Type::Any,
|
Type::Any,
|
||||||
Some(ParseError::UnsupportedOperationLHS(
|
Some(ParseError::UnsupportedOperationRHS(
|
||||||
"subset comparison".into(),
|
"subset comparison".into(),
|
||||||
op.span,
|
op.span,
|
||||||
lhs.span,
|
element.span,
|
||||||
lhs.ty.clone(),
|
element.ty.clone(),
|
||||||
|
container.span,
|
||||||
|
container.ty.clone(),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
Operator::Bits(Bits::ShiftLeft)
|
Operator::Bits(Bits::ShiftLeft)
|
||||||
| Operator::Bits(Bits::ShiftRight)
|
| Operator::Bits(Bits::ShiftRight)
|
||||||
| Operator::Bits(Bits::BitOr)
|
| Operator::Bits(Bits::BitOr)
|
||||||
|
@ -17,6 +17,8 @@ pub enum Comparison {
|
|||||||
NotRegexMatch,
|
NotRegexMatch,
|
||||||
In,
|
In,
|
||||||
NotIn,
|
NotIn,
|
||||||
|
Has,
|
||||||
|
NotHas,
|
||||||
StartsWith,
|
StartsWith,
|
||||||
EndsWith,
|
EndsWith,
|
||||||
}
|
}
|
||||||
@ -90,6 +92,8 @@ impl Operator {
|
|||||||
| Self::Comparison(Comparison::NotEqual)
|
| Self::Comparison(Comparison::NotEqual)
|
||||||
| Self::Comparison(Comparison::In)
|
| Self::Comparison(Comparison::In)
|
||||||
| Self::Comparison(Comparison::NotIn)
|
| Self::Comparison(Comparison::NotIn)
|
||||||
|
| Self::Comparison(Comparison::Has)
|
||||||
|
| Self::Comparison(Comparison::NotHas)
|
||||||
| Self::Math(Math::Concat) => 80,
|
| Self::Math(Math::Concat) => 80,
|
||||||
Self::Bits(Bits::BitAnd) => 75,
|
Self::Bits(Bits::BitAnd) => 75,
|
||||||
Self::Bits(Bits::BitXor) => 70,
|
Self::Bits(Bits::BitXor) => 70,
|
||||||
@ -123,6 +127,8 @@ impl Display for Operator {
|
|||||||
Operator::Comparison(Comparison::EndsWith) => write!(f, "ends-with"),
|
Operator::Comparison(Comparison::EndsWith) => write!(f, "ends-with"),
|
||||||
Operator::Comparison(Comparison::In) => write!(f, "in"),
|
Operator::Comparison(Comparison::In) => write!(f, "in"),
|
||||||
Operator::Comparison(Comparison::NotIn) => write!(f, "not-in"),
|
Operator::Comparison(Comparison::NotIn) => write!(f, "not-in"),
|
||||||
|
Operator::Comparison(Comparison::Has) => write!(f, "has"),
|
||||||
|
Operator::Comparison(Comparison::NotHas) => write!(f, "not-has"),
|
||||||
Operator::Math(Math::Plus) => write!(f, "+"),
|
Operator::Math(Math::Plus) => write!(f, "+"),
|
||||||
Operator::Math(Math::Concat) => write!(f, "++"),
|
Operator::Math(Math::Concat) => write!(f, "++"),
|
||||||
Operator::Math(Math::Minus) => write!(f, "-"),
|
Operator::Math(Math::Minus) => write!(f, "-"),
|
||||||
|
@ -256,6 +256,8 @@ pub trait Eval {
|
|||||||
Comparison::NotEqual => lhs.ne(op_span, &rhs, expr_span),
|
Comparison::NotEqual => lhs.ne(op_span, &rhs, expr_span),
|
||||||
Comparison::In => lhs.r#in(op_span, &rhs, expr_span),
|
Comparison::In => lhs.r#in(op_span, &rhs, expr_span),
|
||||||
Comparison::NotIn => lhs.not_in(op_span, &rhs, expr_span),
|
Comparison::NotIn => lhs.not_in(op_span, &rhs, expr_span),
|
||||||
|
Comparison::Has => lhs.has(op_span, &rhs, expr_span),
|
||||||
|
Comparison::NotHas => lhs.not_has(op_span, &rhs, expr_span),
|
||||||
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr_span),
|
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr_span),
|
||||||
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr_span),
|
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr_span),
|
||||||
Comparison::RegexMatch => {
|
Comparison::RegexMatch => {
|
||||||
|
@ -3540,6 +3540,14 @@ impl Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||||
|
rhs.r#in(op, self, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn not_has(&self, op: Span, rhs: &Value, span: Span) -> Result<Value, ShellError> {
|
||||||
|
rhs.r#not_in(op, self, span)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn regex_match(
|
pub fn regex_match(
|
||||||
&self,
|
&self,
|
||||||
engine_state: &EngineState,
|
engine_state: &EngineState,
|
||||||
|
Loading…
Reference in New Issue
Block a user