bug: fix query not working for or, refactor a bit

This commit is contained in:
ClementTsang 2020-05-21 21:40:40 -04:00
parent 948c1206e9
commit b33ea11af2
3 changed files with 310 additions and 264 deletions

View File

@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed a bug where bottom would incorrectly read the wrong values to calculate the read/write columns for processes in Linux. - Fixed a bug where bottom would incorrectly read the wrong values to calculate the read/write columns for processes in Linux.
- Fixed a bug where OR operations in the process query wouldn't work for process names.
## [0.4.3] - 2020-05-15 ## [0.4.3] - 2020-05-15
### Other ### Other

View File

@ -7,8 +7,10 @@ use crate::{
}, },
}; };
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt::Debug;
const DELIMITER_LIST: [char; 6] = ['=', '>', '<', '(', ')', '\"']; const DELIMITER_LIST: [char; 6] = ['=', '>', '<', '(', ')', '\"'];
const COMPARISON_LIST: [&str; 3] = [">", "=", "<"];
const OR_LIST: [&str; 2] = ["or", "||"]; const OR_LIST: [&str; 2] = ["or", "||"];
const AND_LIST: [&str; 2] = ["and", "&&"]; const AND_LIST: [&str; 2] = ["and", "&&"];
@ -40,36 +42,13 @@ impl ProcessQuery for ProcWidgetState {
fn parse_query(&self) -> Result<Query> { fn parse_query(&self) -> Result<Query> {
fn process_string_to_filter(query: &mut VecDeque<String>) -> Result<Query> { fn process_string_to_filter(query: &mut VecDeque<String>) -> Result<Query> {
let lhs = process_or(query)?; let lhs = process_or(query)?;
let mut and_query = And { let mut list_of_ors = vec![lhs];
lhs: Prefix {
or: Some(Box::from(lhs)),
compare_prefix: None,
regex_prefix: None,
},
rhs: None,
};
while query.front().is_some() { while query.front().is_some() {
let rhs = process_or(query)?; list_of_ors.push(process_or(query)?);
and_query = And {
lhs: Prefix {
or: Some(Box::from(Or {
lhs: and_query,
rhs: None,
})),
compare_prefix: None,
regex_prefix: None,
},
rhs: Some(Box::from(Prefix {
or: Some(Box::from(rhs)),
compare_prefix: None,
regex_prefix: None,
})),
}
} }
Ok(Query { query: and_query }) Ok(Query { query: list_of_ors })
} }
fn process_or(query: &mut VecDeque<String>) -> Result<Or> { fn process_or(query: &mut VecDeque<String>) -> Result<Or> {
@ -77,7 +56,7 @@ impl ProcessQuery for ProcWidgetState {
let mut rhs: Option<Box<And>> = None; let mut rhs: Option<Box<And>> = None;
while let Some(queue_top) = query.front() { while let Some(queue_top) = query.front() {
debug!("OR QT: {:?}", queue_top); // debug!("OR QT: {:?}", queue_top);
if OR_LIST.contains(&queue_top.to_lowercase().as_str()) { if OR_LIST.contains(&queue_top.to_lowercase().as_str()) {
query.pop_front(); query.pop_front();
rhs = Some(Box::new(process_and(query)?)); rhs = Some(Box::new(process_and(query)?));
@ -98,6 +77,8 @@ impl ProcessQuery for ProcWidgetState {
} else { } else {
break; break;
} }
} else if COMPARISON_LIST.contains(&queue_top.to_lowercase().as_str()) {
return Err(QueryError("Comparison not valid here".into()));
} else { } else {
break; break;
} }
@ -111,25 +92,32 @@ impl ProcessQuery for ProcWidgetState {
let mut rhs: Option<Box<Prefix>> = None; let mut rhs: Option<Box<Prefix>> = None;
while let Some(queue_top) = query.front() { while let Some(queue_top) = query.front() {
debug!("AND QT: {:?}", queue_top); // debug!("AND QT: {:?}", queue_top);
if queue_top == ")" { if AND_LIST.contains(&queue_top.to_lowercase().as_str()) {
break;
} else if AND_LIST.contains(&queue_top.to_lowercase().as_str()) {
query.pop_front(); query.pop_front();
}
rhs = Some(Box::new(process_prefix(query, false)?));
if query.front().is_some() { rhs = Some(Box::new(process_prefix(query, false)?));
// Must merge LHS and RHS
lhs = Prefix { if let Some(next_queue_top) = query.front() {
or: Some(Box::new(Or { if AND_LIST.contains(&next_queue_top.to_lowercase().as_str()) {
lhs: And { lhs, rhs }, // Must merge LHS and RHS
rhs: None, lhs = Prefix {
})), or: Some(Box::new(Or {
regex_prefix: None, lhs: And { lhs, rhs },
compare_prefix: None, rhs: None,
}; })),
rhs = None; regex_prefix: None,
compare_prefix: None,
};
rhs = None;
} else {
break;
}
} else {
break;
}
} else if COMPARISON_LIST.contains(&queue_top.to_lowercase().as_str()) {
return Err(QueryError("Comparison not valid here".into()));
} else { } else {
break; break;
} }
@ -138,116 +126,135 @@ impl ProcessQuery for ProcWidgetState {
Ok(And { lhs, rhs }) Ok(And { lhs, rhs })
} }
fn process_prefix(query: &mut VecDeque<String>, inside_quotations: bool) -> Result<Prefix> { fn process_prefix(query: &mut VecDeque<String>, inside_quotation: bool) -> Result<Prefix> {
if let Some(queue_top) = query.pop_front() { if let Some(queue_top) = query.pop_front() {
debug!("Prefix QT: {:?}", queue_top); // debug!("Prefix QT: {:?}", queue_top);
if !inside_quotations && queue_top == "(" {
if query.front().is_none() {
return Err(QueryError("Missing closing parentheses".into()));
}
// Get content within bracket; and check if paren is complete if inside_quotation {
let or = process_or(query)?; if queue_top == "\"" {
if let Some(close_paren) = query.pop_front() { // This means we hit something like "". Return an empty prefix, and to deal with
if close_paren.to_lowercase() == ")" { // the close quote checker, add one to the top of the stack. Ugly fix but whatever.
return Ok(Prefix { query.push_front("\"".to_string());
or: Some(Box::new(or)), return Ok(Prefix {
regex_prefix: None, or: None,
compare_prefix: None, regex_prefix: Some((
}); PrefixType::Name,
StringQuery::Value(String::default()),
)),
compare_prefix: None,
});
} else {
let mut quoted_string = queue_top;
while let Some(next_str) = query.front() {
if next_str == "\"" {
// Stop!
break;
} else {
quoted_string.push_str(next_str);
query.pop_front();
}
}
return Ok(Prefix {
or: None,
regex_prefix: Some((
PrefixType::Name,
StringQuery::Value(quoted_string),
)),
compare_prefix: None,
});
}
} else {
if queue_top == "(" {
if query.is_empty() {
return Err(QueryError("Missing closing parentheses".into()));
}
let mut list_of_ors = VecDeque::new();
while let Some(in_paren_query_top) = query.front() {
if in_paren_query_top != ")" {
list_of_ors.push_back(process_or(query)?);
} else {
break;
}
}
// Ensure not empty
if list_of_ors.is_empty() {
return Err(QueryError("No values within parentheses group".into()));
}
// Now convert this back to a OR...
let mut returned_or = Or {
lhs: And {
lhs: Prefix {
or: Some(Box::new(list_of_ors.pop_front().unwrap())),
compare_prefix: None,
regex_prefix: None,
},
rhs: None,
},
rhs: None,
};
list_of_ors.into_iter().for_each(|rhs| {
returned_or = Or {
lhs: And {
lhs: Prefix {
or: Some(Box::new(returned_or.clone())),
compare_prefix: None,
regex_prefix: None,
},
rhs: Some(Box::new(Prefix {
or: Some(Box::new(rhs)),
compare_prefix: None,
regex_prefix: None,
})),
},
rhs: None,
};
});
if let Some(close_paren) = query.pop_front() {
if close_paren == ")" {
return Ok(Prefix {
or: Some(Box::new(returned_or)),
regex_prefix: None,
compare_prefix: None,
});
} else {
return Err(QueryError("Missing closing parentheses".into()));
}
} else { } else {
return Err(QueryError("Missing closing parentheses".into())); return Err(QueryError("Missing closing parentheses".into()));
} }
} else { } else if queue_top == ")" {
return Err(QueryError("Missing closing parentheses".into())); return Err(QueryError("Missing opening parentheses".into()));
} } else if queue_top == "\"" {
} else if !inside_quotations && queue_top == ")" { // Similar to parentheses, trap and check for missing closing quotes. Note, however, that we
// This is actually caught by the regex creation, but it seems a bit // will DIRECTLY call another process_prefix call...
// sloppy to leave that up to that to do so...
return Err(QueryError("Missing opening parentheses".into())); let prefix = process_prefix(query, true)?;
} else if !inside_quotations && queue_top == "\"" { if let Some(close_paren) = query.pop_front() {
// Similar to parentheses, trap and check for missing closing quotes. Note, however, that we if close_paren == "\"" {
// will DIRECTLY call another process_prefix call... return Ok(prefix);
} else {
let prefix = process_prefix(query, true)?; return Err(QueryError("Missing closing quotation".into()));
if let Some(close_paren) = query.pop_front() { }
if close_paren.to_lowercase() == "\"" {
return Ok(prefix);
} else { } else {
return Err(QueryError("Missing closing quotation".into())); return Err(QueryError("Missing closing quotation".into()));
} }
} else { } else {
return Err(QueryError("Missing closing quotation".into())); // Get prefix type...
} let prefix_type = queue_top.parse::<PrefixType>()?;
} else if inside_quotations && queue_top == "\"" { let content = if let PrefixType::Name = prefix_type {
// This means we hit something like "". Return an empty prefix, and to deal with Some(queue_top)
// the close quote checker, add one to the top of the stack. Ugly fix but whatever. } else {
query.push_front("\"".to_string()); query.pop_front()
return Ok(Prefix { };
or: None,
regex_prefix: Some((
PrefixType::Name,
StringQuery::Value(String::default()),
)),
compare_prefix: None,
});
} else {
// Get prefix type...
let prefix_type = queue_top.parse::<PrefixType>()?;
let content = if let PrefixType::Name = prefix_type {
Some(queue_top)
} else {
query.pop_front()
};
if let Some(content) = content { if let Some(content) = content {
match &prefix_type { match &prefix_type {
PrefixType::Name if !inside_quotations => { PrefixType::Name => {
return Ok(Prefix {
or: None,
regex_prefix: Some((prefix_type, StringQuery::Value(content))),
compare_prefix: None,
})
}
PrefixType::Name if inside_quotations => {
// If *this* is the case, then we must peek until we see a closing quote and add it all together...
let mut final_content = content;
while let Some(next_str) = query.front() {
if next_str == "\"" {
// Stop!
break;
} else {
final_content.push_str(next_str);
query.pop_front();
}
}
return Ok(Prefix {
or: None,
regex_prefix: Some((
prefix_type,
StringQuery::Value(final_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 {
or: None,
regex_prefix: Some((
prefix_type,
StringQuery::Value(queue_next),
)),
compare_prefix: None,
});
}
} else {
return Ok(Prefix { return Ok(Prefix {
or: None, or: None,
regex_prefix: Some(( regex_prefix: Some((
@ -255,128 +262,157 @@ impl ProcessQuery for ProcWidgetState {
StringQuery::Value(content), StringQuery::Value(content),
)), )),
compare_prefix: None, compare_prefix: None,
}); })
} }
} PrefixType::Pid => {
_ => { // We have to check if someone put an "="...
// Now we gotta parse the content... yay. if content == "=" {
// Check next string if possible
let mut condition: Option<QueryComparison> = None; if let Some(queue_next) = query.pop_front() {
let mut value: Option<f64> = None; return Ok(Prefix {
or: None,
if content == "=" { regex_prefix: Some((
condition = Some(QueryComparison::Equal); prefix_type,
if let Some(queue_next) = query.pop_front() { StringQuery::Value(queue_next),
value = queue_next.parse::<f64>().ok(); )),
} else { compare_prefix: None,
return Err(QueryError("Missing value".into()));
}
} 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::<f64>().ok();
} else {
return Err(QueryError("Missing value".into()));
}
} else {
condition = Some(if content == ">" {
QueryComparison::Greater
} else {
QueryComparison::Less
});
value = queue_next.parse::<f64>().ok();
} }
} else { } else {
return Err(QueryError("Missing value".into()));
}
}
if let Some(condition) = condition {
if let Some(read_value) = value {
// Now we want to check one last thing - is there a unit?
// If no unit, assume base.
// Furthermore, base must be PEEKED at initially, and will
// require (likely) prefix_type specific checks
// Lastly, if it *is* a unit, remember to POP!
let mut value = read_value;
match prefix_type {
PrefixType::Rps
| PrefixType::Wps
| PrefixType::TRead
| PrefixType::TWrite => {
if let Some(potential_unit) = query.front() {
match potential_unit.to_lowercase().as_str() {
"tb" => {
value *= 1_000_000_000_000.0;
query.pop_front();
}
"tib" => {
value *= 1_099_511_627_776.0;
query.pop_front();
}
"gb" => {
value *= 1_000_000_000.0;
query.pop_front();
}
"gib" => {
value *= 1_073_741_824.0;
query.pop_front();
}
"mb" => {
value *= 1_000_000.0;
query.pop_front();
}
"mib" => {
value *= 1_048_576.0;
query.pop_front();
}
"kb" => {
value *= 1000.0;
query.pop_front();
}
"kib" => {
value *= 1024.0;
query.pop_front();
}
"b" => {
// Just gotta pop.
query.pop_front();
}
_ => {}
}
}
}
_ => {}
}
return Ok(Prefix { return Ok(Prefix {
or: None, or: None,
regex_prefix: None, regex_prefix: Some((
compare_prefix: Some((
prefix_type, prefix_type,
NumericalQuery { condition, value }, StringQuery::Value(content),
)), )),
compare_prefix: None,
}); });
} }
} }
_ => {
// Now we gotta parse the content... yay.
let mut condition: Option<QueryComparison> = None;
let mut value: Option<f64> = None;
if content == "=" {
condition = Some(QueryComparison::Equal);
if let Some(queue_next) = query.pop_front() {
value = queue_next.parse::<f64>().ok();
} else {
return Err(QueryError("Missing value".into()));
}
} 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::<f64>().ok();
} else {
return Err(QueryError("Missing value".into()));
}
} else {
condition = Some(if content == ">" {
QueryComparison::Greater
} else {
QueryComparison::Less
});
value = queue_next.parse::<f64>().ok();
}
} else {
return Err(QueryError("Missing value".into()));
}
}
if let Some(condition) = condition {
if let Some(read_value) = value {
// Now we want to check one last thing - is there a unit?
// If no unit, assume base.
// Furthermore, base must be PEEKED at initially, and will
// require (likely) prefix_type specific checks
// Lastly, if it *is* a unit, remember to POP!
let mut value = read_value;
match prefix_type {
PrefixType::Rps
| PrefixType::Wps
| PrefixType::TRead
| PrefixType::TWrite => {
if let Some(potential_unit) = query.front() {
match potential_unit.to_lowercase().as_str()
{
"tb" => {
value *= 1_000_000_000_000.0;
query.pop_front();
}
"tib" => {
value *= 1_099_511_627_776.0;
query.pop_front();
}
"gb" => {
value *= 1_000_000_000.0;
query.pop_front();
}
"gib" => {
value *= 1_073_741_824.0;
query.pop_front();
}
"mb" => {
value *= 1_000_000.0;
query.pop_front();
}
"mib" => {
value *= 1_048_576.0;
query.pop_front();
}
"kb" => {
value *= 1000.0;
query.pop_front();
}
"kib" => {
value *= 1024.0;
query.pop_front();
}
"b" => {
// Just gotta pop.
query.pop_front();
}
_ => {}
}
}
}
_ => {}
}
return Ok(Prefix {
or: None,
regex_prefix: None,
compare_prefix: Some((
prefix_type,
NumericalQuery { condition, value },
)),
});
}
}
}
} }
} else {
return Err(QueryError("Missing argument for search prefix".into()));
} }
} else {
return Err(QueryError("Missing argument for search prefix".into()));
} }
} }
} else if inside_quotation {
// Uh oh, it's empty with quotes!
return Err(QueryError("Missing closing quotation".into()));
} }
Err(QueryError("Invalid search".into())) Err(QueryError("Invalid query".into()))
} }
let mut split_query = VecDeque::new(); let mut split_query = VecDeque::new();
@ -411,7 +447,7 @@ impl ProcessQuery for ProcWidgetState {
pub struct Query { pub struct Query {
/// Remember, AND > OR, but AND must come after OR when we parse. /// Remember, AND > OR, but AND must come after OR when we parse.
pub query: And, pub query: Vec<Or>,
} }
impl Query { impl Query {
@ -419,24 +455,29 @@ impl Query {
&mut self, is_searching_whole_word: bool, is_ignoring_case: bool, &mut self, is_searching_whole_word: bool, is_ignoring_case: bool,
is_searching_with_regex: bool, is_searching_with_regex: bool,
) -> Result<()> { ) -> Result<()> {
self.query.process_regexes( for or in &mut self.query {
is_searching_whole_word, or.process_regexes(
is_ignoring_case, is_searching_whole_word,
is_searching_with_regex, is_ignoring_case,
) is_searching_with_regex,
)?;
}
Ok(())
} }
pub fn check(&self, process: &ConvertedProcessData) -> bool { pub fn check(&self, process: &ConvertedProcessData) -> bool {
self.query.check(process) self.query.iter().all(|ok| ok.check(process))
} }
} }
impl std::fmt::Debug for Query { impl Debug for Query {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{:?}", self.query)) f.write_fmt(format_args!("{:?}", self.query))
} }
} }
#[derive(Default, Clone)]
pub struct Or { pub struct Or {
pub lhs: And, pub lhs: And,
pub rhs: Option<Box<And>>, pub rhs: Option<Box<And>>,
@ -472,7 +513,7 @@ impl Or {
} }
} }
impl std::fmt::Debug for Or { impl Debug for Or {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.rhs { match &self.rhs {
Some(rhs) => f.write_fmt(format_args!("({:?} OR {:?})", self.lhs, rhs)), Some(rhs) => f.write_fmt(format_args!("({:?} OR {:?})", self.lhs, rhs)),
@ -481,6 +522,7 @@ impl std::fmt::Debug for Or {
} }
} }
#[derive(Default, Clone)]
pub struct And { pub struct And {
pub lhs: Prefix, pub lhs: Prefix,
pub rhs: Option<Box<Prefix>>, pub rhs: Option<Box<Prefix>>,
@ -516,7 +558,7 @@ impl And {
} }
} }
impl std::fmt::Debug for And { impl Debug for And {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.rhs { match &self.rhs {
Some(rhs) => f.write_fmt(format_args!("({:?} AND {:?})", self.lhs, rhs)), Some(rhs) => f.write_fmt(format_args!("({:?} AND {:?})", self.lhs, rhs)),
@ -525,7 +567,7 @@ impl std::fmt::Debug for And {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum PrefixType { pub enum PrefixType {
Pid, Pid,
Cpu, Cpu,
@ -558,6 +600,7 @@ impl std::str::FromStr for PrefixType {
} }
} }
#[derive(Default, Clone)]
pub struct Prefix { pub struct Prefix {
pub or: Option<Box<Or>>, pub or: Option<Box<Or>>,
pub regex_prefix: Option<(PrefixType, StringQuery)>, pub regex_prefix: Option<(PrefixType, StringQuery)>,
@ -667,12 +710,13 @@ impl Prefix {
_ => true, _ => true,
} }
} else { } else {
// Somehow we have an empty condition... oh well. Return true.
true true
} }
} }
} }
impl std::fmt::Debug for Prefix { impl Debug for Prefix {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(or) = &self.or { if let Some(or) = &self.or {
f.write_fmt(format_args!("{:?}", or)) f.write_fmt(format_args!("{:?}", or))
@ -686,7 +730,7 @@ impl std::fmt::Debug for Prefix {
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum QueryComparison { pub enum QueryComparison {
Equal, Equal,
Less, Less,
@ -695,13 +739,13 @@ pub enum QueryComparison {
GreaterOrEqual, GreaterOrEqual,
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum StringQuery { pub enum StringQuery {
Value(String), Value(String),
Regex(regex::Regex), Regex(regex::Regex),
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct NumericalQuery { pub struct NumericalQuery {
pub condition: QueryComparison, pub condition: QueryComparison,
pub value: f64, pub value: f64,

View File

@ -205,7 +205,7 @@ impl ProcWidgetState {
self.process_search_state.search_state.error_message = None; self.process_search_state.search_state.error_message = None;
} else { } else {
let parsed_query = self.parse_query(); let parsed_query = self.parse_query();
debug!("Parsed query: {:?}", parsed_query); // debug!("Parsed query: {:#?}", parsed_query);
if let Ok(parsed_query) = parsed_query { if let Ok(parsed_query) = parsed_query {
self.process_search_state.search_state.query = Some(parsed_query); self.process_search_state.search_state.query = Some(parsed_query);