pipa-lang 1.0.0-alpha.1

A tiny template language
Documentation
// SPDX-FileCopyrightText: Copyright 2026 olav@occy.org
// SPDX-License-Identifier: MPL-2.0

use crate::value::key::Key;
use crate::value::parse;
use bstr::ByteSlice;
use serde::Deserialize;
use serde::Serialize;
use std::collections::VecDeque;

#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[derive(Serialize, Deserialize)]
pub struct Keys(Vec<Key>);

impl core::fmt::Debug for Keys {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self.0.is_empty() {
            true => write!(f, "Keys([])"),
            false => write!(f, "Keys(\"{self}\")"),
        }
    }
}

impl AsRef<[Key]> for Keys {
    fn as_ref(&self) -> &[Key] {
        &self.0
    }
}

impl core::fmt::Display for Keys {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        if self.0.is_empty() {
            return write!(f, ".");
        }

        let str = self
            .0
            .iter()
            .map(ToString::to_string)
            .collect::<Vec<_>>()
            .join(".");

        f.write_str(&str)
    }
}

impl<T: Into<Key>> From<T> for Keys {
    fn from(value: T) -> Self {
        Self(vec![value.into()])
    }
}

impl<T: Into<Key>> From<Vec<T>> for Keys {
    fn from(value: Vec<T>) -> Self {
        Self(value.into_iter().map(Into::into).collect())
    }
}

impl<T: Into<Key> + Clone> From<&[T]> for Keys {
    fn from(value: &[T]) -> Self {
        Self(value.iter().cloned().map(Into::into).collect())
    }
}

impl TryFrom<Vec<Keys>> for Keys {
    type Error = Vec<Keys>;

    fn try_from(value: Vec<Keys>) -> Result<Self, Self::Error> {
        match value.is_empty() {
            false => Ok(Self(value.into_iter().flat_map(|a| a.0).collect())),
            true => Err(value),
        }
    }
}

impl Keys {
    pub fn parse(value: &[u8]) -> Self {
        if value == b"." || value == b"" {
            return Self(Vec::default());
        }

        let mut keys = value
            .split_str(b".")
            .filter(|k| !k.is_empty())
            .map(Key::from)
            .collect::<VecDeque<_>>();

        if value.starts_with(b"...") {
            keys.push_front(Key::from("..."));
        } else if value.starts_with(b"..") {
            keys.push_front(Key::from(".."));
        }

        Self(keys.into())
    }

    pub(crate) fn new(key: Vec<u8>) -> Self {
        Self(vec![Key::new(key)])
    }

    pub(crate) fn into_vec(self) -> Vec<Key> {
        self.0
    }

    pub fn as_slice(&self) -> &[Key] {
        &self.0
    }

    pub fn as_usize(&self) -> Option<usize> {
        match self.len() {
            1 => self.0.first().and_then(|k| parse::usize(k.as_ref()).ok()),
            _ => None,
        }
    }

    pub fn get(&self, index: usize) -> Option<&Key> {
        self.0.get(index)
    }

    pub fn first(&self) -> Option<&Key> {
        self.0.first()
    }

    pub fn rest(&self) -> &[Key] {
        self.0.get(1..).unwrap_or_default()
    }

    pub fn skip(&self, n: usize) -> &[Key] {
        self.0.get(n..).unwrap_or_default()
    }

    pub fn without_first(mut self) -> Keys {
        self.first().is_some().then(|| self.0.remove(0));
        self
    }

    pub(crate) fn len(&self) -> usize {
        self.0.len()
    }

    pub fn is_empty(&self) -> bool {
        self.len() == 0 || self.0.iter().all(Key::is_empty)
    }

    pub(crate) fn is_placeholder(&self) -> bool {
        self.len() == 1 && self.has_leading_placeholder()
    }

    pub(crate) fn has_leading_placeholder(&self) -> bool {
        self.0.first().is_some_and(|k| k == b"_")
    }

    pub fn is_context_spread(&self) -> bool {
        self.len() == 1 && self.is_spread()
    }

    pub fn is_partial_spread(&self) -> bool {
        self.len() > 1 && self.is_spread()
    }

    pub(crate) fn is_spread(&self) -> bool {
        self.0.first().is_some_and(|k| k == b"...")
    }

    pub fn is_parent(&self) -> bool {
        self.0.first().is_some_and(|k| k == b"..")
    }

    pub fn fold<T: Into<Keys>>(mut self, other: T) -> Self {
        self.0.append(&mut other.into().0);
        self
    }

    pub fn join(mut self) -> Vec<u8> {
        match self.0.len() {
            0 => Vec::default(),
            1 => self.0.remove(0).into(),
            _ => self.to_string().into_bytes(),
        }
    }

    pub fn is_match<T: AsRef<[u8]>>(&self, path: &[T]) -> bool {
        self.0.len() == path.len() && self.is_prefix(path)
    }

    pub fn is_prefix<T: AsRef<[u8]>>(&self, path: &[T]) -> bool {
        path.iter()
            .enumerate()
            .all(|(i, p)| self.0.get(i).is_some_and(|s| s == p))
    }
}