diff options
Diffstat (limited to 'rfc2822/src/component.rs')
-rw-r--r-- | rfc2822/src/component.rs | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/rfc2822/src/component.rs b/rfc2822/src/component.rs new file mode 100644 index 00000000..808f497f --- /dev/null +++ b/rfc2822/src/component.rs @@ -0,0 +1,212 @@ +/// A UserID value typically looks something like: +/// +/// Text (Comment) <name@example.org> +/// +/// That is, it contains three components: a text string, a comment, +/// and an email address. +/// +/// The actual format allows for lots of interleaved comments and +/// multiple texts. Thus, when parsing we build up a vector of +/// Components in the order that they were encountered. +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Component { + // A text string. + Text(String), + // A comment. + // + // The outermost parens are removed. That is, if the comment is: + // "(foo(bar)bam)", then "foo(bar)bam" is stored. + Comment(String), + // An email address. + Address(String), + // White space. + WS, +} + +impl From<Component> for Vec<Component> { + fn from(c: Component) -> Self { + vec![c] + } +} + +impl From<Component> for Option<Vec<Component>> { + fn from(c: Component) -> Self { + Some(vec![c]) + } +} + +// Collect the `Component`s to the vector `v`. +// +// The Components can be anything that can be turned into an +// Option<Vec<Component>>. This currently includes `Component`, and +// `Vec<Component>`. +macro_rules! components_concat_into { + ( $v:expr, $c:expr ) => {{ + let v: &mut Vec<Component> = $v; + let c : Option<Vec<Component>> = $c.into(); + if let Some(mut c) = c { + // If v ends in a WS and c starts with a WS, then collapse + // them. + if destructures_to!(Some(Component::WS) = v.last()) + && destructures_to!(Some(Component::WS) = c.first()) + { + v.pop(); + } + v.append(&mut c); + } + }}; + ( $v:expr, $car:expr, $($cdr:expr),* ) => {{ + let v: &mut Vec<Component> = $v; + let car : Option<Vec<Component>> = $car.into(); + if let Some(mut car) = car { + if destructures_to!(Some(Component::WS) = v.last()) + && destructures_to!(Some(Component::WS) = car.first()) + { + v.pop(); + } + v.append(&mut car) + } + components_concat_into!(v, $($cdr),*); + }}; +} + +// Collect the `Component`s into a vector `v`. +// +// The Components can be anything that can be turned into an +// Option<Vec<Component>>. This currently includes `Component`, and +// `Vec<Component>`. +macro_rules! components_concat { + ( $( $args:expr ),*) => {{ + let mut v : Vec<Component> = Vec::new(); + components_concat_into!(&mut v, $($args),*); + v + }}; +} + +// Kills leading (`left`) and/or trailing (`right`) whitespace +// (`Component::WS`). +pub(crate) fn components_kill_ws(v: Option<Vec<Component>>, + left: bool, right: bool) + -> Vec<Component> +{ + tracer!(::TRACE, "components_kill_ws"); + t!("v: {:?}, left: {}, right: {}", v, left, right); + + let v = if let Some(mut v) = v { + if v.len() > 0 && right { + let mut kill = false; + if let Component::WS = v[v.len() - 1] { + kill = true; + } + if kill { + v.pop(); + } + } + if v.len() > 0 && left { + let mut kill = false; + if let Component::WS = v[0] { + kill = true; + } + if kill { + v.remove(0); + } + } + v + } else { + vec![] + }; + t!("=> {:?}", v); + v +} + +// Merge the components in the vector. +pub(crate) fn components_merge(components: Vec<Component>) + -> Vec<Component> +{ + tracer!(::TRACE, "components_merge", 0); + t!("{:?}", components); + + let mut iter = components.into_iter(); + let mut components = vec![]; + + let mut left = if let Some(left) = iter.next() { + left + } else { + return components; + }; + let mut middleo = iter.next(); + let mut righto = iter.next(); + + while let Some(mut middle) = middleo { + enum Kill { + None, + Middle, + MiddleRight, + Right, + }; + let mut kill = Kill::None; + + match (&mut left, &mut middle, righto.as_mut()) { + (Component::Text(ref mut l), + Component::Text(ref mut m), + _) => { + t!("Merging '{}' and '{}'", l, m); + l.push_str(m); + kill = Kill::Middle; + }, + + (Component::Text(ref mut l), + Component::WS, + Some(Component::Text(ref mut r))) => { + t!("Merging '{}', WS and '{}'", l, r); + l.push(' '); + l.push_str(r); + kill = Kill::MiddleRight; + }, + (_, + Component::WS, + Some(Component::WS)) => { + // This can happen when we have a local-part of the + // following form: + // + // (comment) foo (comment) + // + // The local-part is produced by the dot_atom_left + // production, which puts the dot_atom_text (foo) to + // the right: + // + // COMMENT WS WS COMMENT TEXT + kill = Kill::Right; + }, + _ => (), + } + + match kill { + Kill::Middle => { + middleo = righto; + righto = iter.next(); + } + Kill::MiddleRight => { + middleo = iter.next(); + righto = iter.next(); + } + Kill::Right => { + middleo = Some(middle); + righto = iter.next(); + } + Kill::None => { + components.push(left); + left = middle; + middleo = righto; + righto = iter.next(); + } + } + } + + components.push(left); + if let Some(middle) = middleo { + components.push(middle); + } + + components +} |