use super::Expression;
use crate::{Span, casing::Casing};
use nu_utils::{escape_quote_string, needs_quoting};
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, fmt::Display};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PathMember {
String {
val: String,
span: Span,
optional: bool,
casing: Casing,
},
Int {
val: usize,
span: Span,
optional: bool,
},
}
impl PathMember {
pub fn int(val: usize, optional: bool, span: Span) -> Self {
PathMember::Int {
val,
span,
optional,
}
}
pub fn string(val: String, optional: bool, casing: Casing, span: Span) -> Self {
PathMember::String {
val,
span,
optional,
casing,
}
}
pub fn test_int(val: usize, optional: bool) -> Self {
PathMember::Int {
val,
optional,
span: Span::test_data(),
}
}
pub fn test_string(val: String, optional: bool, casing: Casing) -> Self {
PathMember::String {
val,
optional,
casing,
span: Span::test_data(),
}
}
pub fn make_optional(&mut self) {
match self {
PathMember::String { optional, .. } => *optional = true,
PathMember::Int { optional, .. } => *optional = true,
}
}
pub fn make_insensitive(&mut self) {
match self {
PathMember::String { casing, .. } => *casing = Casing::Insensitive,
PathMember::Int { .. } => {}
}
}
pub fn span(&self) -> Span {
match self {
PathMember::String { span, .. } => *span,
PathMember::Int { span, .. } => *span,
}
}
pub fn memory_size(&self) -> usize {
match self {
PathMember::String { val, .. } => std::mem::size_of::<Self>() + val.capacity(),
PathMember::Int { .. } => std::mem::size_of::<Self>(),
}
}
}
impl PartialEq for PathMember {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
Self::String {
val: l_val,
optional: l_opt,
..
},
Self::String {
val: r_val,
optional: r_opt,
..
},
) => l_val == r_val && l_opt == r_opt,
(
Self::Int {
val: l_val,
optional: l_opt,
..
},
Self::Int {
val: r_val,
optional: r_opt,
..
},
) => l_val == r_val && l_opt == r_opt,
_ => false,
}
}
}
impl PartialOrd for PathMember {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(
PathMember::String {
val: l_val,
optional: l_opt,
..
},
PathMember::String {
val: r_val,
optional: r_opt,
..
},
) => {
let val_ord = Some(l_val.cmp(r_val));
if let Some(Ordering::Equal) = val_ord {
Some(l_opt.cmp(r_opt))
} else {
val_ord
}
}
(
PathMember::Int {
val: l_val,
optional: l_opt,
..
},
PathMember::Int {
val: r_val,
optional: r_opt,
..
},
) => {
let val_ord = Some(l_val.cmp(r_val));
if let Some(Ordering::Equal) = val_ord {
Some(l_opt.cmp(r_opt))
} else {
val_ord
}
}
(PathMember::Int { .. }, PathMember::String { .. }) => Some(Ordering::Greater),
(PathMember::String { .. }, PathMember::Int { .. }) => Some(Ordering::Less),
}
}
}
#[doc(hidden)]
pub struct TestPathMember<T>(T);
impl<S: Into<String>> From<S> for TestPathMember<String> {
fn from(value: S) -> Self {
Self(value.into())
}
}
impl TestPathMember<String> {
pub fn into_path_member(self) -> PathMember {
PathMember::test_string(self.0, false, Casing::Sensitive)
}
}
impl From<usize> for TestPathMember<usize> {
fn from(value: usize) -> Self {
Self(value)
}
}
impl TestPathMember<usize> {
pub fn into_path_member(self) -> PathMember {
PathMember::test_int(self.0, false)
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct CellPath {
pub members: Vec<PathMember>,
}
impl CellPath {
pub fn make_optional(&mut self) {
for member in &mut self.members {
member.make_optional();
}
}
pub fn make_insensitive(&mut self) {
for member in &mut self.members {
member.make_insensitive();
}
}
pub fn to_column_name(&self) -> String {
let mut s = String::new();
for member in &self.members {
match member {
PathMember::Int { val, .. } => {
s += &val.to_string();
}
PathMember::String { val, .. } => {
s += val;
}
}
s.push('.');
}
s.pop(); s
}
pub fn memory_size(&self) -> usize {
std::mem::size_of::<Self>() + self.members.iter().map(|m| m.memory_size()).sum::<usize>()
}
}
impl Display for CellPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "$")?;
for member in self.members.iter() {
match member {
PathMember::Int { val, optional, .. } => {
let question_mark = if *optional { "?" } else { "" };
write!(f, ".{val}{question_mark}")?
}
PathMember::String {
val,
optional,
casing,
..
} => {
let question_mark = if *optional { "?" } else { "" };
let exclamation_mark = if *casing == Casing::Insensitive {
"!"
} else {
""
};
let val = if needs_quoting(val) {
&escape_quote_string(val)
} else {
val
};
write!(f, ".{val}{exclamation_mark}{question_mark}")?
}
}
}
if self.members.is_empty() {
write!(f, ".")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FullCellPath {
pub head: Expression,
pub tail: Vec<PathMember>,
}
#[cfg(test)]
mod test {
use super::*;
use std::cmp::Ordering::Greater;
#[test]
fn path_member_partial_ord() {
assert_eq!(
Some(Greater),
PathMember::test_int(5, true).partial_cmp(&PathMember::test_string(
"e".into(),
true,
Casing::Sensitive
))
);
assert_eq!(
Some(Greater),
PathMember::test_int(5, true).partial_cmp(&PathMember::test_int(5, false))
);
assert_eq!(
Some(Greater),
PathMember::test_int(6, true).partial_cmp(&PathMember::test_int(5, true))
);
assert_eq!(
Some(Greater),
PathMember::test_string("e".into(), true, Casing::Sensitive).partial_cmp(
&PathMember::test_string("e".into(), false, Casing::Sensitive)
)
);
assert_eq!(
Some(Greater),
PathMember::test_string("f".into(), true, Casing::Sensitive).partial_cmp(
&PathMember::test_string("e".into(), true, Casing::Sensitive)
)
);
}
}