mirror of
https://github.com/ClementTsang/bottom.git
synced 2026-05-03 21:40:32 +00:00
feature: support negation in queries (#1948)
This PR adds the ability to use negation in queries. For example, `!=` as an equality operator `(cpu != 10)` and around groups, like `!(cpu > 5 or mem < 10)`. --------- Co-authored-by: ClementTsang <34804052+ClementTsang@users.noreply.github.com>
This commit is contained in:
@@ -33,6 +33,7 @@ That said, these are more guidelines rather than hard rules, though the project
|
||||
- [#2003](https://github.com/ClementTsang/bottom/pull/2003): Configurable default sort column for temperature and disk table widgets.
|
||||
- [#1979](https://github.com/ClementTsang/bottom/pull/1979): Add global `bg_color` option to set widget background colour.
|
||||
- [#2039](https://github.com/ClementTsang/bottom/pull/2039): Support drawing a line separator between the column headers and data.
|
||||
- [#1948](https://github.com/ClementTsang/bottom/pull/1948): Support `!=` operator and `!` negation prefixes in query searches.
|
||||
|
||||
### Changes
|
||||
|
||||
|
||||
@@ -177,6 +177,7 @@ Note all keywords are case-insensitive. To search for a process/command that col
|
||||
| Keywords | Description |
|
||||
| -------- | -------------------------------------------------------------- |
|
||||
| `=` | Checks if the values are equal |
|
||||
| `!=` | Checks if the values are not equal |
|
||||
| `>` | Checks if the left value is strictly greater than the right |
|
||||
| `<` | Checks if the left value is strictly less than the right |
|
||||
| `>=` | Checks if the left value is greater than or equal to the right |
|
||||
@@ -190,6 +191,9 @@ Note all operators are case-insensitive, and the `and` operator takes precedence
|
||||
| ------------------------------------ | ------------------------------------------------------------------------------ | --------------------------------------------------- |
|
||||
| `and` <br/> `&&` <br/> `<Space>` | `<COND 1> and <COND 2>` <br/> `<COND 1> && <COND 2>` <br/> `<COND 1> <COND 2>` | Requires both conditions to be true to match |
|
||||
| `or` <br/> <code>||</code> | `<COND 1> or <COND 2>` <br/> `<COND 1> || <COND 2>` | Requires at least one condition to be true to match |
|
||||
| `!` | `!<COND>` <br/> `!(<COND 1> or <COND 2>)` | Inverts the following condition or group |
|
||||
|
||||
`!` is reserved as an operator, so it cannot appear bare as a value. To match a literal `!` in a name or string field, quote it — e.g. `"foo!"` or `user = "!"`. A bare `!` with nothing parseable after it (such as `user = !` or `!` on its own) is rejected.
|
||||
|
||||
#### Units
|
||||
|
||||
|
||||
+3
-1
@@ -84,7 +84,7 @@ const PROCESS_HELP_TEXT: [&str; 20] = [
|
||||
"z Toggle the display of kernel threads",
|
||||
];
|
||||
|
||||
const SEARCH_HELP_TEXT: [&str; 51] = [
|
||||
const SEARCH_HELP_TEXT: [&str; 53] = [
|
||||
"4 - Process search widget",
|
||||
"Esc Close the search widget (retains the filter)",
|
||||
"Ctrl-a Skip to the start of the search query",
|
||||
@@ -118,6 +118,7 @@ const SEARCH_HELP_TEXT: [&str; 51] = [
|
||||
"",
|
||||
"Comparison operators:",
|
||||
"= ex: cpu = 1",
|
||||
"!= ex: cpu != 1",
|
||||
"> ex: cpu > 1",
|
||||
"< ex: cpu < 1",
|
||||
">= ex: cpu >= 1",
|
||||
@@ -126,6 +127,7 @@ const SEARCH_HELP_TEXT: [&str; 51] = [
|
||||
"Logical operators:",
|
||||
"and, &&, <Space> ex: btm and cpu > 1 and mem > 1",
|
||||
"or, || ex: btm or firefox",
|
||||
"! ex: !firefox, !(cpu > 5 or btm)",
|
||||
"",
|
||||
"Supported units:",
|
||||
"B ex: read > 1 b",
|
||||
|
||||
+5
-5
@@ -577,7 +577,7 @@ mod tests {
|
||||
state.move_left();
|
||||
assert_eq!(state.cursor_index(), 0);
|
||||
|
||||
// At the start — no further movement
|
||||
// At the start - no further movement
|
||||
state.move_left();
|
||||
assert_eq!(state.cursor_index(), 0);
|
||||
|
||||
@@ -588,7 +588,7 @@ mod tests {
|
||||
state.move_right();
|
||||
assert_eq!(state.cursor_index(), 3);
|
||||
|
||||
// At the end — no further movement
|
||||
// At the end - no further movement
|
||||
state.move_right();
|
||||
assert_eq!(state.cursor_index(), 3);
|
||||
}
|
||||
@@ -717,7 +717,7 @@ mod tests {
|
||||
/// producing the wrong result or panicking.
|
||||
#[test]
|
||||
fn delete_previous_word_unicode() {
|
||||
// "你好 world" — '你'=3 bytes, '好'=3 bytes, ' '=1, "world"=5, so 12 bytes
|
||||
// "你好 world" - '你'=3 bytes, '好'=3 bytes, ' '=1, "world"=5, so 12 bytes
|
||||
// total
|
||||
let mut state = InputFieldState::default();
|
||||
state.insert_string("你好 world".to_string());
|
||||
@@ -827,13 +827,13 @@ mod tests {
|
||||
assert_eq!(state.grapheme_cursor.cur_cursor(), 20);
|
||||
assert_eq!(state.display_start_index, 11);
|
||||
|
||||
// Clamped at the end — no further movement.
|
||||
// Clamped at the end - no further movement.
|
||||
state.move_right();
|
||||
state.get_start_position(4, false);
|
||||
assert_eq!(state.grapheme_cursor.cur_cursor(), 20);
|
||||
assert_eq!(state.display_start_index, 11);
|
||||
|
||||
// Moving left — back over the flag emoji.
|
||||
// Moving left - back over the flag emoji.
|
||||
state.move_left();
|
||||
state.get_start_position(4, false);
|
||||
assert_eq!(state.grapheme_cursor.cur_cursor(), 12);
|
||||
|
||||
@@ -22,8 +22,8 @@ use regex::Regex;
|
||||
|
||||
use crate::{collection::processes::ProcessHarvest, multi_eq_ignore_ascii_case};
|
||||
|
||||
const DELIMITER_LIST: [char; 6] = ['=', '>', '<', '(', ')', '\"'];
|
||||
const COMPARISON_LIST: [&str; 3] = [">", "=", "<"];
|
||||
const DELIMITER_LIST: [char; 7] = ['=', '>', '<', '!', '(', ')', '\"'];
|
||||
const COMPARISON_LIST: [&str; 4] = [">", "=", "<", "!="];
|
||||
|
||||
/// A node type that can take a query and read it, advancing the current read
|
||||
/// state and returning an instance of the node.
|
||||
@@ -136,6 +136,16 @@ pub(crate) fn parse_query(search_query: &str, options: &QueryOptions) -> QueryRe
|
||||
}
|
||||
});
|
||||
|
||||
// Merge adjacent "!" and "=" tokens into a single "!=" token.
|
||||
let mut i = 0;
|
||||
while i + 1 < split_query.len() {
|
||||
if split_query[i] == "!" && split_query[i + 1] == "=" {
|
||||
split_query[i] = "!=".to_owned();
|
||||
split_query.remove(i + 1);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
|
||||
process_string_to_filter(&mut split_query, options)
|
||||
}
|
||||
|
||||
@@ -187,6 +197,8 @@ impl std::str::FromStr for PrefixType {
|
||||
// TODO: Didn't add mem_bytes, total_read, and total_write
|
||||
// for now as it causes help to be clogged.
|
||||
|
||||
// TODO: Add a `name` keyword alias so something like `name = blah` is valid.
|
||||
|
||||
let mut result = Name;
|
||||
if multi_eq_ignore_ascii_case!(s, "cpu" | "cpu%") {
|
||||
result = CpuPercentage;
|
||||
@@ -235,6 +247,7 @@ impl std::str::FromStr for PrefixType {
|
||||
#[derive(Debug)]
|
||||
enum QueryComparison {
|
||||
Equal,
|
||||
NotEqual,
|
||||
Less,
|
||||
Greater,
|
||||
LessOrEqual,
|
||||
@@ -255,6 +268,7 @@ impl NumericalQuery {
|
||||
|
||||
match self.condition {
|
||||
QueryComparison::Equal => (lhs - rhs).abs() < f64::EPSILON,
|
||||
QueryComparison::NotEqual => (lhs - rhs).abs() >= f64::EPSILON,
|
||||
QueryComparison::Less => lhs < rhs,
|
||||
QueryComparison::Greater => lhs > rhs,
|
||||
QueryComparison::LessOrEqual => lhs <= rhs,
|
||||
@@ -276,6 +290,7 @@ impl TimeQuery {
|
||||
|
||||
match self.condition {
|
||||
QueryComparison::Equal => lhs == rhs,
|
||||
QueryComparison::NotEqual => lhs != rhs,
|
||||
QueryComparison::Less => lhs < rhs,
|
||||
QueryComparison::Greater => lhs > rhs,
|
||||
QueryComparison::LessOrEqual => lhs <= rhs,
|
||||
@@ -866,4 +881,253 @@ mod tests {
|
||||
|
||||
// assert!(mem.check(&process_a, false));
|
||||
// }
|
||||
|
||||
/// Basic numerical non-equality operator test.
|
||||
#[test]
|
||||
fn numerical_not_equal_query() {
|
||||
let query = parse_query_no_options("cpu != 50").unwrap();
|
||||
|
||||
let mut equal = simple_process("a");
|
||||
equal.cpu_usage_percent = 50.0;
|
||||
|
||||
let mut other = simple_process("a");
|
||||
other.cpu_usage_percent = 40.0;
|
||||
|
||||
assert!(!query.check(&equal, false));
|
||||
assert!(query.check(&other, false));
|
||||
}
|
||||
|
||||
/// `!=` should tokenize the same whether written as `foo!=bar` or
|
||||
/// `foo != bar`.
|
||||
#[test]
|
||||
fn not_equal_tokenizes_without_spaces() {
|
||||
let spaced = parse_query_no_options("cpu != 50").unwrap();
|
||||
let joined = parse_query_no_options("cpu!=50").unwrap();
|
||||
|
||||
let mut proc = simple_process("a");
|
||||
proc.cpu_usage_percent = 40.0;
|
||||
|
||||
assert!(spaced.check(&proc, false));
|
||||
assert!(joined.check(&proc, false));
|
||||
}
|
||||
|
||||
/// Test the non-equality operator with time.
|
||||
#[test]
|
||||
fn time_not_equal_query() {
|
||||
let query = parse_query_no_options("time != 1h").unwrap();
|
||||
|
||||
let mut equal = simple_process("a");
|
||||
equal.time = Duration::from_secs(3600);
|
||||
|
||||
let mut other = simple_process("a");
|
||||
other.time = Duration::from_secs(60);
|
||||
|
||||
assert!(!query.check(&equal, false));
|
||||
assert!(query.check(&other, false));
|
||||
}
|
||||
|
||||
/// Test state queries with the non-equality operator. This also tests that string comparisons.
|
||||
#[test]
|
||||
fn state_not_equal_query() {
|
||||
let query = parse_query_no_options("state != sleeping").unwrap();
|
||||
|
||||
let mut sleeping = simple_process("a");
|
||||
sleeping.process_state.0 = "sleeping";
|
||||
|
||||
let mut running = simple_process("a");
|
||||
running.process_state.0 = "running";
|
||||
|
||||
assert!(!query.check(&sleeping, false));
|
||||
assert!(query.check(&running, false));
|
||||
}
|
||||
|
||||
/// Test byte unit searches work with the non-equality operator.
|
||||
#[test]
|
||||
fn mem_bytes_not_equal_with_unit() {
|
||||
let query = parse_query_no_options("memb != 1 GiB").unwrap();
|
||||
|
||||
let mut equal = simple_process("a");
|
||||
equal.mem_usage = 1024 * 1024 * 1024;
|
||||
|
||||
let mut other = simple_process("a");
|
||||
other.mem_usage = 0;
|
||||
|
||||
assert!(!query.check(&equal, false));
|
||||
assert!(query.check(&other, false));
|
||||
}
|
||||
|
||||
/// `!(...)` negates a whole group.
|
||||
#[test]
|
||||
fn negate_group_query() {
|
||||
let query = parse_query_no_options("!(cpu > 5 or a)").unwrap();
|
||||
|
||||
let mut high_cpu = simple_process("b");
|
||||
high_cpu.cpu_usage_percent = 10.0;
|
||||
|
||||
let name_match = simple_process("a");
|
||||
|
||||
let mut low_cpu_no_match = simple_process("b");
|
||||
low_cpu_no_match.cpu_usage_percent = 1.0;
|
||||
|
||||
assert!(!query.check(&high_cpu, false));
|
||||
assert!(!query.check(&name_match, false));
|
||||
assert!(query.check(&low_cpu_no_match, false));
|
||||
}
|
||||
|
||||
/// `!name` negates a bare-name match.
|
||||
#[test]
|
||||
fn negate_bare_name_query() {
|
||||
let query = parse_query_no_options("!firefox").unwrap();
|
||||
|
||||
let firefox = simple_process("firefox");
|
||||
let other = simple_process("btm");
|
||||
|
||||
assert!(!query.check(&firefox, false));
|
||||
assert!(query.check(&other, false));
|
||||
}
|
||||
|
||||
/// `!"..."` negates a quoted-name match.
|
||||
#[test]
|
||||
fn negate_quoted_name_query() {
|
||||
let query = parse_query_no_options("!\"a or b\"").unwrap();
|
||||
|
||||
let exact = simple_process("a or b");
|
||||
let other = simple_process("c");
|
||||
|
||||
assert!(!query.check(&exact, false));
|
||||
assert!(query.check(&other, false));
|
||||
}
|
||||
|
||||
/// `!!x` is double negation, which should behave like `x`.
|
||||
#[test]
|
||||
fn double_negate_query_a() {
|
||||
let query = parse_query_no_options("!!firefox").unwrap();
|
||||
|
||||
let firefox = simple_process("firefox");
|
||||
let other = simple_process("btm");
|
||||
|
||||
assert!(query.check(&firefox, false));
|
||||
assert!(!query.check(&other, false));
|
||||
}
|
||||
|
||||
/// `!!(cpu > 5)` is double negation, which should behave like `cpu > 5`.
|
||||
#[test]
|
||||
fn double_negate_query_b() {
|
||||
let query = parse_query_no_options("!!(cpu > 5) ").unwrap();
|
||||
|
||||
let mut big = simple_process("big");
|
||||
big.cpu_usage_percent = 10.0;
|
||||
let mut small = simple_process("small");
|
||||
small.cpu_usage_percent = 1.0;
|
||||
|
||||
assert!(query.check(&big, false));
|
||||
assert!(!query.check(&small, false));
|
||||
}
|
||||
|
||||
/// `!` composes with `and`/`or`.
|
||||
#[test]
|
||||
fn negate_with_boolean_operators() {
|
||||
let query = parse_query_no_options("!firefox and cpu > 5").unwrap();
|
||||
|
||||
let mut firefox = simple_process("firefox");
|
||||
firefox.cpu_usage_percent = 10.0;
|
||||
|
||||
let mut other_high = simple_process("btm");
|
||||
other_high.cpu_usage_percent = 10.0;
|
||||
|
||||
let mut other_low = simple_process("btm");
|
||||
other_low.cpu_usage_percent = 1.0;
|
||||
|
||||
assert!(!query.check(&firefox, false));
|
||||
assert!(query.check(&other_high, false));
|
||||
assert!(!query.check(&other_low, false));
|
||||
}
|
||||
|
||||
/// A bare `!` with nothing parseable after it must be rejected so that it
|
||||
/// doesn't silently become `name = "!"`. The literal form `"!"` still
|
||||
/// works.
|
||||
#[test]
|
||||
fn lone_bang_is_rejected() {
|
||||
parse_query_no_options("!").unwrap_err();
|
||||
parse_query_no_options("! ").unwrap_err();
|
||||
parse_query_no_options("!)").unwrap_err();
|
||||
parse_query_no_options("(!)").unwrap_err();
|
||||
}
|
||||
|
||||
/// A quoted `"!"` as the RHS of a prefixed string query should match the
|
||||
/// literal `!`, not be treated as an operator.
|
||||
#[test]
|
||||
fn user_equals_quoted_bang() {
|
||||
let query = parse_query_no_options("user = \"!\"").unwrap();
|
||||
|
||||
let mut with_bang = simple_process("a");
|
||||
with_bang.user = Some("!".into());
|
||||
|
||||
let mut other = simple_process("a");
|
||||
other.user = Some("root".into());
|
||||
|
||||
assert!(query.check(&with_bang, false));
|
||||
assert!(!query.check(&other, false));
|
||||
}
|
||||
|
||||
/// A quoted `"!"` as the RHS of a prefixed string query with "!=" should not match the
|
||||
/// literal `!`, nor should it be treated as an operator.
|
||||
#[test]
|
||||
fn user_negated_equals_quoted_bang() {
|
||||
let query = parse_query_no_options("user != \"!\"").unwrap();
|
||||
|
||||
let mut with_bang = simple_process("a");
|
||||
with_bang.user = Some("!".into());
|
||||
|
||||
let mut other = simple_process("a");
|
||||
other.user = Some("root".into());
|
||||
|
||||
assert!(!query.check(&with_bang, false));
|
||||
assert!(query.check(&other, false));
|
||||
}
|
||||
|
||||
/// A quoted `"!"` should remain a valid literal-name match.
|
||||
#[test]
|
||||
fn quoted_bang_matches_literal() {
|
||||
let query = parse_query_no_options("\"!\"").unwrap();
|
||||
|
||||
let with_bang = simple_process("foo!");
|
||||
let without = simple_process("foo");
|
||||
|
||||
assert!(query.check(&with_bang, false));
|
||||
assert!(!query.check(&without, false));
|
||||
}
|
||||
|
||||
/// Trailing operators with no RHS must error.
|
||||
#[test]
|
||||
fn not_equal_missing_value_is_rejected() {
|
||||
parse_query_no_options("user !=").unwrap_err();
|
||||
parse_query_no_options("state !=").unwrap_err();
|
||||
parse_query_no_options("time !=").unwrap_err();
|
||||
}
|
||||
|
||||
/// Some miscellaneous invalid string searches involving negation (`!`) parsing.
|
||||
#[test]
|
||||
fn misc_invalid_bang_search() {
|
||||
parse_query_no_options("user = !").unwrap_err();
|
||||
parse_query_no_options("user != !").unwrap_err();
|
||||
parse_query_no_options("pid = !").unwrap_err();
|
||||
parse_query_no_options("state !").unwrap_err();
|
||||
parse_query_no_options("!cpu > 5").unwrap_err();
|
||||
parse_query_no_options("! cpu > 5").unwrap_err();
|
||||
}
|
||||
|
||||
/// `=!` is not a valid operator.
|
||||
#[test]
|
||||
fn reversed_bang_equal_is_rejected() {
|
||||
parse_query_no_options("cpu =!").unwrap_err();
|
||||
parse_query_no_options("cpu = !").unwrap_err();
|
||||
parse_query_no_options("cpu = ! 5").unwrap_err();
|
||||
}
|
||||
|
||||
/// `!=` with a non-numeric RHS should fail.
|
||||
#[test]
|
||||
fn not_equal_non_numeric_rhs_is_rejected() {
|
||||
parse_query_no_options("cpu != abc").unwrap_err();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,14 @@ fn process_prefix_units(query: &mut VecDeque<String>, value: &mut f64) {
|
||||
/// now, it's hardcoded for processes.
|
||||
#[derive(Debug)]
|
||||
pub(super) enum Prefix {
|
||||
/// True if the inner OR is true (allowing a recursive tree).
|
||||
Or(Box<Or>),
|
||||
/// A leaf node.
|
||||
Attribute(ProcessAttribute),
|
||||
/// Invert the match result of the inner prefix.
|
||||
///
|
||||
/// TODO: Also support reading with "not".
|
||||
Negate(Box<Prefix>),
|
||||
}
|
||||
|
||||
impl Prefix {
|
||||
@@ -67,6 +73,7 @@ impl Prefix {
|
||||
match self {
|
||||
Prefix::Or(or) => or.check(process, is_using_command),
|
||||
Prefix::Attribute(attribute) => attribute.check(process, is_using_command),
|
||||
Prefix::Negate(inner) => !inner.check(process, is_using_command),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +169,31 @@ impl QueryProcessor for Prefix {
|
||||
};
|
||||
} else if curr == ")" {
|
||||
return Err(QueryError::new("Missing opening parentheses"));
|
||||
} else if curr == "!" {
|
||||
// Negation prefix: `!<expr>` inverts the match of the following
|
||||
// expression. Handles:
|
||||
// - Groups (`!(a or b)`)
|
||||
// - Quoted names (`!"foo"`)
|
||||
// - Bare names (`!foo`)
|
||||
// - Stacked `!` (`!!foo`).
|
||||
match query.front().map(|s| s.as_str()) {
|
||||
None | Some("=") | Some(">") | Some("<") | Some(")") => {
|
||||
return Err(QueryError::new(
|
||||
"`!` must be followed by an expression; use `\"!\"` to match a literal `!`",
|
||||
));
|
||||
}
|
||||
Some(next) if next != "(" && next != "\"" && next != "!" => {
|
||||
let next: Result<PrefixType, QueryError> = next.parse();
|
||||
if !matches!(next, Ok(PrefixType::Name)) {
|
||||
return Err(QueryError::new(
|
||||
"`!` cannot be applied to a prefix keyword; use `!=`, `<=`, `>=` or group with `!(...)` instead",
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let inner = Prefix::process(query, options)?;
|
||||
return Ok(Prefix::Negate(Box::new(inner)));
|
||||
} else if curr == "\"" {
|
||||
// Similar to parentheses, trap and check for missing closing quotes. Note,
|
||||
// however, that we will DIRECTLY call another process_prefix
|
||||
@@ -198,10 +230,17 @@ impl QueryProcessor for Prefix {
|
||||
)?));
|
||||
}
|
||||
PrefixType::Pid | PrefixType::State | PrefixType::User => {
|
||||
// We have to check if someone put an "="...
|
||||
if content == "=" {
|
||||
// We have to check if someone put an (in)equality check...
|
||||
if content == "=" || content == "!=" {
|
||||
let negate = content.starts_with('!');
|
||||
|
||||
// Check next string if possible
|
||||
if let Some(string_value) = query.pop_front() {
|
||||
if string_value == "!" {
|
||||
return Err(QueryError::new(
|
||||
"`!` is reserved; use `\"!\"` to match the literal character",
|
||||
));
|
||||
}
|
||||
// TODO: [Query] Need to consider the following cases:
|
||||
// - (test)
|
||||
// - (test
|
||||
@@ -232,12 +271,22 @@ impl QueryProcessor for Prefix {
|
||||
string_value
|
||||
};
|
||||
|
||||
return Ok(Prefix::Attribute(new_string_attribute(
|
||||
let inner_attribute = Prefix::Attribute(new_string_attribute(
|
||||
prefix_type,
|
||||
&final_value,
|
||||
options,
|
||||
)?));
|
||||
)?);
|
||||
|
||||
return Ok(if negate {
|
||||
Prefix::Negate(Box::new(inner_attribute))
|
||||
} else {
|
||||
inner_attribute
|
||||
});
|
||||
}
|
||||
} else if content == "!" {
|
||||
return Err(QueryError::new(
|
||||
"`!` is reserved; use `\"!\"` to match the literal character",
|
||||
));
|
||||
} else {
|
||||
return Ok(Prefix::Attribute(new_string_attribute(
|
||||
prefix_type,
|
||||
@@ -253,6 +302,9 @@ impl QueryProcessor for Prefix {
|
||||
if content == "=" {
|
||||
condition = Some(QueryComparison::Equal);
|
||||
duration_string = query.pop_front();
|
||||
} else if content == "!=" {
|
||||
condition = Some(QueryComparison::NotEqual);
|
||||
duration_string = query.pop_front();
|
||||
} else if content == ">" || content == "<" {
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
if queue_next == "=" {
|
||||
@@ -291,8 +343,8 @@ impl QueryProcessor for Prefix {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Assume it's some numerical value.
|
||||
// Now we gotta parse the content... yay.
|
||||
// Assume it's some numerical value. Now we gotta parse the content... yay.
|
||||
// Note that for numerical parsing, we handle unit parsing later, not here.
|
||||
|
||||
let mut condition: Option<QueryComparison> = None;
|
||||
let mut value: Option<f64> = None;
|
||||
@@ -306,6 +358,13 @@ impl QueryProcessor for Prefix {
|
||||
} else {
|
||||
return Err(QueryError::missing_value());
|
||||
}
|
||||
} else if content == "!=" {
|
||||
condition = Some(QueryComparison::NotEqual);
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
value = queue_next.parse::<f64>().ok();
|
||||
} else {
|
||||
return Err(QueryError::missing_value());
|
||||
}
|
||||
} else if content == ">" || content == "<" {
|
||||
// We also have to check if the next string is an "="...
|
||||
if let Some(queue_next) = query.pop_front() {
|
||||
|
||||
Reference in New Issue
Block a user