use std::env; use std::io::{Error, ErrorKind, Result}; use std::path::PathBuf; pub struct Configuration { // Default Base Directories, default to XDG dirs but can be // configured by users. pub config_directory: PathBuf, pub data_directory: PathBuf, pub output_directory: PathBuf, pub max_posts: u8, // Derived directories: Config pub remote_config: PathBuf, // Derived directories: Data pub posts_directory: PathBuf, pub archive_directory: PathBuf, pub static_directory: PathBuf, pub templates_directory: PathBuf, // Derived directories: Output pub blog_output_directory: PathBuf, pub archive_output_directory: PathBuf, } impl Configuration { pub fn new() -> Result { let config_directory = Configuration::directory( "BLOG_CONFIG_DIRECTORY", "XDG_CONFIG_HOME", ".config", "blog", )?; let data_directory = Configuration::directory( "BLOG_DATA_DIRECTORY", "XDG_DATA_HOME", ".local/share", "blog", )?; let output_directory = Configuration::directory("BLOG_OUTPUT_DIRECTORY", "XDG_CACHE_HOME", ".cache", "blog")?; let max_posts: u8 = env::var("BLOG_MAX_POSTS") .ok() .and_then(|v| v.parse().ok()) .unwrap_or(3); let remote_config = config_directory.join("blogremote"); let posts_directory = data_directory.join("posts"); let archive_directory = data_directory.join("archive"); let static_directory = data_directory.join("static"); let templates_directory = data_directory.join("templates"); let blog_output_directory = output_directory.join("blog"); let archive_output_directory = output_directory.join("archive"); Ok(Configuration { config_directory, data_directory, output_directory, max_posts, remote_config, posts_directory, archive_directory, static_directory, templates_directory, blog_output_directory, archive_output_directory, }) } fn directory( user_override: &str, default_value: &str, home_fallback: &str, path: &str, ) -> Result { match env::var(user_override) { Ok(directory) => Ok(PathBuf::from(directory).join(path)), Err(_) => match env::var(default_value) { Ok(directory) => Ok(PathBuf::from(directory).join(path)), Err(_) => match env::var("HOME") { Ok(directory) => Ok(PathBuf::from(directory).join(home_fallback).join(path)), Err(_) => Err(Error::new( ErrorKind::NotFound, format!( "Could not find required directory, {user_override} or {default_value} should be set and readable" ), )), }, }, } } } #[cfg(test)] mod tests { use super::*; use std::env; use test_utilities::*; #[test] fn test_reads_configuration_from_configuration_variable_if_set() { let test_dir = setup_test_dir(); // SAFETY: Run only in single-threaded mode unsafe { env::set_var("TEST_BLOG_CONFIG_DIRECTORY", &test_dir); } let config_directory = Configuration::directory( "TEST_BLOG_CONFIG_DIRECTORY", "XDG_CONFIG_HOME", ".config", "beepboop", ) .expect("Could not get config directory"); assert_eq!(config_directory, test_dir.join("beepboop")); cleanup_test_dir(&test_dir); } #[test] fn test_reads_configuration_from_default_location_if_configuration_variable_not_set() { let test_dir = setup_test_dir(); // SAFETY: Run only in single-threaded mode unsafe { env::set_var("TEST_XDG_CONFIG_HOME", &test_dir); } let config_directory = Configuration::directory( "UNDEFINED_BLOG_CONFIG_DIRECTORY", "TEST_XDG_CONFIG_HOME", ".config", "beepboop", ) .expect("Could not get config directory"); assert_eq!(config_directory, test_dir.join("beepboop")); cleanup_test_dir(&test_dir); } #[test] fn test_reads_configuration_from_home_if_configuration_variable_not_set() { let path = PathBuf::from(std::env::var("HOME").unwrap()); let config_directory = Configuration::directory( "UNDEFINED_BLOG_CONFIG_DIRECTORY", "UNDEFINED_FAKE_XDG_CONFIG_HOME", ".config", "beepboop", ) .expect("Could not get config directory"); assert_eq!(config_directory, path.join(".config/beepboop")); } #[test] fn test_fails_configuration_if_no_variable_is_set() { let original_home = env::var("HOME").unwrap(); // SAFETY: Run only in single-threaded mode unsafe { env::remove_var("HOME"); } assert!( Configuration::directory( "UNDEFINED_BLOG_CONFIG_DIRECTORY", "UNDEFINED_FAKE_XDG_CONFIG_HOME", ".config", "beepboop", ) .is_err() ); // SAFETY: Run only in single-threaded mode unsafe { env::set_var("HOME", original_home); } } #[test] fn test_sets_correct_configuration_directories() { let default_configuration = Configuration::new().unwrap(); let test_dir = setup_test_dir(); // SAFETY: Run only in single-threaded mode unsafe { env::set_var("BLOG_CONFIG_DIRECTORY", test_dir.join("config")); env::set_var("BLOG_DATA_DIRECTORY", test_dir.join("data")); env::set_var("BLOG_OUTPUT_DIRECTORY", test_dir.join("output")); env::set_var("BLOG_MAX_POSTS", "99"); } let override_configuration = Configuration::new().unwrap(); // Ensure our overrides were applied assert_eq!( override_configuration.config_directory, test_dir.join("config/blog") ); assert_eq!( override_configuration.output_directory, test_dir.join("output/blog") ); assert_eq!( override_configuration.data_directory, test_dir.join("data/blog") ); assert_eq!(override_configuration.max_posts, 99); assert_eq!( override_configuration.remote_config, test_dir.join("config/blog/blogremote") ); assert_eq!( override_configuration.blog_output_directory, test_dir.join("output/blog/blog") ); assert_eq!( override_configuration.archive_output_directory, test_dir.join("output/blog/archive") ); assert_eq!( override_configuration.posts_directory, test_dir.join("data/blog/posts") ); assert_eq!( override_configuration.archive_directory, test_dir.join("data/blog/archive") ); assert_eq!( override_configuration.static_directory, test_dir.join("data/blog/static") ); assert_eq!( override_configuration.templates_directory, test_dir.join("data/blog/templates") ); // Ensure all the defaults are different from defaults assert_ne!( default_configuration.config_directory, override_configuration.config_directory ); assert_ne!( default_configuration.output_directory, override_configuration.output_directory ); assert_ne!( default_configuration.data_directory, override_configuration.data_directory ); assert_ne!( default_configuration.max_posts, override_configuration.max_posts ); assert_ne!( default_configuration.remote_config, override_configuration.remote_config ); assert_ne!( default_configuration.blog_output_directory, override_configuration.blog_output_directory ); assert_ne!( default_configuration.archive_output_directory, override_configuration.archive_output_directory ); assert_ne!( default_configuration.posts_directory, override_configuration.posts_directory ); assert_ne!( default_configuration.archive_directory, override_configuration.archive_directory ); assert_ne!( default_configuration.static_directory, override_configuration.static_directory ); assert_ne!( default_configuration.templates_directory, override_configuration.templates_directory ); cleanup_test_dir(&test_dir); } }