ellidri 2.3.0

Your kawaii IRC server
Documentation
use std::{fs, path, str};
use std::collections::{BTreeMap, HashMap};
use std::ops::Range;
use super::{Binding, db, Error, Result, SaslBackend};

fn rangestr(inner: &str, outer: &str) -> Range<usize> {
    let ilen = inner.len();
    let inner = inner.as_ptr() as usize;
    let outer = outer.as_ptr() as usize;
    let offset = inner - outer;
    offset..offset + ilen
}

pub struct ModeString(pub String);
pub struct Oper(pub String, pub String);

pub trait TypeName {
    fn type_name() -> String;
}

impl TypeName for bool {
    fn type_name() -> String { "\"true\" or \"false\"".to_owned() }
}

impl TypeName for usize {
    fn type_name() -> String { "a positive integer".to_owned() }
}

impl TypeName for String {
    fn type_name() -> String { "a string".to_owned() }
}

impl TypeName for Binding {
    fn type_name() -> String { "following the format \"bind_to <address> [path]\"".to_owned() }
}

impl TypeName for ModeString {
    fn type_name() -> String { "a valid mode string".to_owned() }
}

impl TypeName for Oper {
    fn type_name() -> String { "following the format \"oper <name> <password>\"".to_owned() }
}

impl TypeName for SaslBackend {
    fn type_name() -> String { "\"none\" or \"db\"".to_owned() }
}

impl TypeName for db::Driver {
    fn type_name() -> String {
        if cfg!(all(feature = "sqlite", feature = "postgres")) {
            "\"sqlite\" or \"postgres\"".to_owned()
        } else if cfg!(feature = "sqlite") {
            "\"sqlite\"".to_owned()
        } else if cfg!(feature = "postgres") {
            "\"postgres\"".to_owned()
        } else {
            "omitted, ellidri hasn't been built with database support".to_owned()
        }
    }
}

impl TypeName for db::Url {
    fn type_name() -> String {
        if cfg!(all(feature = "sqlite", feature = "postgres")) {
            "an url like \"sqlite://...\" or \"postgres://...\"".to_owned()
        } else if cfg!(feature = "sqlite") {
            "an url like \"sqlite://...\"".to_owned()
        } else if cfg!(feature = "postgres") {
            "an url like \"postgres://...\"".to_owned()
        } else {
            "omitted, ellidri hasn't been built with database support".to_owned()
        }
    }
}

impl str::FromStr for SaslBackend {
    type Err = ();

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            "none" => Ok(Self::None),
            "db" => Ok(Self::Database),
            _ => Err(()),
        }
    }
}

impl str::FromStr for db::Driver {
    type Err = ();

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            #[cfg(feature = "sqlite")]
            "sqlite" => Ok(Self::Sqlite),
            #[cfg(feature = "postgres")]
            "postgres" | "postgresql" | "psql" => Ok(Self::Postgres),
            _ => Err(()),
        }
    }
}

impl str::FromStr for db::Url {
    type Err = ();

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        let mut split = s.splitn(2, "://");
        let driver = split.next().ok_or(())?.parse()?;
        let url = split.next().ok_or(())?;
        Ok(db::Url(driver, url.to_owned()))
    }
}

impl str::FromStr for ModeString {
    type Err = ();

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        if crate::modes::is_channel_mode_string(s) {
            Ok(ModeString(s.to_owned()))
        } else {
            Err(())
        }
    }
}

impl str::FromStr for Oper {
    type Err = ();

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        let mut words = s.split_whitespace();
        let name = words.next().ok_or(())?;
        let pass = words.next().ok_or(())?;
        Ok(Oper(name.to_owned(), pass.to_owned()))
    }
}

impl str::FromStr for Binding {
    type Err = ();

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        let mut words = s.splitn(2, char::is_whitespace).map(str::trim);
        let address = words.next().ok_or(())?
            .parse().map_err(|_| ())?;
        let tls_identity = words.next()
            .map(|word| word.parse().map_err(|_| ()))
            .transpose()?;
        Ok(Binding { address, tls_identity })
    }
}

#[derive(Default, Debug)]
pub struct Parser {
    lines: Vec<String>,
    settings: BTreeMap<usize, Setting>,
    occurences: HashMap<String, Vec<usize>>,
}

#[derive(Debug, Clone)]
pub struct Setting {
    pub lineno: usize,
    pub krange: Range<usize>,
    pub vrange: Range<usize>,
}

impl Parser {
    pub fn read<P>(path: P) -> Result<Self>
        where P: AsRef<path::Path>
    {
        let lines = fs::read_to_string(path)?.lines().map(str::to_owned).collect();
        let mut res = Self { lines, ..Self::default() };

        for (lineno, line) in res.lines.iter().enumerate() {
            let mut split = line.splitn(2, ' ').map(str::trim).filter(|s| !s.is_empty());

            let key = if let Some(key) = split.next() {key} else {continue};
            if key.starts_with('#') { continue; }
            let krange = rangestr(key, line);

            let value = if let Some(value) = split.next() {
                value
            } else {
                return Err(res.error(lineno, krange, "this setting has no value"));
            };
            let vrange = rangestr(value, line);

            res.settings.insert(lineno, Setting { lineno, krange, vrange });
            res.occurences.entry(key.to_owned()).or_default().push(lineno);
        }

        Ok(res)
    }

    pub fn unique_setting<S, F>(mut self, key: &str, required: bool, and_then: F) -> Result<Self>
        where S: str::FromStr + TypeName,
              F: FnOnce(S),
    {
        if let Some(occ) = self.occurences.get(key) {
            if occ.is_empty() {
                unreachable!("occurences must not have empty Vecs");
            }
            if occ.len() > 1 {
                let last = *occ.last().unwrap();
                let setting = &self.settings[&last];
                let krange = setting.krange.clone();
                let msg = format!("{:?} must not appear more than once. Specified at lines {:?}",
                                  key, occ);
                return Err(self.error(last, krange, msg));
            }
            let lineno = occ[0];
            let setting = &self.settings[&lineno];
            let value = match self.lines[lineno][setting.vrange.clone()].parse() {
                Ok(value) => value,
                Err(_) => {
                    let msg = format!("this setting must be {}", S::type_name());
                    let vrange = setting.vrange.clone();
                    return Err(self.error(lineno, vrange, msg));
                }
            };
            and_then(value);
        } else if required {
            return Err(Error::Format(self, None, 0..0, format!("missing setting {:?}", key)));
        }
        self.occurences.remove(key);
        Ok(self)
    }

    pub fn setting<S, F>(mut self, key: &str, and_then: F) -> Result<Self>
        where S: str::FromStr + TypeName,
              F: FnOnce(Vec<S>),
    {
        if let Some(occ) = self.occurences.get(key) {
            if occ.is_empty() {
                unreachable!("occurences must not have empty Vecs");
            }
            let mut res = Vec::new();
            for setting in occ.iter().map(|lno| self.settings[lno].clone()) {
                let value = match self.lines[setting.lineno][setting.vrange.clone()].parse() {
                    Ok(value) => value,
                    Err(_) => {
                        let msg = format!("this setting must be {}", S::type_name());
                        return Err(self.error(setting.lineno, setting.vrange, msg));
                    }
                };
                res.push(value);
            }
            and_then(res);
        }
        self.occurences.remove(key);
        Ok(self)
    }

    pub fn check_unknown_settings(self) -> Result<()> {
        if let Some((key, occ)) = self.occurences.iter().next() {
            if occ.is_empty() {
                unreachable!("occurences must not have empty Vecs");
            }
            let lineno = occ[0];
            let setting = &self.settings[&lineno];
            let krange = setting.krange.clone();
            let msg = format!("unknown setting {:?}", key);
            return Err(self.error(lineno, krange, msg));
        }
        Ok(())
    }

    pub fn error<S>(self, lineno: usize, col: Range<usize>, msg: S) -> Error
        where S: Into<String>
    {
        Error::Format(self, Some(lineno), col, msg.into())
    }

    pub fn lines(&self) -> impl Iterator<Item=&str> + '_ {
        self.lines.iter().map(String::as_ref)
    }
}