use std::collections::HashMap;
const DEFAULT_ESCAPE_BYTE : u8 = b'%';
pub(crate) struct Spec {
expansions: HashMap<u8, Vec<u8>>,
escape: u8,
}
impl Spec {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn with_escape(escape: u8) -> Self {
Self { escape, .. Self::new() }
}
pub(crate) fn replace<T: Into<Vec<u8>>>(&mut self, literal: u8, replacement: T) {
let _ = self.expansions.insert(literal, replacement.into());
}
pub(crate) fn expand(&self, template: &[u8]) -> Vec<u8> {
let mut result = Vec::with_capacity(template.len());
let mut iter = template.iter().cloned();
while iter.len() != 0 {
result.extend(
iter.by_ref().take_while(|b| *b != self.escape )
);
let byte = match iter.next() {
Some(b) => b,
None => break,
};
match self.expansions.get(&byte) {
Some(expansion) => result.extend_from_slice(expansion),
None => result.push(byte),
};
}
result
}
}
impl Default for Spec {
fn default() -> Self {
Self {
expansions: HashMap::new(),
escape: DEFAULT_ESCAPE_BYTE,
}
}
}
impl From<HashMap<u8, Vec<u8>>> for Spec {
fn from(expansions: HashMap<u8, Vec<u8>>) -> Self {
Self { expansions, .. Self::new() }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new() {
let spec = Spec::new();
assert_eq!(DEFAULT_ESCAPE_BYTE, spec.escape);
}
#[test]
fn with_escape() {
let spec = Spec::with_escape(b'\\');
assert_eq!(b'\\', spec.escape);
}
#[test]
fn from_hashmap() {
let mut map = HashMap::new();
let _ = map.insert(b'x', b"abc".to_vec());
let spec = Spec::from(map.clone());
assert_eq!(map, spec.expansions);
}
#[test]
fn no_expansions() {
let spec = Spec::new();
let template = b"this has no expansions";
assert_eq!(
template[..],
spec.expand(template)[..]
);
}
#[test]
fn expansions() {
let mut spec = Spec::new();
let template = b"a: %a, b: %b";
let _ = spec.replace(b'a', &b"foo"[..]);
let _ = spec.replace(b'b', &b"bar"[..]);
assert_eq!(
b"a: foo, b: bar"[..],
spec.expand(template)[..],
);
}
#[test]
fn repeated_expansions() {
let mut spec = Spec::new();
let template = b"%a%a%a%b%a%a%b";
let _ = spec.replace(b'a', &b"x"[..]);
let _ = spec.replace(b'b', &b"y"[..]);
assert_eq!(
b"xxxyxxy"[..],
spec.expand(template)[..],
);
}
#[test]
fn expansion_inserts_itself() {
let mut spec = Spec::new();
let template = b"test %x test";
spec.replace(b'x', &b"x"[..]);
assert_eq!(
b"test x test"[..],
spec.expand(template)[..],
);
}
#[test]
fn expansion_isnt_recursive() {
let mut spec = Spec::new();
let template = b"test %x test";
spec.replace(b'x', &b"%x %y %z % %%"[..]);
spec.replace(b'y', &b"BUG"[..]);
assert_eq!(
b"test %x %y %z % %% test"[..],
spec.expand(template)[..],
);
}
#[test]
fn expansion_inserts_nothing() {
let mut spec = Spec::new();
let template = b"test %X test";
spec.replace(b'X', &b""[..]);
assert_eq!(
b"test test"[..],
spec.expand(template)[..],
);
}
#[test]
fn unused_expansions() {
let mut spec = Spec::new();
let template = b"only y should be expanded %y";
spec.replace(b'y', &b"qwerty"[..]);
spec.replace(b'n', &b"uiop["[..]);
assert_eq!(
b"only y should be expanded qwerty"[..],
spec.expand(template)[..],
);
}
#[test]
fn literals() {
let mut spec = Spec::new();
let template = b"a: %a, b: %b";
spec.replace(b'b', &b"bar"[..]);
assert_eq!(
b"a: a, b: bar"[..],
spec.expand(template)[..],
);
}
#[test]
fn literal_escape_character() {
let spec = Spec::new();
let template = b"%%%%%%%%%%%%%%";
assert_eq!(
b"%%%%%%%"[..],
spec.expand(template)[..],
);
}
#[test]
fn bug_wontfix_expand_escape_character() {
let mut spec = Spec::new();
let template = b"|%%|";
spec.replace(b'%', &b"x"[..]);
assert_eq!(
b"|x|"[..],
spec.expand(template)[..]
);
}
#[test]
fn bug_wontfix_swallow_trailing_escape_character() {
let spec = Spec::new();
let template = b"some text%";
assert_eq!(
b"some text"[..],
spec.expand(template)[..]
);
}
}