preftool-dirs 0.2.0

Configuration library for CLI tools/servers.
use std::env;
use std::path::PathBuf;

use crate::ConfigDirs;

pub fn config_dirs(application: &str, logger: slog::Logger) -> std::io::Result<ConfigDirs> {
  let app_path = env::current_exe()?;
  let app_dir = app_path
    .parent()
    .ok_or(std::io::Error::new(
      std::io::ErrorKind::NotFound,
      "App dir not found",
    ))?
    .to_owned();
  let application = PathBuf::from(&trim_and_lowercase_then_replace_spaces(application, "-"));
  let home_dir = dirs_sys::home_dir();
  let cwd_dir = env::current_dir()?;

  // Directories in reverse order of precedence. For instance, config found in `$HOME/.config`
  // takes precedence over config found in $XDG_CONFIG_HOME.
  let dirs = vec_opt!(
    // OSX preffered config dir
    home_dir
      .as_ref()
      .map(|home_dir| home_dir.join("Library/Preferences").join(&application)),
    // XDG config path
    env::var_os("XDG_CONFIG_HOME")
      .and_then(dirs_sys::is_absolute_path)
      .map(|p| p.join(&application)),
    // $HOME/.config path
    home_dir
      .as_ref()
      .map(|home_dir| home_dir.join(".config").join(&application)),
  );

  Ok(ConfigDirs {
    logger,
    dirs,
    app_dir,
    home_dir,
    cwd_dir,
  })
}

fn trim_and_lowercase_then_replace_spaces(name: &str, replacement: &str) -> String {
  let mut buf = String::with_capacity(name.len());
  let mut parts = name.split_whitespace();
  let mut current_part = parts.next();
  let replace = !replacement.is_empty();
  while current_part.is_some() {
    let value = current_part.unwrap().to_lowercase();
    buf.push_str(&value);
    current_part = parts.next();
    if replace && current_part.is_some() {
      buf.push_str(replacement);
    }
  }
  buf
}

#[cfg(test)]
mod tests {
  use crate::mac::trim_and_lowercase_then_replace_spaces;

  #[test]
  fn test_trim_and_lowercase_then_replace_spaces() {
    let input1 = "Bar App";
    let actual1 = trim_and_lowercase_then_replace_spaces(input1, "-");
    let expected1 = "bar-app";
    assert_eq!(expected1, actual1);

    let input2 = "BarApp-Foo";
    let actual2 = trim_and_lowercase_then_replace_spaces(input2, "-");
    let expected2 = "barapp-foo";
    assert_eq!(expected2, actual2);

    let input3 = " Bar App ";
    let actual3 = trim_and_lowercase_then_replace_spaces(input3, "-");
    let expected3 = "bar-app";
    assert_eq!(expected3, actual3);

    let input4 = "  Bar  App  ";
    let actual4 = trim_and_lowercase_then_replace_spaces(input4, "-");
    let expected4 = "bar-app";
    assert_eq!(expected4, actual4);
  }
}