use crate::Value; use derive_new::new; use getset::Getters; use nu_source::{b, span_for_spanned_list, DebugDocBuilder, HasFallibleSpan, PrettyDebug, Span}; use num_bigint::BigInt; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] pub enum UnspannedPathMember { String(String), Int(BigInt), } impl UnspannedPathMember { pub fn into_path_member(self, span: impl Into) -> PathMember { PathMember { unspanned: self, span: span.into(), } } } #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)] pub struct PathMember { pub unspanned: UnspannedPathMember, pub span: Span, } impl PrettyDebug for &PathMember { fn pretty(&self) -> DebugDocBuilder { match &self.unspanned { UnspannedPathMember::String(string) => b::primitive(format!("{:?}", string)), UnspannedPathMember::Int(int) => b::primitive(format!("{}", int)), } } } #[derive( Debug, Hash, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq, Getters, Clone, new, )] pub struct ColumnPath { #[get = "pub"] members: Vec, } impl ColumnPath { pub fn iter(&self) -> impl Iterator { self.members.iter() } pub fn split_last(&self) -> (&PathMember, &[PathMember]) { self.members.split_last().unwrap() } } impl PrettyDebug for ColumnPath { fn pretty(&self) -> DebugDocBuilder { let members: Vec = self.members.iter().map(|member| member.pretty()).collect(); b::delimit( "(", b::description("path") + b::equals() + b::intersperse(members, b::space()), ")", ) .nest() } } impl HasFallibleSpan for ColumnPath { fn maybe_span(&self) -> Option { if self.members.len() == 0 { None } else { Some(span_for_spanned_list(self.members.iter().map(|m| m.span))) } } } impl PathMember { pub fn string(string: impl Into, span: impl Into) -> PathMember { UnspannedPathMember::String(string.into()).into_path_member(span) } pub fn int(int: impl Into, span: impl Into) -> PathMember { UnspannedPathMember::Int(int.into()).into_path_member(span) } } pub fn did_you_mean(obj_source: &Value, field_tried: &PathMember) -> Option> { let field_tried = match &field_tried.unspanned { UnspannedPathMember::String(string) => string.clone(), UnspannedPathMember::Int(int) => format!("{}", int), }; let possibilities = obj_source.data_descriptors(); let mut possible_matches: Vec<_> = possibilities .into_iter() .map(|x| { let word = x.clone(); let distance = natural::distance::levenshtein_distance(&word, &field_tried); (distance, word) }) .collect(); if !possible_matches.is_empty() { possible_matches.sort(); Some(possible_matches) } else { None } }