Skip to main content

ical/tree/
leaf.rs

1//! # Leaf
2//!
3//! The atom of the syntax tree: a single raw piece of a calendar.
4//!
5//! Two leaf kinds split on a spec boundary. [`IcalLeaf`] wraps still-escaped
6//! *text* (a name, a parameter value, a line ending), US-ASCII in every
7//! version and so always valid UTF-8.
8//!
9//! [`IcalValueLeaf`] wraps a still-escaped *value* component as raw bytes,
10//! because a property value may carry a foreign charset (a vCalendar 1.0
11//! `CHARSET`) that is not UTF-8.
12//!
13//! Both are a [`Cow`], so a parsed leaf borrows the source (the basis of
14//! byte-faithful round-trips) and only becomes owned when a build or an edit
15//! replaces it.
16
17use alloc::{borrow::Cow, string::String, vec::Vec};
18
19/// A single raw text piece of a calendar (a name, a parameter value, a line
20/// ending): borrowed when parsed, owned when built or edited. Always valid
21/// UTF-8, since the parser rejects a non-UTF-8 name or parameter.
22#[derive(Clone, Debug)]
23pub struct IcalLeaf<'a>(pub Cow<'a, str>);
24
25impl<'a> IcalLeaf<'a> {
26    /// The raw (still-escaped) text of the leaf.
27    pub fn get(&self) -> &str {
28        &self.0
29    }
30
31    /// Replace the leaf's raw text.
32    pub fn set(&mut self, text: impl Into<Cow<'a, str>>) {
33        self.0 = text.into();
34    }
35
36    /// Convert into an owned leaf (`'static`), cloning the text if borrowed.
37    pub(crate) fn into_static(self) -> IcalLeaf<'static> {
38        IcalLeaf(Cow::Owned(self.0.into_owned()))
39    }
40}
41
42impl<'a> From<&'a str> for IcalLeaf<'a> {
43    fn from(text: &'a str) -> Self {
44        Self(Cow::Borrowed(text))
45    }
46}
47
48impl From<String> for IcalLeaf<'_> {
49    fn from(text: String) -> Self {
50        Self(Cow::Owned(text))
51    }
52}
53
54/// A single raw *value* component of a calendar, held as raw bytes so a foreign
55/// charset survives byte for byte. Borrowed when parsed, owned when built or
56/// edited. The codec resolves these bytes to the decoded model's UTF-8 text
57/// (lossily, when they are not UTF-8); the raw bytes stay reachable here.
58#[derive(Clone, Debug)]
59pub struct IcalValueLeaf<'a>(pub Cow<'a, [u8]>);
60
61impl<'a> IcalValueLeaf<'a> {
62    /// The raw (still-escaped) bytes of the leaf.
63    pub fn as_bytes(&self) -> &[u8] {
64        &self.0
65    }
66
67    /// The raw bytes as UTF-8 text, lossily (invalid sequences become the
68    /// replacement character). For a diagnostic or a best-effort read; the
69    /// exact bytes are [`as_bytes`](Self::as_bytes).
70    pub fn to_str_lossy(&self) -> Cow<'_, str> {
71        String::from_utf8_lossy(&self.0)
72    }
73
74    /// Replace the leaf's raw bytes.
75    pub fn set(&mut self, bytes: impl Into<Cow<'a, [u8]>>) {
76        self.0 = bytes.into();
77    }
78
79    /// Convert into an owned leaf (`'static`), cloning the bytes if borrowed.
80    pub(crate) fn into_static(self) -> IcalValueLeaf<'static> {
81        IcalValueLeaf(Cow::Owned(self.0.into_owned()))
82    }
83}
84
85impl<'a> From<&'a [u8]> for IcalValueLeaf<'a> {
86    fn from(bytes: &'a [u8]) -> Self {
87        Self(Cow::Borrowed(bytes))
88    }
89}
90
91impl From<Vec<u8>> for IcalValueLeaf<'_> {
92    fn from(bytes: Vec<u8>) -> Self {
93        Self(Cow::Owned(bytes))
94    }
95}
96
97impl<'a> From<Cow<'a, str>> for IcalValueLeaf<'a> {
98    fn from(text: Cow<'a, str>) -> Self {
99        Self(match text {
100            Cow::Borrowed(text) => Cow::Borrowed(text.as_bytes()),
101            Cow::Owned(text) => Cow::Owned(text.into_bytes()),
102        })
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use alloc::vec::Vec;
109
110    use crate::tree::leaf::{IcalLeaf, IcalValueLeaf};
111
112    #[test]
113    fn replaces_leaf_contents() {
114        let mut text = IcalLeaf::from("a");
115        text.set("b");
116        assert_eq!(text.get(), "b");
117
118        let mut bytes = IcalValueLeaf::from(b"a".as_slice());
119        bytes.set(Vec::from(b"c".as_slice()));
120        assert_eq!(bytes.as_bytes(), b"c");
121        assert_eq!(bytes.to_str_lossy(), "c");
122    }
123}