preftool 0.1.0

Configuration library for CLI tools/servers.
Documentation
use crate::*;
use std::fmt;
use std::ops::Deref;
use std::sync::Arc;

#[derive(Default)]
pub struct ConfigBuilder {
  providers: Vec<Box<dyn ConfigurationProvider>>,
}

impl ConfigBuilder {
  pub fn new() -> Self {
    Self {
      providers: Vec::new(),
    }
  }
}

struct ConfigRoot {
  providers: Vec<Box<dyn ConfigurationProvider>>,
}

#[derive(Clone)]
pub struct Config {
  root: Arc<ConfigRoot>,
}

impl fmt::Debug for Config {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    fn debug_build<'a, 'b, C: Configuration>(config: &C, builder: &mut fmt::DebugMap<'a, 'b>) {
      for s in config.sections() {
        match s.value() {
          None => {}
          Some(v) => {
            builder.entry(&s.path(), &v);
          }
        }

        debug_build(&s, builder);
      }
    }

    let mut m = f.debug_map();
    debug_build(self, &mut m);
    m.finish()
  }
}

trait ConfigExt: Configuration {
  fn child_sections(&self, key: &ConfigKey) -> Self::Sections;
  fn get_owned_section(&self, key: ConfigKey) -> Self::Section;
}

#[doc(hidden)]
impl Configuration for Arc<ConfigRoot> {
  type Section = ConfigSection;
  type Sections = std::vec::IntoIter<Self::Section>;

  fn get<K: Into<ConfigKey>>(&self, key: K) -> Option<&str> {
    self.deref().get(&key.into())
  }

  fn get_section<K: Into<ConfigKey>>(&self, key: K) -> Self::Section {
    let path = key.into();
    let key = path.section_key();
    let value = self.get(&path).map(ToOwned::to_owned);
    let root = self.clone();

    ConfigSection {
      root,
      key,
      path,
      value,
    }
  }

  fn sections(&self) -> Self::Sections {
    self.child_sections(&ConfigKey::empty())
  }
}

#[doc(hidden)]
impl ConfigExt for Arc<ConfigRoot> {
  fn get_owned_section(&self, path: ConfigKey) -> Self::Section {
    let key = path.section_key();
    let value = self.get(&path).map(ToOwned::to_owned);
    let root = self.clone();

    ConfigSection {
      root,
      key,
      path,
      value,
    }
  }

  fn child_sections(&self, key: &ConfigKey) -> Self::Sections {
    let mut section_names = HashSet::new();
    for provider in self.providers.iter() {
      provider.get_child_keys(&key, &mut section_names);
    }

    let collected: Vec<_> = section_names
      .into_iter()
      .map(|name| {
        let path = key.combine(name);
        self.get_owned_section(path)
      })
      .collect();

    collected.into_iter()
  }
}

#[derive(Clone)]
pub struct ConfigSection {
  root: Arc<ConfigRoot>,
  key: ConfigKey,
  path: ConfigKey,
  value: Option<String>,
}

impl ConfigRoot {
  fn get(&self, key: &ConfigKey) -> Option<&str> {
    for provider in self.providers.iter() {
      let value = provider.try_get(&key);
      if value.is_some() {
        return value;
      }
    }

    None
  }
}

impl ConfigurationBuilder for ConfigBuilder {
  type Config = Config;

  fn push_provider<P: ConfigurationProvider + 'static>(mut self, source: P) -> Self {
    self.providers.push(Box::new(source));
    self
  }

  fn build(self) -> std::io::Result<Self::Config> {
    Ok(Config::new(self.providers))
  }
}

impl Config {
  fn new(providers: Vec<Box<dyn ConfigurationProvider>>) -> Self {
    let root = ConfigRoot { providers };
    let root = Arc::new(root);
    Config { root }
  }
}

impl Configuration for ConfigSection {
  type Section = ConfigSection;
  type Sections = std::vec::IntoIter<Self::Section>;

  #[inline]
  fn get<K: Into<ConfigKey>>(&self, key: K) -> Option<&str> {
    let key = self.path().combine(key);
    self.root.get(key.as_ref())
  }

  #[inline]
  fn get_section<K: Into<ConfigKey>>(&self, key: K) -> Self::Section {
    let key = self.path().combine(key);
    self.root.get_owned_section(key)
  }

  #[inline]
  fn sections(&self) -> Self::Sections {
    self.root.child_sections(self.key())
  }
}

impl ConfigurationSection for ConfigSection {
  fn key(&self) -> &ConfigKey {
    &self.key
  }

  fn path(&self) -> &ConfigKey {
    &self.path
  }

  fn value(&self) -> Option<&str> {
    match &self.value {
      None => None,
      Some(s) => Some(s.as_ref()),
    }
  }
}

impl Configuration for Config {
  type Section = ConfigSection;
  type Sections = std::vec::IntoIter<Self::Section>;

  fn get<K: Into<ConfigKey>>(&self, key: K) -> Option<&str> {
    self.root.get(key)
  }

  fn get_section<K: Into<ConfigKey>>(&self, key: K) -> Self::Section {
    self.root.get_section(key)
  }

  fn sections(&self) -> Self::Sections {
    self.root.sections()
  }
}