server: mcp + global: refactor

This commit is contained in:
nym21
2025-06-21 12:43:14 +02:00
parent c9e0f9d985
commit c3ae3cb768
92 changed files with 13601 additions and 12554 deletions

View File

@@ -1,40 +0,0 @@
use clap_derive::ValueEnum;
use color_eyre::eyre::eyre;
use serde::Deserialize;
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Deserialize)]
pub enum Format {
#[serde(alias = "json")]
JSON,
#[serde(alias = "csv")]
CSV,
#[serde(alias = "tsv")]
TSV,
#[serde(alias = "md", alias = "markdown")]
#[value(alias("markdown"))]
MD,
}
impl TryFrom<Option<String>> for Format {
type Error = color_eyre::Report;
fn try_from(value: Option<String>) -> Result<Self, Self::Error> {
if let Some(value) = value {
let value = value.to_lowercase();
let value = value.as_str();
if value == "md" || value == "markdown" {
Ok(Self::MD)
} else if value == "csv" {
Ok(Self::CSV)
} else if value == "tsv" {
Ok(Self::TSV)
} else if value == "json" {
Ok(Self::JSON)
} else {
Err(eyre!("Fail"))
}
} else {
Err(eyre!("Fail"))
}
}
}

View File

@@ -1,153 +0,0 @@
use std::fmt::{self, Debug};
use color_eyre::eyre::eyre;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Index {
DateIndex,
DecadeIndex,
DifficultyEpoch,
EmptyOutputIndex,
HalvingEpoch,
Height,
InputIndex,
MonthIndex,
OpReturnIndex,
OutputIndex,
P2AIndex,
P2MSIndex,
P2PK33Index,
P2PK65Index,
P2PKHIndex,
P2SHIndex,
P2TRIndex,
P2WPKHIndex,
P2WSHIndex,
QuarterIndex,
TxIndex,
UnknownOutputIndex,
WeekIndex,
YearIndex,
}
impl Index {
pub fn all() -> [Self; 24] {
[
Self::DateIndex,
Self::DecadeIndex,
Self::DifficultyEpoch,
Self::EmptyOutputIndex,
Self::HalvingEpoch,
Self::Height,
Self::InputIndex,
Self::MonthIndex,
Self::OpReturnIndex,
Self::OutputIndex,
Self::P2AIndex,
Self::P2MSIndex,
Self::P2PK33Index,
Self::P2PK65Index,
Self::P2PKHIndex,
Self::P2SHIndex,
Self::P2TRIndex,
Self::P2WPKHIndex,
Self::P2WSHIndex,
Self::QuarterIndex,
Self::TxIndex,
Self::UnknownOutputIndex,
Self::WeekIndex,
Self::YearIndex,
]
}
pub fn possible_values(&self) -> &[&str] {
// Always have the "correct" id at the end
match self {
Self::DateIndex => &["d", "date", "dateindex"],
Self::DecadeIndex => &["decade", "decadeindex"],
Self::DifficultyEpoch => &["difficulty", "difficultyepoch"],
Self::EmptyOutputIndex => &["empty", "emptyoutputindex"],
Self::HalvingEpoch => &["halving", "halvingepoch"],
Self::Height => &["h", "height"],
Self::InputIndex => &["txin", "inputindex"],
Self::MonthIndex => &["m", "month", "monthindex"],
Self::OpReturnIndex => &["opreturn", "opreturnindex"],
Self::OutputIndex => &["txout", "outputindex"],
Self::P2AIndex => &["p2a", "p2aindex"],
Self::P2MSIndex => &["p2ms", "p2msindex"],
Self::P2PK33Index => &["p2pk33", "p2pk33index"],
Self::P2PK65Index => &["p2pk65", "p2pk65index"],
Self::P2PKHIndex => &["p2pkh", "p2pkhindex"],
Self::P2SHIndex => &["p2sh", "p2shindex"],
Self::P2TRIndex => &["p2tr", "p2trindex"],
Self::P2WPKHIndex => &["p2wpkh", "p2wpkhindex"],
Self::P2WSHIndex => &["p2wsh", "p2wshindex"],
Self::QuarterIndex => &["q", "quarter", "quarterindex"],
Self::TxIndex => &["tx", "txindex"],
Self::UnknownOutputIndex => &["unknown", "unknownoutputindex"],
Self::WeekIndex => &["w", "week", "weekindex"],
Self::YearIndex => &["y", "year", "yearindex"],
}
}
pub fn all_possible_values() -> Vec<String> {
Self::all()
.iter()
.flat_map(|i| i.possible_values().iter().map(|s| s.to_string()))
.collect::<Vec<_>>()
}
pub fn serialize_short(&self) -> String {
self.possible_values()
.iter()
.find(|str| str.len() > 1)
.unwrap()
.to_string()
}
pub fn serialize_long(&self) -> String {
self.possible_values().last().unwrap().to_string()
}
}
impl TryFrom<&str> for Index {
type Error = color_eyre::Report;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(match value.to_lowercase().as_str() {
v if (Self::DateIndex).possible_values().contains(&v) => Self::DateIndex,
v if (Self::DecadeIndex).possible_values().contains(&v) => Self::DecadeIndex,
v if (Self::DifficultyEpoch).possible_values().contains(&v) => Self::DifficultyEpoch,
v if (Self::EmptyOutputIndex).possible_values().contains(&v) => Self::EmptyOutputIndex,
v if (Self::HalvingEpoch).possible_values().contains(&v) => Self::HalvingEpoch,
v if (Self::Height).possible_values().contains(&v) => Self::Height,
v if (Self::InputIndex).possible_values().contains(&v) => Self::InputIndex,
v if (Self::MonthIndex).possible_values().contains(&v) => Self::MonthIndex,
v if (Self::OpReturnIndex).possible_values().contains(&v) => Self::OpReturnIndex,
v if (Self::OutputIndex).possible_values().contains(&v) => Self::OutputIndex,
v if (Self::P2AIndex).possible_values().contains(&v) => Self::P2AIndex,
v if (Self::P2MSIndex).possible_values().contains(&v) => Self::P2MSIndex,
v if (Self::P2PK33Index).possible_values().contains(&v) => Self::P2PK33Index,
v if (Self::P2PK65Index).possible_values().contains(&v) => Self::P2PK65Index,
v if (Self::P2PKHIndex).possible_values().contains(&v) => Self::P2PKHIndex,
v if (Self::P2SHIndex).possible_values().contains(&v) => Self::P2SHIndex,
v if (Self::P2TRIndex).possible_values().contains(&v) => Self::P2TRIndex,
v if (Self::P2WPKHIndex).possible_values().contains(&v) => Self::P2WPKHIndex,
v if (Self::P2WSHIndex).possible_values().contains(&v) => Self::P2WSHIndex,
v if (Self::QuarterIndex).possible_values().contains(&v) => Self::QuarterIndex,
v if (Self::QuarterIndex).possible_values().contains(&v) => Self::QuarterIndex,
v if (Self::TxIndex).possible_values().contains(&v) => Self::TxIndex,
v if (Self::WeekIndex).possible_values().contains(&v) => Self::WeekIndex,
v if (Self::YearIndex).possible_values().contains(&v) => Self::YearIndex,
v if (Self::UnknownOutputIndex).possible_values().contains(&v) => {
Self::UnknownOutputIndex
}
_ => return Err(eyre!("Bad index")),
})
}
}
impl fmt::Display for Index {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Debug::fmt(self, f)
}
}

View File

@@ -1,178 +0,0 @@
#![doc = include_str!("../README.md")]
#![doc = "\n## Example\n\n```rust"]
#![doc = include_str!("../examples/main.rs")]
#![doc = "```"]
use brk_computer::Computer;
use brk_core::Result;
use brk_indexer::Indexer;
use brk_vec::AnyCollectableVec;
use tabled::settings::Style;
mod format;
mod index;
mod output;
mod params;
mod table;
mod vec_trees;
pub use format::Format;
pub use index::Index;
pub use output::{Output, Value};
pub use params::{Params, ParamsOpt};
pub use table::Tabled;
use vec_trees::VecTrees;
pub struct Query<'a> {
pub vec_trees: VecTrees<'a>,
_indexer: &'a Indexer,
_computer: &'a Computer,
}
impl<'a> Query<'a> {
pub fn build(indexer: &'a Indexer, computer: &'a Computer) -> Self {
let mut vec_trees = VecTrees::default();
indexer
.vecs()
.vecs()
.into_iter()
.for_each(|vec| vec_trees.insert(vec));
computer
.vecs()
.into_iter()
.for_each(|vec| vec_trees.insert(vec));
Self {
vec_trees,
_indexer: indexer,
_computer: computer,
}
}
pub fn search(&self, index: Index, ids: &[&str]) -> Vec<(String, &&dyn AnyCollectableVec)> {
let tuples = ids
.iter()
.flat_map(|s| {
s.to_lowercase()
.replace("_", "-")
.split_whitespace()
.flat_map(|s| {
s.split(',')
.flat_map(|s| s.split('+').map(|s| s.to_string()))
})
.collect::<Vec<_>>()
})
.map(|mut id| {
let mut res = self.vec_trees.id_to_index_to_vec.get(&id);
if res.is_none() {
if let Ok(index) = Index::try_from(id.as_str()) {
id = index.possible_values().last().unwrap().to_string();
res = self.vec_trees.id_to_index_to_vec.get(&id)
}
}
(id, res)
})
.filter(|(_, opt)| opt.is_some())
.map(|(id, vec)| (id, vec.unwrap()))
.collect::<Vec<_>>();
tuples
.iter()
.flat_map(|(str, i_to_v)| i_to_v.get(&index).map(|vec| (str.to_owned(), vec)))
.collect::<Vec<_>>()
}
pub fn format(
&self,
vecs: Vec<(String, &&dyn AnyCollectableVec)>,
from: Option<i64>,
to: Option<i64>,
format: Option<Format>,
) -> color_eyre::Result<Output> {
let mut values = vecs
.iter()
.map(|(_, vec)| -> Result<Vec<serde_json::Value>> {
vec.collect_range_serde_json(from, to)
})
.collect::<Result<Vec<_>>>()?;
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::<Vec<_>>()
.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::<Vec<_>>());
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,
index: Index,
ids: &[&str],
from: Option<i64>,
to: Option<i64>,
format: Option<Format>,
) -> color_eyre::Result<Output> {
self.format(self.search(index, ids), from, to, format)
}
}

View File

@@ -1,43 +0,0 @@
use std::fmt;
use serde::Serialize;
use tabled::Tabled as TabledTabled;
use crate::Format;
#[derive(Debug)]
pub enum Output {
Json(Value),
CSV(String),
TSV(String),
MD(String),
}
#[derive(Debug, Serialize, TabledTabled)]
#[serde(untagged)]
pub enum Value {
Matrix(Vec<Vec<serde_json::Value>>),
List(Vec<serde_json::Value>),
Single(serde_json::Value),
}
impl Output {
pub fn default(format: Option<Format>) -> Self {
match format {
Some(Format::CSV) => Output::CSV("".to_string()),
Some(Format::TSV) => Output::TSV("".to_string()),
_ => Output::Json(Value::Single(serde_json::Value::Null)),
}
}
}
impl fmt::Display for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Json(value) => write!(f, "{}", serde_json::to_string_pretty(value).unwrap()),
Self::CSV(string) => write!(f, "{}", string),
Self::TSV(string) => write!(f, "{}", string),
Self::MD(string) => write!(f, "{}", string),
}
}
}

View File

@@ -1,129 +0,0 @@
use std::{fmt::Display, ops::Deref, str::FromStr};
use clap::builder::PossibleValuesParser;
use clap_derive::Parser;
use serde::{Deserialize, Deserializer};
use serde_with::{OneOrMany, formats::PreferOne, serde_as};
use crate::{Format, Index};
#[serde_as]
#[derive(Debug, Deserialize, Parser)]
pub struct Params {
#[clap(short, long, value_parser = PossibleValuesParser::new(Index::all_possible_values()))]
#[serde(alias = "i")]
/// Index of the values requested
pub index: String,
#[clap(short, long, value_delimiter = ' ', num_args = 1..)]
#[serde(alias = "v")]
#[serde_as(as = "OneOrMany<_, PreferOne>")]
/// Names of the values requested
pub values: Vec<String>,
#[clap(flatten)]
#[serde(flatten)]
pub rest: ParamsOpt,
}
// The macro creates custom deserialization code.
// You need to specify a function name and the field name of the flattened field.
serde_with::flattened_maybe!(deserialize_rest, "rest");
impl Deref for Params {
type Target = ParamsOpt;
fn deref(&self) -> &Self::Target {
&self.rest
}
}
impl From<((String, String), ParamsOpt)> for Params {
fn from(((index, id), rest): ((String, String), ParamsOpt)) -> Self {
Self {
index,
values: vec![id],
rest,
}
}
}
#[serde_as]
#[derive(Debug, Deserialize, Parser)]
pub struct ParamsOpt {
#[clap(short, long, allow_hyphen_values = true)]
#[serde(default, alias = "f", deserialize_with = "de_unquote_i64")]
/// Inclusive starting index, if negative will be from the end
from: Option<i64>,
#[clap(short, long, allow_hyphen_values = true)]
#[serde(default, alias = "t", deserialize_with = "de_unquote_i64")]
/// Exclusive ending index, if negative will be from the end, overrides 'count'
to: Option<i64>,
#[clap(short, long, allow_hyphen_values = true)]
#[serde(default, alias = "c", deserialize_with = "de_unquote_usize")]
/// Number of values
count: Option<usize>,
#[clap(short = 'F', long)]
/// Format of the output
format: Option<Format>,
}
impl ParamsOpt {
pub fn from(&self) -> Option<i64> {
self.from
}
pub fn to(&self) -> Option<i64> {
if self.to.is_none() {
if let Some(c) = self.count {
let c = c as i64;
if let Some(f) = self.from {
if f.is_positive() || f.abs() > c {
return Some(f + c);
}
} else {
return Some(c);
}
}
}
self.to
}
pub fn format(&self) -> Option<Format> {
self.format
}
}
fn de_unquote_i64<'de, D>(deserializer: D) -> Result<Option<i64>, D::Error>
where
D: Deserializer<'de>,
{
de_unquote(deserializer)
}
fn de_unquote_usize<'de, D>(deserializer: D) -> Result<Option<usize>, D::Error>
where
D: Deserializer<'de>,
{
de_unquote(deserializer)
}
fn de_unquote<'de, D, F>(deserializer: D) -> Result<Option<F>, D::Error>
where
D: Deserializer<'de>,
F: FromStr + Display,
<F as std::str::FromStr>::Err: Display,
{
let opt: Option<String> = Option::deserialize(deserializer)?;
let s = match opt {
None => return Ok(None),
Some(mut s) => {
// strip any leading/trailing quotes
if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
s = s[1..s.len() - 1].to_string();
}
s
}
};
s.parse::<F>()
.map(Some)
.map_err(|e| serde::de::Error::custom(format!("cannot parse `{}` as type: {}", s, e)))
}

View File

@@ -1,26 +0,0 @@
use tabled::{Table, builder::Builder};
pub trait Tabled {
fn to_table(&self, ids: Vec<String>) -> Table;
}
impl Tabled for Vec<Vec<serde_json::Value>> {
fn to_table(&self, ids: Vec<String>) -> Table {
let mut builder = Builder::default();
builder.push_record(ids);
if let Some(first) = self.first() {
let len = first.len();
(0..len).for_each(|index| {
builder.push_record(
self.iter()
.map(|vec| vec.get(index).unwrap().to_string().replace("\"", "")),
);
});
}
builder.build()
}
}

View File

@@ -1,116 +0,0 @@
use std::collections::BTreeMap;
use brk_vec::AnyCollectableVec;
use derive_deref::{Deref, DerefMut};
use super::index::Index;
#[derive(Default)]
pub struct VecTrees<'a> {
pub id_to_index_to_vec: BTreeMap<String, IndexToVec<'a>>,
pub index_to_id_to_vec: BTreeMap<Index, IdToVec<'a>>,
}
impl<'a> VecTrees<'a> {
// Not the most performant or type safe but only built once so that's okay
pub fn insert(&mut self, vec: &'a dyn AnyCollectableVec) {
let name = vec.name();
let split = name.split("_to_").collect::<Vec<_>>();
if split.len() != 2
&& !(split.len() == 3
&& split.get(1).is_some_and(|s| {
s == &"up"
|| s == &"start"
|| s.ends_with("relative")
|| s.starts_with("from")
|| s == &"cumulative_up"
|| s.starts_with("cumulative_start")
|| s.starts_with("cumulative_from")
|| s == &"activity"
}))
&& !(split.len() == 4
&& split.get(1).is_some_and(|s| {
s == &"up"
|| s == &"start"
|| s.starts_with("from")
|| s == &"cumulative_up"
|| s == &"cumulative_start"
|| s.starts_with("cumulative_from")
})
&& split.get(2).is_some_and(|s| s.ends_with("relative")))
{
dbg!(&name, &split);
panic!();
}
let str = vec
.index_type_to_string()
.split("::")
.last()
.unwrap()
.to_lowercase();
let index = Index::try_from(str.as_str())
.inspect_err(|_| {
dbg!(&str);
})
.unwrap();
if split[0] != index.to_string().to_lowercase() {
dbg!(&name, split[0], index.to_string());
panic!();
}
let key = split[1..].join("_to_").to_string().replace("_", "-");
let prev = self
.id_to_index_to_vec
.entry(key.clone())
.or_default()
.insert(index, vec);
if prev.is_some() {
dbg!(&key, str, name);
panic!()
}
let prev = self
.index_to_id_to_vec
.entry(index)
.or_default()
.insert(key.clone(), vec);
if prev.is_some() {
dbg!(&key, str, name);
panic!()
}
}
pub fn serialize_id_to_index_to_vec(&self) -> BTreeMap<String, Vec<String>> {
self.id_to_index_to_vec
.iter()
.map(|(id, index_to_vec)| {
(
id.to_string(),
index_to_vec
.keys()
.map(|i| i.serialize_long())
.collect::<Vec<_>>(),
)
})
.collect()
}
pub fn serialize_index_to_id_to_vec(&self) -> BTreeMap<String, Vec<String>> {
self.index_to_id_to_vec
.iter()
.map(|(index, id_to_vec)| {
(
index.serialize_long(),
id_to_vec
.keys()
.map(|id| id.to_string())
.collect::<Vec<_>>(),
)
})
.collect()
}
}
#[derive(Default, Deref, DerefMut)]
pub struct IndexToVec<'a>(BTreeMap<Index, &'a dyn AnyCollectableVec>);
#[derive(Default, Deref, DerefMut)]
pub struct IdToVec<'a>(BTreeMap<String, &'a dyn AnyCollectableVec>);