diff --git a/Cargo.toml b/Cargo.toml index f9f5c5b..77b7e28 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,4 @@ rust-version = "1.68.2" [dependencies] fqdn = {version = "0.4.2", features = ["domain-label-length-limited-to-63", "domain-name-without-special-chars"]} -email-address-parser = "2.0.0" \ No newline at end of file +email-address-parser = "2.0.0" diff --git a/src/main.rs b/src/main.rs index 65eb210..547dbc0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,34 @@ use email_address_parser::EmailAddress; use fqdn::FQDN; -use std::collections::HashMap; +use std::cmp::Ordering; +use std::collections::BTreeMap; use std::env; use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; use std::str::FromStr; -#[derive(Debug)] struct AliasFile { content: io::Lines>, default_domain: FQDN, } -type AliasMap = HashMap>; +#[derive(PartialEq, Eq, Clone)] +struct OrdEmailAddress(EmailAddress); + +impl PartialOrd for OrdEmailAddress { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.0.to_string().cmp(&other.0.to_string())) + } +} + +impl Ord for OrdEmailAddress { + fn cmp(&self, other: &Self) -> Ordering { + self.0.to_string().cmp(&other.0.to_string()) + } +} + +type AliasMap = BTreeMap>; fn main() { let args: Vec = env::args().collect(); @@ -35,16 +50,17 @@ fn main() { fn generate_sieve_script(redirects: AliasMap) -> String { let mut script: String = "require [\"envelope\", \"copy\"];\n\n".to_string(); - for (redirect, destinations) in redirects { - script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, { + for (redirect, mut destinations) in redirects { + script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect.0, { let mut subscript: String = String::new(); + destinations.sort(); for destination in destinations.iter().rev().skip(1).rev() { - subscript += format!(" redirect :copy \"{destination}\";\n").as_str(); + subscript += format!(" redirect :copy \"{}\";\n", destination.0).as_str(); } subscript + format!( " redirect \"{}\";\n", - destinations.iter().next_back().unwrap() + destinations.iter().next_back().unwrap().0 ) .as_str() }) @@ -56,7 +72,7 @@ fn generate_sieve_script(redirects: AliasMap) -> String { fn parse_alias_to_map(alias_files: Vec) -> AliasMap { // File must exist in the current path let mut redirect_map: AliasMap = AliasMap::new(); - let mut destinations: Vec = Vec::new(); + let mut destinations: Vec = Vec::new(); for alias_file in alias_files { for line in alias_file.content { let line = line.unwrap(); @@ -65,7 +81,7 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { if destination.is_empty() { continue; } - let redirects: Vec = line + let redirects: Vec = line .split_at(line.find(char::is_whitespace).unwrap_or(0)) .1 .split(' ') @@ -107,13 +123,13 @@ fn parse_alias_to_map(alias_files: Vec) -> AliasMap { redirect_map } -fn to_mailaddress(local_part: &str, default_domain: &FQDN) -> EmailAddress { +fn to_mailaddress(local_part: &str, default_domain: &FQDN) -> OrdEmailAddress { let mut addr = local_part.trim().to_string(); addr = addr.replace(',', ""); if addr.contains('@') { - return EmailAddress::parse(&addr, None).unwrap(); + return OrdEmailAddress(EmailAddress::parse(&addr, None).unwrap()); } - EmailAddress::new(&addr, &default_domain.to_string(), None).unwrap() + OrdEmailAddress(EmailAddress::new(&addr, &default_domain.to_string(), None).unwrap()) } // The output is wrapped in a Result to allow matching on errors.