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

//! A list of bytes.

use crate::value::Value;
use crate::value::list::List;
use crate::value::meta::Meta;
use crate::value::print::Print;
use crate::value::tracer::Tracer;
use bstr::ByteSlice;
use serde::Deserialize;
use serde::Serialize;
use unicode_segmentation::UnicodeSegmentation;

#[derive(Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Bytes {
    #[serde(default, skip_serializing_if = "Tracer::is_empty")]
    pub(crate) tracer: Tracer,
    #[serde(default, skip_serializing_if = "Meta::is_empty")]
    pub(crate) meta: Meta,
    #[serde(with = "crate::serde::bytes_as_base64")]
    pub(crate) data: Vec<u8>,
}

impl core::fmt::Debug for Bytes {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Bytes")
            .field("tracer", &self.tracer)
            .field("meta", &self.meta)
            .field("data", &String::from_utf8_lossy(&self.data))
            .finish()
    }
}

impl core::fmt::Display for Bytes {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(&String::from_utf8_lossy(&self.clone().print_syntax()))
    }
}

impl From<Vec<u8>> for Bytes {
    fn from(value: Vec<u8>) -> Self {
        Self {
            data: value,
            ..Default::default()
        }
    }
}

impl Bytes {
    pub(crate) fn trace<T: Into<Tracer>>(&mut self, tracer: T) {
        self.tracer.trace(tracer);
    }

    pub(crate) fn tracer(&self) -> &Tracer {
        &self.tracer
    }

    pub fn as_bytes(&self) -> &[u8] {
        &self.data
    }

    pub fn as_chars(&self) -> List {
        self.data
            .chars()
            .map(String::from)
            .map(Value::from)
            .collect()
    }

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

    pub fn contains(self, needle: &[u8]) -> bool {
        match (self.is_empty(), needle.is_empty()) {
            (false, false) => self.data.windows(needle.len()).any(|w| w == needle),
            (true, false) => false,
            (_, true) => true,
        }
    }

    pub fn join(self) -> Vec<u8> {
        self.data
    }

    pub fn clear(mut self) -> Self {
        self.data = Vec::default();
        self
    }

    pub fn trim(mut self) -> Self {
        let trim = self.data.trim();

        if self.data.len() != trim.len() {
            self.data = trim.to_vec();
        }

        self
    }

    pub fn first(mut self) -> Bytes {
        self.data = str::from_utf8(&self.data)
            .unwrap_or_default()
            .graphemes(true)
            .next()
            .map(|v| v.as_bytes().to_vec())
            .unwrap_or_default();
        self
    }

    pub fn last(mut self) -> Bytes {
        self.data = str::from_utf8(&self.data)
            .unwrap_or_default()
            .graphemes(true)
            .next_back()
            .map(|v| v.as_bytes().to_vec())
            .unwrap_or_default();
        self
    }

    pub fn rest(mut self) -> Bytes {
        self.data = str::from_utf8(&self.data)
            .unwrap_or_default()
            .graphemes(true)
            .skip(1)
            .collect::<String>()
            .into_bytes();
        self
    }

    pub(crate) fn fold(&mut self, mut value: Self) {
        match value.meta.get(b"internal-insert") {
            Some(b"prepend") => {
                value.data.append(&mut self.data);
                self.data = value.data;
            }
            Some(b"append") => {
                self.data.append(&mut value.data);
            }
            _ => {
                *self = value;
            }
        }
    }
}