changeset 20:4914bd1e162f draft

Finish implementation of Config
author Lewin Bormann <lbo@spheniscida.de>
date Sat, 03 Dec 2016 15:45:27 +0100
parents c4be6edfffec
children eae478565a9f
files src/config.rs
diffstat 1 files changed, 208 insertions(+), 5 deletions(-) [+]
line wrap: on
line diff
--- a/src/config.rs	Sat Dec 03 14:41:35 2016 +0100
+++ b/src/config.rs	Sat Dec 03 15:45:27 2016 +0100
@@ -5,12 +5,62 @@
 use std::collections::btree_map::Entry;
 use std::default::Default;
 use std::net::SocketAddr;
+use std::str::FromStr;
 
 use dns_lookup;
 use toml;
 
 use priority;
 
+/// Parses strings like 0/1s/2m/3h/4d/5w and returns seconds. If no unit is there, seconds is
+/// assumed.
+fn parse_duration(s: &str) -> Option<u64> {
+    let s = s.trim();
+    let suffix = &s[s.len() - 1..];
+    let num_part = &s[0..s.len() - 1];
+
+    if suffix == "s" {
+        if let Ok(s) = u64::from_str(num_part) {
+            Some(s)
+        } else {
+            None
+        }
+    } else if suffix == "m" {
+        if let Ok(m) = u64::from_str(num_part) {
+            Some(m * 60)
+        } else {
+            None
+        }
+    } else if suffix == "h" {
+        if let Ok(h) = u64::from_str(num_part) {
+            Some(h * 60 * 60)
+        } else {
+            None
+        }
+    } else if suffix == "d" {
+        if let Ok(d) = u64::from_str(num_part) {
+            Some(d * 24 * 60 * 60)
+        } else {
+            None
+        }
+    } else if suffix == "w" {
+        if let Ok(w) = u64::from_str(num_part) {
+            Some(w * 7 * 24 * 60 * 60)
+        } else {
+            None
+        }
+    } else if let Ok(_) = u64::from_str(suffix) {
+        // no explicit unit
+        if let Ok(s) = u64::from_str(s) {
+            Some(s)
+        } else {
+            None
+        }
+    } else {
+        None
+    }
+}
+
 // helper functions for extracting values from TOML values
 fn get_string(tbl: &toml::Table, key: &str, default: String) -> String {
     if let Some(e) = tbl.get(key) {
@@ -30,6 +80,53 @@
     default
 }
 
+fn get_bool(tbl: &toml::Table, key: &str, default: bool) -> bool {
+    if let Some(e) = tbl.get(key) {
+        if let &toml::Value::Boolean(b) = e {
+            return b;
+        }
+    }
+    default
+}
+
+fn get_tbl<'a>(tbl: &'a toml::Table, key: &str) -> Option<&'a toml::Table> {
+    if let Some(e) = tbl.get(key) {
+        if let &toml::Value::Table(ref t) = e {
+            return Some(t);
+        }
+    }
+    None
+}
+
+/// Extract an array of tables.
+fn get_tbl_arr(tbl: &toml::Table, key: &str) -> Option<Vec<toml::Table>> {
+    if let Some(e) = tbl.get(key) {
+        if let &toml::Value::Array(ref a) = e {
+            let mut v = Vec::with_capacity(a.len());
+
+            for elem in a {
+                match elem {
+                    &toml::Value::Table(ref t) => v.push(t.clone()),
+                    _ => (),
+                }
+            }
+        }
+    }
+    None
+}
+
+fn get_tbls(tbl: &toml::Table) -> Option<Vec<(String, toml::Table)>> {
+    let mut res = Vec::with_capacity(tbl.len());
+
+    for (k, t) in tbl.iter() {
+        if let &toml::Value::Table(ref t) = t {
+            res.push((k.clone(), t.clone()));
+        }
+    }
+
+    Some(res)
+}
+
 fn tomlerr<T>(s: String) -> Result<T, toml::Error> {
     Err(toml::Error::Custom(s))
 }
@@ -42,6 +139,7 @@
     pub max_msg_len: usize,
 }
 
+/// Takes a "general" table
 fn assemble_general(t: &toml::Table) -> Result<General, toml::Error> {
     let default_bind_path = "/dev/log".to_string();
     let default_max_msg_len = 8192;
@@ -59,7 +157,8 @@
     addr: SocketAddr,
 }
 
-/// Assemble a Remote from a `remote` entry, and resolve the address if needed.
+/// Assemble a Remote from a `remote` entry, and resolve the address if needed; takes a "remote"
+/// table.
 fn assemble_remote(t: &toml::Table, name: &str) -> Result<Remote, toml::Error> {
     let addr = get_string(t, "addr", String::new());
     let port = get_int(t, "port", 0);
@@ -84,7 +183,7 @@
 
 fn assemble_compress_type(v: &toml::Value) -> Result<CompressType, toml::Error> {
     if let &toml::Value::String(ref s) = v {
-        if s == "none" {
+        if s == "none" || s == "" {
             return Ok(CompressType::NoCompression);
         } else if s == "gzip" {
             return Ok(CompressType::Gzip);
@@ -113,7 +212,19 @@
     compress: CompressType,
 }
 
-
+fn assemble_file(tbl: &toml::Table, name: &str) -> Result<File, toml::Error> {
+    let f = File {
+        name: name.to_string(),
+        location: get_string(tbl, "file", "".to_string()),
+        max_size: get_int(tbl, "max_size", 4 * 1024 * 1024) as usize,
+        max_age: parse_duration(&get_string(tbl, "max_age", "".to_string())).unwrap_or(0),
+        history: get_int(tbl, "history", 10) as i32,
+        compress: assemble_compress_type(tbl.get("compress")
+                .unwrap_or(&toml::Value::String("".to_string())))
+            .unwrap_or(CompressType::NoCompression),
+    };
+    Ok(f)
+}
 
 #[derive(Clone, Debug)]
 pub enum FacPattern {
@@ -165,6 +276,10 @@
     let mut matcher = Vec::with_capacity(3);
 
     for filter in s.trim().split(';') {
+        if filter.len() == 0 {
+            return Ok(matcher);
+        }
+
         match parse_filter(filter) {
             Ok(f) => matcher.push(f),
             Err(e) => return Err(e),
@@ -262,6 +377,24 @@
     stop: bool,
 }
 
+/// Takes a "rule" table
+fn assemble_rule(t: &toml::Table) -> Result<Rule, toml::Error> {
+    let pattern = get_string(t, "pattern", "".to_string());
+    let matcher = try!(parse_matcher(&pattern));
+
+    let file = get_string(t, "dest", "null".to_string());
+    let remote = get_string(t, "remote_dest", "".to_string());
+
+    let stop = get_bool(t, "stop", false);
+
+    Ok(Rule {
+        pattern: matcher,
+        file: file,
+        remote_dest: remote,
+        stop: stop,
+    })
+}
+
 #[derive(Clone,Default)]
 pub struct Config {
     pub general: General,
@@ -271,8 +404,54 @@
 }
 
 fn decode_config(text: &String) -> Result<Config, toml::Error> {
+    let mut cfg = Config::default();
+
     if let Some(tbl) = toml::Parser::new(&text).parse() {
-        let general = try!(assemble_general(&tbl));
+        if let Some(gen) = get_tbl(&tbl, "general") {
+            cfg.general = try!(assemble_general(&tbl));
+        } else {
+            return tomlerr("Could not find 'general' section".to_string());
+        }
+
+        // remotes: { remote1: { addr: xy, port: 12 }, remote2: { addr: xz, port: 34 } }
+        if let Some(remotes) = get_tbl(&tbl, "remotes") {
+            if let Some(remotes) = get_tbls(&remotes) {
+                for (name, remote) in remotes {
+                    match assemble_remote(&remote, &name) {
+                        Ok(remote) => {
+                            cfg.remotes.insert(name, remote);
+                        }
+                        Err(e) => return tomlerr(format!("Error setting up remotes: {}", e)),
+                    }
+                }
+            }
+        }
+
+        // files: { file1: { file: "/a/b/c" history: "1w" }, file2: { file: "/d/e/f", compress:
+        // "none" } }
+        if let Some(files) = get_tbl(&tbl, "files") {
+            if let Some(files) = get_tbls(&files) {
+                for (name, file) in files {
+                    match assemble_file(&file, &name) {
+                        Ok(file) => {
+                            cfg.files.insert(name, file);
+                        }
+                        Err(e) => return tomlerr(format!("Error setting up files: {}", e)),
+                    }
+                }
+            }
+        }
+
+        if let Some(rules) = get_tbl_arr(&tbl, "rules") {
+            for r in rules {
+                match assemble_rule(&r) {
+                    Ok(r) => {
+                        cfg.rules.push(r);
+                    }
+                    Err(e) => return tomlerr(format!("Error setting up rules: {}", e)),
+                }
+            }
+        }
 
         unimplemented!()
     } else {
@@ -294,6 +473,30 @@
 
         let m = "mail, news.info; kern.warn";
 
-        assert_eq!(format!("{:?}", super::parse_matcher(m)), "Ok([(MultiFacility([NEWS, LOCAL0]), Level(INFO)), (Facility(KERN), Level(WARNING))])");
+        assert_eq!(format!("{:?}", super::parse_matcher(m)),
+                   "Ok([(MultiFacility([NEWS, LOCAL0]), Level(INFO)), (Facility(KERN), \
+                    Level(WARNING))])");
+
+        assert_eq!(format!("{:?}", super::parse_matcher("")), "Ok([])");
+    }
+
+    #[test]
+    fn test_config_duration_parse() {
+        use super::parse_duration;
+
+        let cases = vec![("1", 1),
+                         ("10", 10),
+                         ("10s", 10),
+                         ("1m", 60),
+                         ("30m", 30 * 60),
+                         ("60m", 60 * 60),
+                         ("1h", 60 * 60),
+                         ("24h", 24 * 60 * 60),
+                         ("1d", 24 * 60 * 60),
+                         ("4w", 4 * 7 * 24 * 60 * 60)];
+
+        for (test, result) in cases {
+            assert_eq!(parse_duration(test), Some(result));
+        }
     }
 }