From 5e874eab7771f25ab612bdc97b57966aea1a2e6e Mon Sep 17 00:00:00 2001 From: ClementTsang Date: Sat, 2 May 2020 18:36:06 -0400 Subject: [PATCH] refactor/bug: moved search logic; fix quoted words --- src/app/query.rs | 262 +++++++++++++++++++++++++++++++++++++++++++++- src/app/states.rs | 257 +-------------------------------------------- 2 files changed, 262 insertions(+), 257 deletions(-) diff --git a/src/app/query.rs b/src/app/query.rs index 16a78f84..a73e1ef5 100644 --- a/src/app/query.rs +++ b/src/app/query.rs @@ -1,7 +1,267 @@ +use super::ProcWidgetState; use crate::{ data_conversion::ConvertedProcessData, - utils::error::{BottomError, Result}, + utils::error::{ + BottomError::{self, QueryError}, + Result, + }, }; +use std::collections::VecDeque; + +const DELIMITER_LIST: [char; 5] = ['=', '>', '<', '(', ')']; + +/// I only separated this as otherwise, the states.rs file gets huge... and this should +/// belong in another file anyways, IMO. +pub trait ProcessQuery { + /// In charge of parsing the given query. + /// We are defining the following language for a query (case-insensitive prefixes): + /// + /// - Process names: No prefix required, can use regex, match word, or case. + /// Enclosing anything, including prefixes, in quotes, means we treat it as an entire process + /// rather than a prefix. + /// - PIDs: Use prefix `pid`, can use regex or match word (case is irrelevant). + /// - CPU: Use prefix `cpu`, cannot use r/m/c (regex, match word, case). Can compare. + /// - MEM: Use prefix `mem`, cannot use r/m/c. Can compare. + /// - STATE: Use prefix `state`, TODO when we update how state looks in 0.5 probably. + /// - Read/s: Use prefix `r`. Can compare. + /// - Write/s: Use prefix `w`. Can compare. + /// - Total read: Use prefix `read`. Can compare. + /// - Total write: Use prefix `write`. Can compare. + /// + /// For queries, whitespaces are our delimiters. We will merge together any adjacent non-prefixed + /// or quoted elements after splitting to treat as process names. + /// Furthermore, we want to support boolean joiners like AND and OR, and brackets. + fn parse_query(&self) -> Result; +} + +impl ProcessQuery for ProcWidgetState { + fn parse_query(&self) -> Result { + fn process_string_to_filter(query: &mut VecDeque) -> Result { + Ok(Query { + query: process_and(query)?, + }) + } + + fn process_and(query: &mut VecDeque) -> Result { + let mut lhs = process_or(query)?; + let mut rhs: Option> = None; + + while let Some(queue_top) = query.front() { + if queue_top.to_lowercase() == "and" { + query.pop_front(); + rhs = Some(Box::new(process_or(query)?)); + + if let Some(queue_next) = query.front() { + if queue_next.to_lowercase() == "and" { + // Must merge LHS and RHS + lhs = Or { + lhs: Prefix { + and: Some(Box::new(And { lhs, rhs })), + regex_prefix: None, + compare_prefix: None, + }, + rhs: None, + }; + rhs = None; + } + } else { + break; + } + } else { + break; + } + } + + Ok(And { lhs, rhs }) + } + + fn process_or(query: &mut VecDeque) -> Result { + let mut lhs = process_prefix(query)?; + let mut rhs: Option> = None; + + while let Some(queue_top) = query.front() { + if queue_top.to_lowercase() == "or" { + query.pop_front(); + rhs = Some(Box::new(process_prefix(query)?)); + + if let Some(queue_next) = query.front() { + if queue_next.to_lowercase() == "or" { + // Must merge LHS and RHS + lhs = Prefix { + and: Some(Box::new(And { + lhs: Or { lhs, rhs }, + rhs: None, + })), + regex_prefix: None, + compare_prefix: None, + }; + rhs = None; + } + } else { + break; + } + } else { + break; + } + } + + Ok(Or { lhs, rhs }) + } + + fn process_prefix(query: &mut VecDeque) -> Result { + if let Some(queue_top) = query.pop_front() { + if queue_top == "(" { + // Get content within bracket; and check if paren is complete + let and = process_and(query)?; + if let Some(close_paren) = query.pop_front() { + if close_paren.to_lowercase() == ")" { + return Ok(Prefix { + and: Some(Box::new(and)), + regex_prefix: None, + compare_prefix: None, + }); + } else { + return Err(QueryError("Missing closing parentheses".into())); + } + } else { + return Err(QueryError("Missing closing parentheses".into())); + } + } else if queue_top == ")" { + // This is actually caught by the regex creation, but it seems a bit + // sloppy to leave that up to that to do so... + + return Err(QueryError("Missing opening parentheses".into())); + } else { + // Get prefix type... + let prefix_type = queue_top.parse::()?; + let content = if let PrefixType::Name = prefix_type { + Some(queue_top) + } else { + query.pop_front() + }; + + if let Some(content) = content { + match &prefix_type { + PrefixType::Name => { + return Ok(Prefix { + and: None, + regex_prefix: Some(( + prefix_type, + StringQuery::Value(content.trim_matches('\"').to_owned()), + )), + compare_prefix: None, + }) + } + PrefixType::Pid => { + // We have to check if someone put an "="... + if content == "=" { + // Check next string if possible + if let Some(queue_next) = query.pop_front() { + return Ok(Prefix { + and: None, + regex_prefix: Some(( + prefix_type, + StringQuery::Value(queue_next), + )), + compare_prefix: None, + }); + } + } else { + return Ok(Prefix { + and: None, + regex_prefix: Some(( + prefix_type, + StringQuery::Value(content), + )), + compare_prefix: None, + }); + } + } + _ => { + // Now we gotta parse the content... yay. + + let mut condition: Option = None; + let mut value: Option = None; + + if content == "=" { + // TODO: Do we want to allow just an empty space to work here too? ie: cpu 5? + condition = Some(QueryComparison::Equal); + if let Some(queue_next) = query.pop_front() { + value = queue_next.parse::().ok(); + } + } else if content == ">" || content == "<" { + // We also have to check if the next string is an "="... + if let Some(queue_next) = query.pop_front() { + if queue_next == "=" { + condition = Some(if content == ">" { + QueryComparison::GreaterOrEqual + } else { + QueryComparison::LessOrEqual + }); + if let Some(queue_next_next) = query.pop_front() { + value = queue_next_next.parse::().ok(); + } + } else { + condition = Some(if content == ">" { + QueryComparison::Greater + } else { + QueryComparison::Less + }); + value = queue_next.parse::().ok(); + } + } + } + + if let Some(condition) = condition { + if let Some(value) = value { + return Ok(Prefix { + and: None, + regex_prefix: None, + compare_prefix: Some(( + prefix_type, + NumericalQuery { condition, value }, + )), + }); + } + } + } + } + } + } + } + + Err(QueryError("Failed to parse comparator.".into())) + } + + let mut split_query = VecDeque::new(); + + self.get_current_search_query() + .split_whitespace() + .for_each(|s| { + // From https://stackoverflow.com/a/56923739 in order to get a split but include the parentheses + let mut last = 0; + for (index, matched) in s.match_indices(|x| DELIMITER_LIST.contains(&x)) { + if last != index { + split_query.push_back(s[last..index].to_owned()); + } + split_query.push_back(matched.to_owned()); + last = index + matched.len(); + } + if last < s.len() { + split_query.push_back(s[last..].to_owned()); + } + }); + + let mut process_filter = process_string_to_filter(&mut split_query)?; + process_filter.process_regexes( + self.process_search_state.is_searching_whole_word, + self.process_search_state.is_ignoring_case, + self.process_search_state.is_searching_with_regex, + )?; + + Ok(process_filter) + } +} #[derive(Debug)] pub struct Query { diff --git a/src/app/states.rs b/src/app/states.rs index 825a08fa..3d45a572 100644 --- a/src/app/states.rs +++ b/src/app/states.rs @@ -1,7 +1,4 @@ -use std::{ - collections::{HashMap, VecDeque}, - time::Instant, -}; +use std::{collections::HashMap, time::Instant}; use unicode_segmentation::GraphemeCursor; @@ -11,7 +8,6 @@ use crate::{ app::{layout_manager::BottomWidgetType, query::*}, constants, data_harvester::processes, - utils::error::{BottomError::*, Result}, }; #[derive(Debug)] @@ -241,257 +237,6 @@ impl ProcWidgetState { ) .unwrap(); } - - /// The filtering function. Based on the results of the query. - pub fn matches_filter(&self) -> bool { - // The way this will have to work is that given a "query" structure, we have - // to filter based on it. - - false - } - - /// In charge of parsing the given query. - /// We are defining the following language for a query (case-insensitive prefixes): - /// - /// - Process names: No prefix required, can use regex, match word, or case. - /// Enclosing anything, including prefixes, in quotes, means we treat it as an entire process - /// rather than a prefix. - /// - PIDs: Use prefix `pid`, can use regex or match word (case is irrelevant). - /// - CPU: Use prefix `cpu`, cannot use r/m/c (regex, match word, case). Can compare. - /// - MEM: Use prefix `mem`, cannot use r/m/c. Can compare. - /// - STATE: Use prefix `state`, TODO when we update how state looks in 0.5 probably. - /// - Read/s: Use prefix `r`. Can compare. - /// - Write/s: Use prefix `w`. Can compare. - /// - Total read: Use prefix `read`. Can compare. - /// - Total write: Use prefix `write`. Can compare. - /// - /// For queries, whitespaces are our delimiters. We will merge together any adjacent non-prefixed - /// or quoted elements after splitting to treat as process names. - /// Furthermore, we want to support boolean joiners like AND and OR, and brackets. - fn parse_query(&self) -> Result { - fn process_string_to_filter(query: &mut VecDeque) -> Result { - Ok(Query { - query: process_and(query)?, - }) - } - - fn process_and(query: &mut VecDeque) -> Result { - let mut lhs = process_or(query)?; - let mut rhs: Option> = None; - - while let Some(queue_top) = query.front() { - if queue_top.to_lowercase() == "and" { - query.pop_front(); - rhs = Some(Box::new(process_or(query)?)); - - if let Some(queue_next) = query.front() { - if queue_next.to_lowercase() == "and" { - // Must merge LHS and RHS - lhs = Or { - lhs: Prefix { - and: Some(Box::new(And { lhs, rhs })), - regex_prefix: None, - compare_prefix: None, - }, - rhs: None, - }; - rhs = None; - } - } else { - break; - } - } else { - break; - } - } - - Ok(And { lhs, rhs }) - } - - fn process_or(query: &mut VecDeque) -> Result { - let mut lhs = process_prefix(query)?; - let mut rhs: Option> = None; - - while let Some(queue_top) = query.front() { - if queue_top.to_lowercase() == "or" { - query.pop_front(); - rhs = Some(Box::new(process_prefix(query)?)); - - if let Some(queue_next) = query.front() { - if queue_next.to_lowercase() == "or" { - // Must merge LHS and RHS - lhs = Prefix { - and: Some(Box::new(And { - lhs: Or { lhs, rhs }, - rhs: None, - })), - regex_prefix: None, - compare_prefix: None, - }; - rhs = None; - } - } else { - break; - } - } else { - break; - } - } - - Ok(Or { lhs, rhs }) - } - - fn process_prefix(query: &mut VecDeque) -> Result { - if let Some(queue_top) = query.pop_front() { - if queue_top == "(" { - // Get content within bracket; and check if paren is complete - let and = process_and(query)?; - if let Some(close_paren) = query.pop_front() { - if close_paren.to_lowercase() == ")" { - return Ok(Prefix { - and: Some(Box::new(and)), - regex_prefix: None, - compare_prefix: None, - }); - } else { - return Err(QueryError("Missing closing parentheses".into())); - } - } else { - return Err(QueryError("Missing closing parentheses".into())); - } - } else if queue_top == ")" { - // This is actually caught by the regex creation, but it seems a bit - // sloppy to leave that up to that to do so... - - return Err(QueryError("Missing opening parentheses".into())); - } else { - // Get prefix type... - let prefix_type = queue_top.parse::()?; - let content = if let PrefixType::Name = prefix_type { - Some(queue_top) - } else { - query.pop_front() - }; - - if let Some(content) = content { - match &prefix_type { - PrefixType::Name => { - return Ok(Prefix { - and: None, - regex_prefix: Some((prefix_type, StringQuery::Value(content))), - compare_prefix: None, - }) - } - PrefixType::Pid => { - // We have to check if someone put an "="... - if content == "=" { - // Check next string if possible - if let Some(queue_next) = query.pop_front() { - return Ok(Prefix { - and: None, - regex_prefix: Some(( - prefix_type, - StringQuery::Value(queue_next), - )), - compare_prefix: None, - }); - } - } else { - return Ok(Prefix { - and: None, - regex_prefix: Some(( - prefix_type, - StringQuery::Value(content), - )), - compare_prefix: None, - }); - } - } - _ => { - // Now we gotta parse the content... yay. - - let mut condition: Option = None; - let mut value: Option = None; - - if content == "=" { - // TODO: Do we want to allow just an empty space to work here too? ie: cpu 5? - condition = Some(QueryComparison::Equal); - if let Some(queue_next) = query.pop_front() { - value = queue_next.parse::().ok(); - } - } else if content == ">" || content == "<" { - // We also have to check if the next string is an "="... - if let Some(queue_next) = query.pop_front() { - if queue_next == "=" { - condition = Some(if content == ">" { - QueryComparison::GreaterOrEqual - } else { - QueryComparison::LessOrEqual - }); - if let Some(queue_next_next) = query.pop_front() { - value = queue_next_next.parse::().ok(); - } - } else { - condition = Some(if content == ">" { - QueryComparison::Greater - } else { - QueryComparison::Less - }); - value = queue_next.parse::().ok(); - } - } - } - - if let Some(condition) = condition { - if let Some(value) = value { - return Ok(Prefix { - and: None, - regex_prefix: None, - compare_prefix: Some(( - prefix_type, - NumericalQuery { condition, value }, - )), - }); - } - } - } - } - } - } - } - - Err(QueryError("Failed to parse comparator.".into())) - } - - let mut split_query = VecDeque::new(); - - self.get_current_search_query() - .split_whitespace() - .for_each(|s| { - // From https://stackoverflow.com/a/56923739 in order to get a split but include the parentheses - let mut last = 0; - for (index, matched) in s.match_indices(|x| ['=', '>', '<', '(', ')'].contains(&x)) - { - if last != index { - split_query.push_back(s[last..index].to_owned()); - } - split_query.push_back(matched.to_owned()); - last = index + matched.len(); - } - if last < s.len() { - split_query.push_back(s[last..].to_owned()); - } - }); - - let mut process_filter = process_string_to_filter(&mut split_query)?; - process_filter.process_regexes( - self.process_search_state.is_searching_whole_word, - self.process_search_state.is_ignoring_case, - self.process_search_state.is_searching_with_regex, - )?; - - Ok(process_filter) - } } pub struct ProcState {