nettext/src/enc/mod.rs

186 lines
4.7 KiB
Rust
Raw Normal View History

2022-11-17 21:53:36 +00:00
//! Functions to generate nettext representations of data structures
2022-11-17 16:55:50 +00:00
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<u8>),
Dict(HashMap<&'a [u8], T<'a>>),
List(Vec<T<'a>>),
}
#[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<Term<'_>, 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<Term<'_>, 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<Item = Term<'a>>>(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<Item = (&'a str, Term<'a>)>>(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<Term<'a>, 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<u8> {
let mut buf = Vec::with_capacity(128);
encode_aux(&mut buf, &t.0, 0);
buf
}
fn encode_aux<'a>(buf: &mut Vec<u8>, 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::<Vec<_>>();
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);
}
}