forked from Fachschaft/nixConfig
88 lines
No EOL
3.5 KiB
Rust
88 lines
No EOL
3.5 KiB
Rust
use std::io::{self};
|
|
use std::collections::BTreeMap;
|
|
use std::env;
|
|
|
|
fn main() {
|
|
let args: Vec<String> = 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, Vec<String>>) -> 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<String, Vec<String>> {
|
|
// File must exist in the current path
|
|
let mut redirect_map : BTreeMap<String, Vec<String>> = BTreeMap::new();
|
|
let mut destinations : Vec<String> = 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<String> = 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<String, Vec<String>> = 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");
|
|
} |