#![doc = include_str!("../README.md")] use std::{collections::BTreeMap, sync::OnceLock}; use brk_computer::Computer; use brk_error::{Error, Result}; use brk_indexer::Indexer; use brk_structs::Height; use nucleo_matcher::{ Config, Matcher, pattern::{AtomKind, CaseMatching, Normalization, Pattern}, }; use quick_cache::sync::Cache; use tabled::settings::Style; use vecdb::{AnyCollectableVec, AnyStoredVec}; mod deser; mod format; mod ids; mod index; mod output; mod pagination; mod params; mod table; mod vecs; pub use format::Format; pub use index::Index; pub use output::{Output, Value}; pub use pagination::{PaginatedIndexParam, PaginationParam}; pub use params::{IdParam, Params, ParamsOpt}; pub use table::Tabled; use vecs::Vecs; use crate::vecs::{IdToVec, IndexToVec}; pub fn cached_errors() -> &'static Cache { static CACHE: OnceLock> = OnceLock::new(); CACHE.get_or_init(|| Cache::new(1000)) } #[allow(dead_code)] pub struct Interface<'a> { vecs: Vecs<'a>, indexer: &'a Indexer, computer: &'a Computer, } impl<'a> Interface<'a> { pub fn build(indexer: &Indexer, computer: &Computer) -> Self { let indexer = indexer.static_clone(); let computer = computer.static_clone(); let vecs = Vecs::build(indexer, computer); Self { vecs, indexer, computer, } } pub fn get_height(&self) -> Height { Height::from(self.indexer.vecs.height_to_blockhash.stamp()) } pub fn search(&self, params: &Params) -> Result> { let ids = ¶ms.ids; let index = params.index; let ids_to_vec = self .vecs .index_to_id_to_vec .get(&index) .ok_or(Error::String(format!( "Index \"{}\" isn't a valid index", index )))?; ids.iter() .map(|id| { let vec = ids_to_vec.get(id.as_str()).ok_or_else(|| { let cached_errors = cached_errors(); if let Some(message) = cached_errors.get(id) { return Error::String(message) } let mut message = format!( "No vec named \"{}\" indexed by \"{}\" found.\n", id, index ); let mut matcher = Matcher::new(Config::DEFAULT); let matches = Pattern::new( id.as_str(), CaseMatching::Ignore, Normalization::Smart, AtomKind::Fuzzy, ) .match_list(ids_to_vec.keys(), &mut matcher) .into_iter() .take(10) .map(|(s, _)| s) .collect::>(); if !matches.is_empty() { message += &format!("\nMaybe you meant one of the following: {matches:#?} ?\n"); } if let Some(index_to_vec) = self.id_to_index_to_vec().get(id.as_str()) { message += &format!("\nBut there is a vec named {id} which supports the following indexes: {:#?}\n", index_to_vec.keys()); } cached_errors.insert(id.clone(), message.clone()); Error::String(message) }); vec.map(|vec| (id.clone(), vec)) }) .collect::>>() } pub fn format( &self, vecs: Vec<(String, &&dyn AnyCollectableVec)>, params: &ParamsOpt, ) -> Result { let from = params.from().map(|from| { vecs.iter() .map(|(_, v)| v.i64_to_usize(from)) .min() .unwrap_or_default() }); let to = params.to().map(|to| { vecs.iter() .map(|(_, v)| v.i64_to_usize(to)) .min() .unwrap_or_default() }); let mut values = vecs .iter() .map(|(_, vec)| -> Result> { Ok(vec.collect_range_serde_json(from, to)?) }) .collect::>>()?; let format = params.format(); if values.is_empty() { return Ok(Output::default(format)); } let ids_last_i = vecs.len() - 1; Ok(match format { Some(Format::CSV) | Some(Format::TSV) => { let delimiter = if format == Some(Format::CSV) { ',' } else { '\t' }; let mut text = vecs .iter() .map(|(id, _)| id.to_owned()) .collect::>() .join(&delimiter.to_string()); text.push('\n'); let values_len = values.first().unwrap().len(); (0..values_len).for_each(|i| { let mut line = "".to_string(); values.iter().enumerate().for_each(|(id_i, v)| { line += &v.get(i).unwrap().to_string(); if id_i == ids_last_i { line.push('\n'); } else { line.push(delimiter); } }); text += &line; }); if format == Some(Format::CSV) { Output::CSV(text) } else { Output::TSV(text) } } Some(Format::MD) => { let mut table = values.to_table(vecs.iter().map(|(s, _)| s.to_owned()).collect::>()); table.with(Style::markdown()); Output::MD(table.to_string()) } Some(Format::JSON) | None => { if values.len() == 1 { let mut values = values.pop().unwrap(); if values.len() == 1 { let value = values.pop().unwrap(); Output::Json(Value::Single(value)) } else { Output::Json(Value::List(values)) } } else { Output::Json(Value::Matrix(values)) } } }) } pub fn search_and_format(&self, params: Params) -> Result { self.format(self.search(¶ms)?, ¶ms.rest) } pub fn id_to_index_to_vec(&self) -> &BTreeMap<&str, IndexToVec<'_>> { &self.vecs.id_to_index_to_vec } pub fn index_to_id_to_vec(&self) -> &BTreeMap> { &self.vecs.index_to_id_to_vec } pub fn get_vecid_count(&self) -> usize { self.vecs.id_count } pub fn get_index_count(&self) -> usize { self.vecs.index_count } pub fn get_vec_count(&self) -> usize { self.vecs.vec_count } pub fn get_indexes(&self) -> &[&'static str] { &self.vecs.indexes } pub fn get_accepted_indexes(&self) -> &BTreeMap<&'static str, &'static [&'static str]> { &self.vecs.accepted_indexes } pub fn get_vecids(&self, pagination: PaginationParam) -> &[&str] { self.vecs.ids(pagination) } pub fn get_index_to_vecids(&self, paginated_index: PaginatedIndexParam) -> Vec<&str> { self.vecs.index_to_ids(paginated_index) } pub fn get_vecid_to_indexes(&self, id: String) -> Option<&Vec<&'static str>> { self.vecs.id_to_indexes(id) } }