summaryrefslogtreecommitdiffstats
path: root/rfc2822/src/component.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rfc2822/src/component.rs')
-rw-r--r--rfc2822/src/component.rs212
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
+}