use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;
pub struct NameIndex {
inner: Arc<RwLock<HashMap<String, Vec<i64>>>>,
}
impl Default for NameIndex {
fn default() -> Self {
Self::new()
}
}
impl NameIndex {
pub fn new() -> Self {
Self {
inner: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn insert(&self, name: String, node_id: i64) {
let mut index = self.inner.write();
index.entry(name).or_default().push(node_id);
}
pub fn clear(&self) {
let mut index = self.inner.write();
index.clear();
}
pub fn get_exact(&self, name: &str) -> Vec<i64> {
let index = self.inner.read();
index.get(name).cloned().unwrap_or_default()
}
pub fn get_prefix(&self, prefix: &str) -> Vec<i64> {
let index = self.inner.read();
let mut result = Vec::new();
for (name, ids) in index.iter() {
if name.starts_with(prefix) {
result.extend(ids.clone());
}
}
result
}
pub fn get_substring(&self, substring: &str) -> Vec<i64> {
let index = self.inner.read();
let mut result = Vec::new();
for (name, ids) in index.iter() {
if name.contains(substring) {
result.extend(ids.clone());
}
}
result
}
pub fn get_glob(&self, pattern: &str) -> Vec<i64> {
let index = self.inner.read();
let mut result = Vec::new();
for (name, ids) in index.iter() {
if glob_match(pattern, name) {
result.extend(ids.clone());
}
}
result
}
pub fn stats(&self) -> NameIndexStats {
let index = self.inner.read();
let total_names = index.len();
let total_nodes: usize = index.values().map(|v| v.len()).sum();
NameIndexStats {
unique_names: total_names,
total_nodes,
}
}
}
pub struct NameIndexStats {
pub unique_names: usize,
pub total_nodes: usize,
}
impl NameIndex {
pub(crate) fn export(&self) -> HashMap<String, Vec<i64>> {
self.inner.read().clone()
}
pub(crate) fn import(&self, data: HashMap<String, Vec<i64>>) {
let mut index = self.inner.write();
*index = data;
}
}
fn glob_match(pattern: &str, text: &str) -> bool {
let mut p = pattern.chars().peekable();
let t: Vec<char> = text.chars().collect();
glob_match_inner(&mut p, &t, 0)
}
fn glob_match_inner<I: Iterator<Item = char>>(
p: &mut std::iter::Peekable<I>,
chars: &[char],
mut idx: usize,
) -> bool {
loop {
match p.next() {
None => return idx == chars.len(),
Some('?') => {
if idx >= chars.len() {
return false;
}
idx += 1;
}
Some('*') => {
while p.peek() == Some(&'*') {
p.next();
}
let rest: Vec<char> = p.by_ref().collect();
for end in idx..=chars.len() {
let mut rp = rest.iter().copied().peekable();
if glob_match_inner(&mut rp, chars, end) {
return true;
}
}
return false;
}
Some(literal) => {
if idx >= chars.len() || chars[idx] != literal {
return false;
}
idx += 1;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_glob_match_basic() {
assert!(glob_match("User", "User"));
assert!(!glob_match("User", "SuperUser"));
assert!(!glob_match("User", "UserAdmin"));
assert!(!glob_match("User", "Use"));
assert!(glob_match("User*", "User"));
assert!(glob_match("User*", "UserAdmin"));
assert!(!glob_match("User*", "SuperUser"));
assert!(glob_match("*User", "User"));
assert!(glob_match("*User", "SuperUser"));
assert!(!glob_match("*User", "UserAdmin"));
assert!(glob_match("*User*", "User"));
assert!(glob_match("*User*", "SuperUser"));
assert!(glob_match("*User*", "UserAdmin"));
assert!(glob_match("User?", "User1"));
assert!(!glob_match("User?", "User"));
assert!(!glob_match("User?", "User12"));
assert!(glob_match("?ser", "User"));
assert!(glob_match("Us?r", "User"));
assert!(glob_match("U*r?", "User1"));
assert!(glob_match("U*r?", "Usr1"));
assert!(!glob_match("U*r?", "Usr"));
assert!(glob_match("*", ""));
assert!(glob_match("*", "anything"));
assert!(glob_match("a**b", "axyzb"));
assert!(glob_match("a**b", "ab"));
assert!(glob_match("", ""));
assert!(!glob_match("", "x"));
assert!(!glob_match("user", "User"));
}
#[test]
fn test_exact_match() {
let index = NameIndex::new();
index.insert("func_a".to_string(), 1);
index.insert("func_b".to_string(), 2);
index.insert("class_a".to_string(), 3);
assert_eq!(index.get_exact("func_a"), vec![1]);
assert_eq!(index.get_exact("func_b"), vec![2]);
assert_eq!(index.get_exact("nonexistent"), Vec::<i64>::new());
}
#[test]
fn test_prefix_match() {
let index = NameIndex::new();
index.insert("func_a".to_string(), 1);
index.insert("func_b".to_string(), 2);
index.insert("class_a".to_string(), 3);
index.insert("func_ab".to_string(), 4);
let results = index.get_prefix("func");
assert_eq!(results.len(), 3);
assert!(results.contains(&1));
assert!(results.contains(&2));
assert!(results.contains(&4));
assert!(!results.contains(&3));
}
#[test]
fn test_multiple_nodes_same_name() {
let index = NameIndex::new();
index.insert("duplicate".to_string(), 1);
index.insert("duplicate".to_string(), 2);
index.insert("duplicate".to_string(), 3);
let mut results = index.get_exact("duplicate");
results.sort();
assert_eq!(results, vec![1, 2, 3]);
}
#[test]
fn test_stats() {
let index = NameIndex::new();
index.insert("a".to_string(), 1);
index.insert("b".to_string(), 2);
index.insert("a".to_string(), 3);
let stats = index.stats();
assert_eq!(stats.unique_names, 2);
assert_eq!(stats.total_nodes, 3);
}
}