preftool 0.1.0

Configuration library for CLI tools/servers.
Documentation
use caseless::default_case_fold_str;
use std::fmt;
use std::hash::{Hash, Hasher};

const KEY_DELIMITER: &str = ":";

/// Represents a configuration key. Configuration keys are case-insensitive,
/// and all instances are lower-cased.
#[derive(Clone)]
pub struct ConfigKey(String);

impl ConfigKey {
  fn new() -> Self {
    Self(String::new())
  }

  /// Get an empty config key.
  pub fn empty() -> Self {
    Self::new()
  }

  pub fn is_empty(&self) -> bool {
    self.0.is_empty()
  }

  /// Key length.
  pub fn len(&self) -> usize {
    self.0.len()
  }

  /// Configuration key seperator.
  pub fn separator() -> &'static str {
    KEY_DELIMITER
  }

  /// Get the section key given a config path.
  ///
  /// # Arguments
  ///
  /// * `path` - configuration path.
  ///
  /// # Example
  ///
  /// ```
  /// use preftool::ConfigKey;
  /// assert_eq!(ConfigKey::from("").section_key(), "");
  /// assert_eq!(ConfigKey::from("foo").section_key(), "foo");
  /// assert_eq!(ConfigKey::from("foo:bar").section_key(), "bar");
  /// assert_eq!(ConfigKey::from("foo:bar:baz").section_key(), "baz");
  /// ```
  pub fn section_key(&self) -> Self {
    if self == "" {
      ConfigKey::new()
    } else {
      match self.0.rfind(KEY_DELIMITER) {
        None => self.clone(),
        Some(i) => Self(self.0[(i + 1)..].to_owned()),
      }
    }
  }

  /// Get the parent config path.
  ///
  /// # Arguments
  ///
  /// * `path` - configuration path.
  ///
  /// # Example
  ///
  /// ```
  /// use preftool::ConfigKey;
  /// assert_eq!(ConfigKey::from("").parent(), "");
  /// assert_eq!(ConfigKey::from("foo").parent(), "");
  /// assert_eq!(ConfigKey::from("foo:bar").parent(), "foo");
  /// assert_eq!(ConfigKey::from("foo:bar:baz").parent(), "foo:bar");
  /// ```
  pub fn parent(&self) -> Self {
    if self == "" {
      ConfigKey::new()
    } else {
      match self.0.rfind(KEY_DELIMITER) {
        None => ConfigKey::new(),
        Some(i) => Self(self.0[..i].to_owned()),
      }
    }
  }

  /// Combine two config path segments.
  ///
  /// # Arguments
  ///
  /// * `path` - configuration path.
  /// * `key` - configuration sub-key.
  ///
  /// # Example
  ///
  /// ```
  /// use preftool::ConfigKey;
  /// assert_eq!(ConfigKey::from("").combine(""), "");
  /// assert_eq!(ConfigKey::from("").combine("foo"), "foo");
  /// assert_eq!(ConfigKey::from("foo:bar").combine("baz"), "foo:bar:baz");
  /// assert_eq!(ConfigKey::from("foo").combine("bar:baz"), "foo:bar:baz");
  /// ```
  pub fn combine<S2: Into<ConfigKey>>(&self, key: S2) -> Self {
    let key = key.into();
    if self == "" {
      key
    } else {
      let mut s = String::with_capacity(self.len() + key.len() + KEY_DELIMITER.len());
      s.push_str(self.as_ref());
      s.push_str(KEY_DELIMITER);
      s.push_str(key.as_ref());
      Self(s)
    }
  }

  /// Join a list of path segments into one config path.
  ///
  /// # Arguments
  ///
  /// * `paths` - configuration path segments.
  ///
  /// # Example
  ///
  /// ```
  /// use preftool::ConfigKey;
  /// assert_eq!(ConfigKey::join::<&'static str, Vec<_>>(vec!()), "");
  /// assert_eq!(ConfigKey::join(vec!("foo")), "foo");
  /// assert_eq!(ConfigKey::join(vec!("", "foo", "")), "foo");
  /// assert_eq!(ConfigKey::join(vec!("", "foo:bar", "", "baz")), "foo:bar:baz");
  /// ```
  pub fn join<S: Into<ConfigKey>, I: IntoIterator<Item = S>>(parts: I) -> Self {
    // default that's normally enough
    let mut s = String::with_capacity(50);
    let mut first = true;
    for part in parts.into_iter().filter_map(|part| {
      let part = part.into();
      if part.as_ref() == "" {
        None
      } else {
        Some(part)
      }
    }) {
      if first {
        first = false;
      } else {
        s.push_str(KEY_DELIMITER);
      }
      s.push_str(part.as_ref());
    }

    Self(s)
  }

  /// Construct a config key from a string. This does not do any
  /// validation/lowercasing of the string, and should only be used
  /// if the string is guaranteed to be safe. This method is typically
  /// used by `derive` implementations.
  ///
  /// # Arguments
  ///
  /// * value - string value.
  ///
  /// # Example
  ///
  /// ```
  /// use preftool::ConfigKey;
  /// assert_eq!(ConfigKey::unsafe_from("foo:bar"), "foo:bar");
  /// ```
  pub fn unsafe_from<S: Into<String>>(value: S) -> Self {
    Self(value.into())
  }
}

impl fmt::Display for ConfigKey {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    fmt::Display::fmt(&self.0, f)
  }
}

impl fmt::Debug for ConfigKey {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    fmt::Debug::fmt(&self.0, f)
  }
}

impl AsRef<str> for ConfigKey {
  fn as_ref(&self) -> &str {
    &self.0
  }
}

impl Hash for ConfigKey {
  #[inline]
  fn hash<H: Hasher>(&self, hasher: &mut H) {
    self.0.hash(hasher)
  }
}

impl PartialEq for ConfigKey {
  #[inline]
  fn eq(&self, other: &Self) -> bool {
    self.0.eq(&other.0)
  }
}

impl PartialEq<String> for ConfigKey {
  fn eq(&self, other: &String) -> bool {
    self.eq(&ConfigKey::from(other))
  }
}

impl PartialEq<str> for ConfigKey {
  fn eq(&self, other: &str) -> bool {
    self.eq(&ConfigKey::from(other))
  }
}

impl PartialEq<&String> for ConfigKey {
  fn eq(&self, other: &&String) -> bool {
    self.eq(&ConfigKey::from(*other))
  }
}

impl PartialEq<&str> for ConfigKey {
  fn eq(&self, other: &&str) -> bool {
    self.eq(&ConfigKey::from(*other))
  }
}

impl Eq for ConfigKey {}

impl From<&str> for ConfigKey {
  fn from(value: &str) -> Self {
    ConfigKey(default_case_fold_str(value))
  }
}

impl From<&String> for ConfigKey {
  #[inline]
  fn from(value: &String) -> Self {
    let s: &str = &value;
    ConfigKey::from(s)
  }
}

impl From<String> for ConfigKey {
  #[inline]
  fn from(value: String) -> Self {
    let s: &str = &value;
    ConfigKey::from(s)
  }
}

impl From<&ConfigKey> for ConfigKey {
  #[inline]
  fn from(value: &ConfigKey) -> Self {
    value.clone()
  }
}

impl Into<String> for ConfigKey {
  fn into(self) -> String {
    self.0
  }
}