use std::cmp::Ordering;
use std::fmt;
use std::slice::Iter;
use std::slice::SliceIndex;
#[derive(Debug, Default, Eq, PartialEq, Clone, PartialOrd, Ord)]
pub struct NormalizedPath<'a>(Vec<PathElement<'a>>);
impl<'a> NormalizedPath<'a> {
pub(crate) fn push<T: Into<PathElement<'a>>>(&mut self, elem: T) {
self.0.push(elem.into())
}
pub(crate) fn clone_and_push<T: Into<PathElement<'a>>>(&self, elem: T) -> Self {
let mut new_path = self.clone();
new_path.push(elem.into());
new_path
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> Iter<'_, PathElement<'a>> {
self.0.iter()
}
pub fn get<I>(&self, index: I) -> Option<&I::Output>
where
I: SliceIndex<[PathElement<'a>]>,
{
self.0.get(index)
}
pub fn first(&self) -> Option<&PathElement<'a>> {
self.0.first()
}
pub fn last(&self) -> Option<&PathElement<'a>> {
self.0.last()
}
}
impl<'a> IntoIterator for NormalizedPath<'a> {
type Item = PathElement<'a>;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl fmt::Display for NormalizedPath<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "$")?;
for elem in &self.0 {
match elem {
PathElement::Name(name) => write!(f, "['{name}']")?,
PathElement::Index(index) => write!(f, "[{index}]")?,
}
}
Ok(())
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum PathElement<'a> {
Name(&'a str),
Index(usize),
}
impl PathElement<'_> {
pub fn as_name(&self) -> Option<&str> {
match self {
PathElement::Name(n) => Some(n),
PathElement::Index(_) => None,
}
}
pub fn as_index(&self) -> Option<usize> {
match self {
PathElement::Name(_) => None,
PathElement::Index(i) => Some(*i),
}
}
pub fn is_name(&self) -> bool {
self.as_name().is_some()
}
pub fn is_index(&self) -> bool {
self.as_index().is_some()
}
}
impl<'a> From<&'a String> for PathElement<'a> {
fn from(s: &'a String) -> Self {
Self::Name(s.as_str())
}
}
impl From<usize> for PathElement<'_> {
fn from(index: usize) -> Self {
Self::Index(index)
}
}
impl PartialOrd for PathElement<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PathElement<'_> {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(PathElement::Name(a), PathElement::Name(b)) => a.cmp(b),
(PathElement::Index(a), PathElement::Index(b)) => a.cmp(b),
(PathElement::Name(_), PathElement::Index(_)) => Ordering::Greater,
(PathElement::Index(_), PathElement::Name(_)) => Ordering::Less,
}
}
}
impl PartialEq<str> for PathElement<'_> {
fn eq(&self, other: &str) -> bool {
match self {
PathElement::Name(s) => s.eq(&other),
PathElement::Index(_) => false,
}
}
}
impl PartialEq<&str> for PathElement<'_> {
fn eq(&self, other: &&str) -> bool {
match self {
PathElement::Name(s) => s.eq(other),
PathElement::Index(_) => false,
}
}
}
impl PartialEq<usize> for PathElement<'_> {
fn eq(&self, other: &usize) -> bool {
match self {
PathElement::Name(_) => false,
PathElement::Index(i) => i.eq(other),
}
}
}
impl fmt::Display for PathElement<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathElement::Name(n) => {
for c in n.chars() {
match c {
'\u{0008}' => write!(f, r#"\b"#)?, '\u{000C}' => write!(f, r#"\f"#)?, '\u{000A}' => write!(f, r#"\n"#)?, '\u{000D}' => write!(f, r#"\r"#)?, '\u{0009}' => write!(f, r#"\t"#)?, '\u{0027}' => write!(f, r#"\'"#)?, '\u{005C}' => write!(f, r#"\"#)?, ('\x00'..='\x07') | '\x0b' | '\x0e' | '\x0f' => {
write!(f, "\\u000{:x}", c as i32)?
}
_ => write!(f, "{c}")?,
}
}
Ok(())
}
PathElement::Index(i) => write!(f, "{i}"),
}
}
}
#[cfg(test)]
mod tests {
use insta::assert_snapshot;
use super::PathElement;
#[test]
fn test_normalized_element() {
assert_snapshot!(PathElement::Name("foo"), @"foo");
assert_snapshot!(PathElement::Index(1), @"1");
assert_snapshot!(PathElement::Name("'hi'"), @r#"\'hi\'"#);
assert_snapshot!(PathElement::Name(r#"'\b\f\n\r\t\\'"#), @r#"\'\b\f\n\r\t\\\'"#);
assert_snapshot!(PathElement::Name("\u{000B}"), @r#"\u000b"#);
assert_snapshot!(PathElement::Name("\u{0000}"), @r#"\u0000"#);
assert_snapshot!(PathElement::Name(
"\u{0001}\u{0002}\u{0003}\u{0004}\u{0005}\u{0006}\u{0007}\u{000e}\u{000F}"
), @r#"\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u000e\u000f"#);
}
}