#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct ParsedQuery {
pub text: String,
pub languages: Vec<String>,
pub paths: Vec<String>,
pub names: Vec<String>,
}
impl ParsedQuery {
#[allow(dead_code)]
pub fn has_filters(&self) -> bool {
!self.languages.is_empty() || !self.paths.is_empty() || !self.names.is_empty()
}
}
pub fn parse_query(raw: &str) -> ParsedQuery {
let mut pq = ParsedQuery::default();
let mut text_tokens: Vec<String> = Vec::new();
for tok in tokenize(raw) {
match split_field(&tok) {
Some((field, value)) if !value.is_empty() => {
let v = value.to_ascii_lowercase();
match field.to_ascii_lowercase().as_str() {
"lang" | "language" => pq.languages.push(v),
"path" => pq.paths.push(v),
"name" => pq.names.push(v),
_ => text_tokens.push(tok),
}
}
_ => text_tokens.push(tok),
}
}
pq.text = text_tokens.join(" ");
pq
}
fn tokenize(raw: &str) -> Vec<String> {
let mut out = Vec::new();
let mut cur = String::new();
let mut in_quote = false;
for c in raw.chars() {
match c {
'"' => in_quote = !in_quote,
c if c.is_whitespace() && !in_quote => {
if !cur.is_empty() {
out.push(std::mem::take(&mut cur));
}
}
c => cur.push(c),
}
}
if !cur.is_empty() {
out.push(cur);
}
out
}
fn split_field(tok: &str) -> Option<(&str, &str)> {
let idx = tok.find(':')?;
if idx == 0 {
return None;
}
Some((&tok[..idx], &tok[idx + 1..]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_query_has_no_filters() {
let pq = parse_query("how does login work");
assert_eq!(pq.text, "how does login work");
assert!(!pq.has_filters());
}
#[test]
fn extracts_all_three_fields() {
let pq = parse_query("lang:Rust path:src/auth name:Login refresh the token");
assert_eq!(pq.languages, vec!["rust"]);
assert_eq!(pq.paths, vec!["src/auth"]);
assert_eq!(pq.names, vec!["login"]);
assert_eq!(pq.text, "refresh the token");
assert!(pq.has_filters());
}
#[test]
fn language_alias_and_repeats_or() {
let pq = parse_query("language:rust lang:go HandlerStack");
assert_eq!(pq.languages, vec!["rust", "go"]);
assert_eq!(pq.text, "HandlerStack");
}
#[test]
fn unknown_prefix_stays_text() {
let pq = parse_query("TODO: fix the parser");
assert!(!pq.has_filters());
assert_eq!(pq.text, "TODO: fix the parser");
}
#[test]
fn quoted_value_keeps_spaces() {
let pq = parse_query("path:\"src/some path\" parse");
assert_eq!(pq.paths, vec!["src/some path"]);
assert_eq!(pq.text, "parse");
}
#[test]
fn empty_value_is_text() {
let pq = parse_query("name: thing");
assert!(pq.names.is_empty());
assert_eq!(pq.text, "name: thing");
}
}