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::serde::byte_map_as_strings;
use indexmap::IndexMap;
use itertools::Itertools;
use serde::Deserialize;
use serde::Serialize;

#[derive(Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Meta {
    #[serde(with = "byte_map_as_strings")]
    data: IndexMap<Vec<u8>, Vec<u8>, ahash::RandomState>,
}

const INTERNAL_META_KEYS: &[&[u8]] = &[
    b"internal-builtin",
    b"internal-content",
    b"internal-description",
    b"internal-format",
    b"internal-hide",
    b"internal-insert",
    b"internal-label",
    b"internal-order",
    b"internal-placeholder",
    b"internal-safe",
    b"internal-serialize",
    b"internal-sort",
    b"internal-taken",
    b"internal-toggle",
    b"internal-variant",
];

impl core::fmt::Debug for Meta {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        if self == &Meta::default() {
            return write!(f, "Meta::default()");
        }

        let mut f = f.debug_struct("Meta");
        for (k, v) in &self.data {
            f.field(&String::from_utf8_lossy(k), &String::from_utf8_lossy(v));
        }

        f.finish()
    }
}

impl core::fmt::Display for Meta {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        let u8l = |d| String::from_utf8_lossy(d).into_owned();
        let iter = self.data.iter().map(|(k, v)| [u8l(k), u8l(v)]);
        f.write_str(&iter.map(|p| p.join(": ")).join(", "))
    }
}

impl core::hash::Hash for Meta {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        for (k, v) in &self.data {
            k.hash(state);
            v.hash(state);
        }
    }
}

impl Meta {
    pub fn new<K: Into<Vec<u8>>, V: Into<Vec<u8>>>(key: K, value: V) -> Self {
        let data = [(key.into(), value.into())];
        let data = data.into_iter().collect();

        Self { data }
    }

    pub(crate) fn into_keys(self) -> Vec<Vec<u8>> {
        self.data.into_keys().collect()
    }

    pub fn flag<K: Into<Vec<u8>>>(&mut self, key: K) {
        self.push(key, String::default());
    }

    pub fn push<K: Into<Vec<u8>>, V: Into<Vec<u8>>>(&mut self, key: K, value: V) {
        let key = key.into();
        let value = value.into();
        validate_internal_meta_key(&key);
        self.data.insert(key, value);
    }

    pub(crate) fn append(&mut self, other: Meta) {
        for (k, v) in other.data {
            self.push(k, v);
        }
    }

    pub(crate) fn remove(&mut self, key: &[u8]) -> Option<Vec<u8>> {
        validate_internal_meta_key(key);
        self.data.swap_remove(key)
    }

    pub fn get(&self, key: &[u8]) -> Option<&[u8]> {
        validate_internal_meta_key(key);
        self.data.get(key).map(Vec::as_slice)
    }

    pub fn contains(&self, key: &[u8]) -> bool {
        validate_internal_meta_key(key);
        self.data.contains_key(key)
    }

    pub(crate) fn is_empty(&self) -> bool {
        self.data.is_empty()
    }
}

fn validate_internal_meta_key(k: &[u8]) {
    debug_assert!(
        !unknown_internal_meta_key(k),
        "unknown internal meta key: {}",
        String::from_utf8_lossy(k)
    );
}

fn unknown_internal_meta_key(k: &[u8]) -> bool {
    k.starts_with(b"internal-") && !INTERNAL_META_KEYS.contains(&k)
}