use std::collections::HashMap; use crate::dec::decode; use crate::{is_string_char, is_whitespace}; pub struct Term<'a>(T<'a>); enum T<'a> { Str(&'a [u8]), OwnedStr(Vec), Dict(HashMap<&'a [u8], T<'a>>), List(Vec>), } #[derive(Debug)] pub enum Error { InvalidCharacter(u8), InvalidRaw, NotADictionnary, } // ---- helpers to build terms ---- /// Encode a string (may contain whitespace) /// /// ``` /// use nettext::enc::*; /// /// assert_eq!(encode(&string("Hello world .").unwrap()), b"Hello world ."); /// ``` pub fn string(s: &str) -> Result, Error> { for c in s.as_bytes().iter() { if !(is_string_char(*c) || is_whitespace(*c)) { return Err(Error::InvalidCharacter(*c)); } } Ok(Term(T::Str(s.as_bytes()))) } /// Include a raw nettext value /// /// ``` /// use nettext::enc::*; /// /// assert_eq!(encode(&raw(b"Hello { a = b, c = d} .").unwrap()), b"Hello { a = b, c = d} ."); /// ``` pub fn raw(bytes: &[u8]) -> Result, Error> { if decode(bytes).is_err() { return Err(Error::InvalidRaw); } Ok(Term(T::Str(bytes))) } /// Encode a list of items /// /// ``` /// use nettext::enc::*; /// /// assert_eq!(encode(&list([ /// string("Hello").unwrap(), /// string("world").unwrap() /// ])), b"Hello world"); /// ``` pub fn list<'a, I: IntoIterator>>(terms: I) -> Term<'a> { Term(T::List(terms.into_iter().map(|x| x.0).collect())) } /// Encode a list of items /// /// ``` /// use nettext::enc::*; /// /// assert_eq!(encode(&dict([ /// ("a", string("Hello").unwrap()), /// ("b", string("world").unwrap()) /// ])), b"{\n a = Hello,\n b = world,\n}"); /// ``` pub fn dict<'a, I: IntoIterator)>>(pairs: I) -> Term<'a> { Term(T::Dict( pairs .into_iter() .map(|(k, v)| (k.as_bytes(), v.0)) .collect(), )) } impl<'a> Term<'a> { pub fn push(self, t: Term<'a>) -> Term<'a> { match self.0 { T::List(mut v) => { v.push(t.0); Term(T::List(v)) } x => Term(T::List(vec![x, t.0])), } } pub fn insert(self, k: &'a str, v: Term<'a>) -> Result, Error> { match self.0 { T::Dict(mut d) => { d.insert(k.as_bytes(), v.0); Ok(Term(T::Dict(d))) } _ => Err(Error::NotADictionnary), } } } // ---- encoding function ---- pub fn encode<'a>(t: &Term<'a>) -> Vec { let mut buf = Vec::with_capacity(128); encode_aux(&mut buf, &t.0, 0); buf } fn encode_aux<'a>(buf: &mut Vec, term: &T<'a>, indent: usize) { match term { T::Str(s) => buf.extend_from_slice(s), T::OwnedStr(s) => buf.extend_from_slice(&s), T::Dict(d) => { buf.extend_from_slice(b"{\n"); let indent2 = indent + 2; let mut keys = d.keys().collect::>(); keys.sort(); for k in keys { let v = d.get(k).unwrap(); for _ in 0..indent2 { buf.push(b' '); } buf.extend_from_slice(k); buf.extend_from_slice(b" = "); encode_aux(buf, v, indent2); buf.extend_from_slice(b",\n"); } for _ in 0..indent { buf.push(b' '); } buf.push(b'}'); } T::List(l) => { let indent2 = indent + 2; for (i, v) in l.iter().enumerate() { if buf.iter().rev().take_while(|c| **c != b'\n').count() > 80 { buf.push(b'\n'); for _ in 0..indent2 { buf.push(b' '); } } else if i > 0 { buf.push(b' '); } encode_aux(buf, v, indent2); } } } } #[cfg(test)] mod tests { use super::*; #[test] fn complex1() { let input = list([ string("HELLO").unwrap(), string("alexhelloworld").unwrap(), dict([ ("from", string("jxx").unwrap()), ("subject", string("hello").unwrap()), ("data", raw(b"{ f1 = plop, f2 = kuko }").unwrap()), ]), ]); let expected = b"HELLO alexhelloworld { data = { f1 = plop, f2 = kuko }, from = jxx, subject = hello, }"; let enc = encode(&input); eprintln!("{}", std::str::from_utf8(&enc).unwrap()); eprintln!("{}", std::str::from_utf8(&expected[..]).unwrap()); assert_eq!(encode(&input), expected); } }