From ed9be64e541c214b9f3b910df77792343c039e15 Mon Sep 17 00:00:00 2001
From: Gonne <gonne@mathebau.de>
Date: Sun, 3 Nov 2024 08:46:55 +0100
Subject: [PATCH] Generate sieve script from hashmap

---
 packages/alias-to-sieve/main.rs | 117 +++++++++++++++++---------------
 1 file changed, 61 insertions(+), 56 deletions(-)

diff --git a/packages/alias-to-sieve/main.rs b/packages/alias-to-sieve/main.rs
index f4279ee..cd31b7a 100644
--- a/packages/alias-to-sieve/main.rs
+++ b/packages/alias-to-sieve/main.rs
@@ -1,69 +1,74 @@
-use std::fs::File;
-use std::io::{self, BufRead};
-use std::path::Path;
-use std::env;
+use std::io::{self};
 use std::collections::HashMap;
 
 fn main() {
-    let args: Vec<String> = env::args().collect();
+    let redirects = parse_alias_to_hashmap();
+    let sieve_script = generate_sieve_script(redirects);
+    println!("{}", sieve_script);
+}
 
-    let file_path = &args[1];
-
-    // File must exist in the current path
-    if let Ok(lines) = read_lines(file_path) {
-        // Consumes the iterator, returns an (Optional) String
-        let mut redirect_map : HashMap<String, Vec<String>> = HashMap::new();
-        let mut destinations : Vec<String> = Vec::new();
-        for line in lines.flatten() {
-            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 == "" {
-                continue;
-            }
-            let redirects: Vec<String> = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(", ")
-              .map(|address| to_mailaddress(address)).collect();
-            if redirects.len() == 0 {
-                continue;
-            }
-            destinations.push(to_mailaddress(destination));
-            redirect_map.insert(to_mailaddress(destination), redirects);
-        }
-        let mut changed = true; 
-        while changed {
-            changed = false;
-            let mut all_new_redirects : HashMap<String, Vec<String>> = HashMap::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().map(|x| x.clone())));
-                    }
+fn generate_sieve_script(redirects: HashMap<String, Vec<String>>) -> String {
+    let mut script : String = "require [\"envelope\", \"copy\"];\n".to_string();
+    for (redirect, destinations) in redirects {
+        script +=  format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect,
+            (|| {
+                let mut subscript : String = "".to_string();
+                for destination in destinations.iter().rev().skip(1).rev() {
+                    subscript += format!("  redirect :copy \"{}\";\n", destination).as_str();
+                }
+                subscript += format!("  redirect \"{}\";\n", destinations.iter().rev().next().unwrap()).as_str();
+                return subscript;
+            })()
+            ).as_str();
+    }
+    return  script;
+}
+
+fn parse_alias_to_hashmap() -> HashMap<String, Vec<String>> {
+    // File must exist in the current path
+    let mut redirect_map : HashMap<String, Vec<String>> = HashMap::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 == "" {
+            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(|address| to_mailaddress(address)).collect();
+        if redirects.len() == 0 {
+            continue;
+        }
+        destinations.push(to_mailaddress(destination));
+        redirect_map.insert(to_mailaddress(destination), redirects);
+    }
+    let mut changed = true; 
+    while changed {
+        changed = false;
+        let mut all_new_redirects : HashMap<String, Vec<String>> = HashMap::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().map(|x| x.clone())));
                 }
-                
-            }
-            
-            for (destination, new_redirect) in all_new_redirects {
-                *redirect_map.get_mut(&destination).unwrap() = new_redirect;
             }
         }
-        println!("{:#?}", redirect_map);
+        for (destination, new_redirect) in all_new_redirects {
+            *redirect_map.get_mut(&destination).unwrap() = new_redirect;
+        }
     }
+    return redirect_map;
 }
 
 fn to_mailaddress(local_part: &str) -> String {
-    let local_part = local_part.trim();
-    if local_part.contains("@") {
-        return String::from(local_part);
+    let mut addr = local_part.trim().to_string();
+    addr = addr.replace(",", "");
+    if addr.contains("@") {
+        return addr;
     }
-    return local_part.to_string() + "@mathebau.de";
+    return addr + "@mathebau.de";
 }
-
-// The output is wrapped in a Result to allow matching on errors.
-// Returns an Iterator to the Reader of the lines of the file.
-fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
-where P: AsRef<Path>, {
-    let file = File::open(filename)?;
-    Ok(io::BufReader::new(file).lines())
-}
\ No newline at end of file