use std::fmt::Debug;
use std::time::Duration;
use std::{borrow::Cow, collections::VecDeque};
use humantime::parse_duration;
use super::data_harvester::processes::ProcessHarvest;
use crate::utils::error::{
BottomError::{self, QueryError},
Result,
};
const DELIMITER_LIST: [char; 6] = ['=', '>', '<', '(', ')', '\"'];
const COMPARISON_LIST: [&str; 3] = [">", "=", "<"];
const OR_LIST: [&str; 2] = ["or", "||"];
const AND_LIST: [&str; 2] = ["and", "&&"];
/// 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`, can use regex, match word, or case.
/// - USER: Use prefix `user`, can use regex, match word, or case.
/// - 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.
pub fn parse_query(
search_query: &str, is_searching_whole_word: bool, is_ignoring_case: bool,
is_searching_with_regex: bool,
) -> Result<Query> {
fn process_string_to_filter(query: &mut VecDeque<String>) -> Result<Query> {
let lhs = process_or(query)?;
let mut list_of_ors = vec![lhs];
while query.front().is_some() {
list_of_ors.push(process_or(query)?);
}
Ok(Query { query: list_of_ors })
}
fn process_or(query: &mut VecDeque<String>) -> Result<Or> {
let mut lhs = process_and(query)?;
let mut rhs: Option<Box<And>> = None;
while let Some(queue_top) = query.front() {
// debug!("OR QT: {:?}", queue_top);
if OR_LIST.contains(&queue_top.to_lowercase().as_str()) {
query.pop_front();
rhs = Some(Box::new(process_and(query)?));
if let Some(queue_next) = query.front() {
if OR_LIST.contains(&queue_next.to_lowercase().as_str()) {
// Must merge LHS and RHS
lhs = And {
lhs: Prefix {
or: Some(Box::new(Or { lhs, rhs })),
regex_prefix: None,
compare_prefix: None,
},
rhs: None,
};
rhs = None;
}
} else {
break;
}
} else if COMPARISON_LIST.contains(&queue_top.to_lowercase().as_str()) {
return Err(QueryError(Cow::Borrowed("Comparison not valid here")));
} else {
break;
}
}
Ok(Or { lhs, rhs })
}
fn process_and(query: &mut VecDeque<String>) -> Result<And> {
let mut lhs = process_prefix(query, false)?;
let mut rhs: Option<Box<Prefix>> = None;
while let Some(queue_top) = query.front() {
// debug!("AND QT: {:?}", queue_top);
if AND_LIST.contains(&queue_top.to_lowercase().as_str()) {
query.pop_front();
rhs = Some(Box::new(process_prefix(query, false)?));
if let Some(next_queue_top) = query.front() {
if AND_LIST.contains(&next_queue_top.to_lowercase().as_str()) {
// Must merge LHS and RHS
lhs = Prefix {
or: Some(Box::new(Or {
lhs: And { lhs, rhs },
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(Cow::Borrowed("Comparison not valid here")));
} else {
break;
}
}
Ok(And { lhs, rhs })
}
fn process_prefix(query: &mut VecDeque<String>, inside_quotation: bool) -> Result<Prefix> {
if let Some(queue_top) = query.pop_front() {
if inside_quotation {
if queue_top == "\"" {
// This means we hit something like "". Return an empty prefix, and to deal with
// the close quote checker, add one to the top of the stack. Ugly fix but whatever.
query.push_front("\"".to_string());
return