lv2_atom/
string.rs

1//! String handling atoms.
2//!
3//! This module contains two different atoms: The [`String`](struct.String.html) and the [`Literal`](struct.Literal.html). The former is for simple, non-localized UTF-8 strings, like URIs or paths, and the later is either for localized text, e.g. descriptions in the user interface, or RDF literals.
4//!
5//! Reading and writing these atoms is pretty simple: They don't require a parameter and return a either a `&str` or the literal info and a `&str`. Writing is done with a writing handle which can append strings to the string/literal. When dropped, the handle will append the null character, you therefore don't have to handle it on your own.
6//!
7//! # Example
8//! ```
9//! use lv2_core::prelude::*;
10//! use lv2_atom::prelude::*;
11//! use lv2_atom::string::StringWriter;
12//!
13//! #[derive(PortCollection)]
14//! struct MyPorts {
15//!     input: InputPort<AtomPort>,
16//!     output: OutputPort<AtomPort>,
17//! }
18//!
19//! fn run(ports: &mut MyPorts, urids: &AtomURIDCollection) {
20//!     let input: &str = ports.input.read(urids.string, ()).unwrap();
21//!     let mut writer: StringWriter = ports.output.init(urids.string, ()).unwrap();
22//!     writer.append(input).unwrap();
23//! }
24//! ```
25//!
26//! # Specifications
27//!
28//! [http://lv2plug.in/ns/ext/atom/atom.html#String](http://lv2plug.in/ns/ext/atom/atom.html#String)
29//! [http://lv2plug.in/ns/ext/atom/atom.html#Literal](http://lv2plug.in/ns/ext/atom/atom.html#Literal)
30use crate::prelude::*;
31use crate::space::*;
32use urid::*;
33
34/// An atom containing either a localized string or an RDF literal.
35///
36/// [See also the module documentation.](index.html)
37pub struct Literal;
38
39unsafe impl UriBound for Literal {
40    const URI: &'static [u8] = sys::LV2_ATOM__Literal;
41}
42
43#[derive(Clone, Copy, PartialEq, Eq, Debug)]
44/// The type or language URID of a literal.
45pub enum LiteralInfo {
46    Language(URID),
47    Datatype(URID),
48}
49
50impl<'a, 'b> Atom<'a, 'b> for Literal
51where
52    'a: 'b,
53{
54    type ReadParameter = ();
55    type ReadHandle = (LiteralInfo, &'a str);
56    type WriteParameter = LiteralInfo;
57    type WriteHandle = StringWriter<'a, 'b>;
58
59    fn read(body: Space<'a>, _: ()) -> Option<(LiteralInfo, &'a str)> {
60        let (header, body) = body.split_type::<sys::LV2_Atom_Literal_Body>()?;
61        let info = if header.lang != 0 && header.datatype == 0 {
62            LiteralInfo::Language(URID::new(header.lang)?)
63        } else if header.lang == 0 && header.datatype != 0 {
64            LiteralInfo::Datatype(URID::new(header.datatype)?)
65        } else {
66            return None;
67        };
68        let data = body.data()?;
69        std::str::from_utf8(&data[0..data.len() - 1])
70            .or_else(|error| std::str::from_utf8(&data[0..error.valid_up_to()]))
71            .ok()
72            .map(|string| (info, string))
73    }
74
75    fn init(mut frame: FramedMutSpace<'a, 'b>, info: LiteralInfo) -> Option<StringWriter<'a, 'b>> {
76        (&mut frame as &mut dyn MutSpace).write(
77            &match info {
78                LiteralInfo::Language(lang) => sys::LV2_Atom_Literal_Body {
79                    lang: lang.get(),
80                    datatype: 0,
81                },
82                LiteralInfo::Datatype(datatype) => sys::LV2_Atom_Literal_Body {
83                    lang: 0,
84                    datatype: datatype.get(),
85                },
86            },
87            true,
88        )?;
89        Some(StringWriter { frame })
90    }
91}
92
93/// An atom containing a UTF-8 encoded string.
94///
95/// [See also the module documentation.](index.html)
96pub struct String;
97
98unsafe impl UriBound for String {
99    const URI: &'static [u8] = sys::LV2_ATOM__String;
100}
101
102impl<'a, 'b> Atom<'a, 'b> for String
103where
104    'a: 'b,
105{
106    type ReadParameter = ();
107    type ReadHandle = &'a str;
108    type WriteParameter = ();
109    type WriteHandle = StringWriter<'a, 'b>;
110
111    fn read(body: Space<'a>, _: ()) -> Option<&'a str> {
112        body.data()
113            .and_then(|data| std::str::from_utf8(data).ok())
114            .map(|string| &string[..string.len() - 1]) // removing the null-terminator
115    }
116
117    fn init(frame: FramedMutSpace<'a, 'b>, _: ()) -> Option<StringWriter<'a, 'b>> {
118        Some(StringWriter { frame })
119    }
120}
121
122/// Handle to append strings to a string or literal.
123pub struct StringWriter<'a, 'b> {
124    frame: FramedMutSpace<'a, 'b>,
125}
126
127impl<'a, 'b> StringWriter<'a, 'b> {
128    /// Append a string.
129    ///
130    /// This method copies the given string to the end of the string atom/literal and then returns a mutable reference to the copy.
131    ///
132    /// If the internal space for the atom is not big enough, this method returns `None`.
133    pub fn append(&mut self, string: &str) -> Option<&mut str> {
134        let data = string.as_bytes();
135        let space = self.frame.write_raw(data, false)?;
136        unsafe { Some(std::str::from_utf8_unchecked_mut(space)) }
137    }
138}
139
140impl<'a, 'b> Drop for StringWriter<'a, 'b> {
141    fn drop(&mut self) {
142        // Null terminator.
143        (&mut self.frame as &mut dyn MutSpace).write(&0u8, false);
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use crate::prelude::*;
150    use crate::space::*;
151    use std::ffi::CStr;
152    use std::mem::{size_of, size_of_val};
153    use urid::*;
154
155    struct German;
156    unsafe impl UriBound for German {
157        const URI: &'static [u8] = b"http://lexvo.org/id/iso639-1/de\0";
158    }
159
160    #[derive(URIDCollection)]
161    pub struct TestURIDs {
162        atom: AtomURIDCollection,
163        german: URID<German>,
164    }
165
166    const SAMPLE0: &str = "Da steh ich nun, ich armer Tor! ";
167    const SAMPLE1: &str = "Und bin so klug als wie zuvor;";
168
169    #[test]
170    fn test_literal() {
171        let map = HashURIDMapper::new();
172        let urids = TestURIDs::from_map(&map).unwrap();
173
174        let mut raw_space: Box<[u8]> = Box::new([0; 256]);
175
176        // writing
177        {
178            let mut space = RootMutSpace::new(raw_space.as_mut());
179            let mut writer = (&mut space as &mut dyn MutSpace)
180                .init(
181                    urids.atom.literal,
182                    LiteralInfo::Language(urids.german.into_general()),
183                )
184                .unwrap();
185            writer.append(SAMPLE0).unwrap();
186            writer.append(SAMPLE1).unwrap();
187        }
188
189        // verifying
190        {
191            let (atom, space) = raw_space.split_at(size_of::<sys::LV2_Atom_Literal>());
192
193            let literal = unsafe { &*(atom.as_ptr() as *const sys::LV2_Atom_Literal) };
194            assert_eq!(literal.atom.type_, urids.atom.literal.get());
195            assert_eq!(
196                literal.atom.size as usize,
197                size_of::<sys::LV2_Atom_Literal_Body>()
198                    + size_of_val(SAMPLE0)
199                    + size_of_val(SAMPLE1)
200                    + 1
201            );
202            assert_eq!(literal.body.lang, urids.german.get());
203            assert_eq!(literal.body.datatype, 0);
204
205            let size = literal.atom.size as usize - size_of::<sys::LV2_Atom_Literal_Body>();
206            let string = CStr::from_bytes_with_nul(space.split_at(size).0)
207                .unwrap()
208                .to_str()
209                .unwrap();
210            assert_eq!(SAMPLE0.to_owned() + SAMPLE1, string);
211        }
212
213        // reading
214        {
215            let space = Space::from_slice(raw_space.as_ref());
216            let (body, _) = space.split_atom_body(urids.atom.literal).unwrap();
217            let (info, text) = Literal::read(body, ()).unwrap();
218
219            assert_eq!(info, LiteralInfo::Language(urids.german.into_general()));
220            assert_eq!(text, SAMPLE0.to_owned() + SAMPLE1);
221        }
222    }
223
224    #[test]
225    fn test_string() {
226        let map = HashURIDMapper::new();
227        let urids = crate::AtomURIDCollection::from_map(&map).unwrap();
228
229        let mut raw_space: Box<[u8]> = Box::new([0; 256]);
230
231        // writing
232        {
233            let mut space = RootMutSpace::new(raw_space.as_mut());
234            let mut writer = (&mut space as &mut dyn MutSpace)
235                .init(urids.string, ())
236                .unwrap();
237            writer.append(SAMPLE0).unwrap();
238            writer.append(SAMPLE1).unwrap();
239        }
240
241        // verifying
242        {
243            let (string, space) = raw_space.split_at(size_of::<sys::LV2_Atom_String>());
244
245            let string = unsafe { &*(string.as_ptr() as *const sys::LV2_Atom_String) };
246            assert_eq!(string.atom.type_, urids.string);
247            assert_eq!(string.atom.size as usize, SAMPLE0.len() + SAMPLE1.len() + 1);
248
249            let string = std::str::from_utf8(space.split_at(string.atom.size as usize).0).unwrap();
250            assert_eq!(string[..string.len() - 1], SAMPLE0.to_owned() + SAMPLE1);
251        }
252
253        // reading
254        {
255            let space = Space::from_slice(raw_space.as_ref());
256            let (body, _) = space.split_atom_body(urids.string).unwrap();
257            let string = String::read(body, ()).unwrap();
258            assert_eq!(string, SAMPLE0.to_owned() + SAMPLE1);
259        }
260    }
261}