use std::io::{self}; use std::collections::BTreeMap; use std::env; fn main() { let args: Vec = env::args().collect(); if args.len() < 2 { print_help(); return; } let default_domain = &args[1]; let redirects = parse_alias_to_map(default_domain); let sieve_script = generate_sieve_script(redirects); println!("{}", sieve_script); } fn generate_sieve_script(redirects: BTreeMap>) -> String { let mut script : String = "require [\"envelope\", \"copy\"];\n\n".to_string(); for (redirect, mut destinations) in redirects { script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, { let mut subscript : String = "".to_string(); destinations.sort(); for destination in destinations.iter().rev().skip(1).rev() { subscript += format!(" redirect :copy \"{}\";\n", destination).as_str(); } subscript + format!(" redirect \"{}\";\n", destinations.iter().next_back().unwrap()).as_str() } ).as_str(); } script } fn parse_alias_to_map(default_domain : &str) -> BTreeMap> { // File must exist in the current path let mut redirect_map : BTreeMap> = BTreeMap::new(); let mut destinations : Vec = Vec::new(); for line in io::stdin().lines() { let line = line.unwrap(); let line = String::from(line.split_at(line.find("#").unwrap_or(line.len())).0); let destination = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).0; if destination.is_empty() { continue; } let redirects: Vec = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ") .filter(|address| address.trim().to_string().replace(",","") != "").map(|addr| to_mailaddress(addr, default_domain)).collect(); if redirects.is_empty() { continue; } destinations.push(to_mailaddress(destination, default_domain)); redirect_map.insert(to_mailaddress(destination, default_domain), redirects); } let mut changed = true; while changed { changed = false; let mut all_new_redirects : BTreeMap> = BTreeMap::new(); for destination in destinations.iter() { for forward_to in redirect_map.get(destination).unwrap().iter() { if let Some(new_redirects) = redirect_map.get(forward_to) { changed = true; all_new_redirects.entry(destination.clone()).or_insert(redirect_map.get(destination).unwrap().clone()) .retain(|dest| *dest != *forward_to); all_new_redirects.entry(destination.clone()).and_modify(|d| d.extend(new_redirects.iter().cloned())); } } } for (destination, new_redirect) in all_new_redirects { *redirect_map.get_mut(&destination).unwrap() = new_redirect; } } redirect_map } fn to_mailaddress(local_part: &str, default_domain : &str) -> String { let mut addr = local_part.trim().to_string(); addr = addr.replace(",", ""); if addr.contains("@") { return addr; } addr + "@" + default_domain } fn print_help(){ print!("Reads a virtual alias file from STDIN and needs a default domain to append to local paths, e.g. cat virt_aliases | ./alias_to_sieve example.com"); }