use crate::{Env, Path, PathBuf, is};
#[doc = crate::_tags!(platform)]
#[doc = crate::_doc_location!("sys/env")]
#[derive(Clone, Debug, PartialEq)]
pub struct AppConfig {
tld: String,
author: String,
app_name: String,
}
impl AppConfig {
pub fn new(tld: &str, author: &str, app_name: &str) -> Option<Self> {
if Self::validate_tld(tld)
&& Self::validate_author(author)
&& Self::validate_app_name(app_name)
{
None
} else {
Some(Self {
tld: tld.into(),
author: author.into(),
app_name: app_name.into(),
})
}
}
#[must_use]
pub fn tld(&self) -> &str {
&self.tld
}
#[must_use]
pub fn author(&self) -> &str {
&self.author
}
#[must_use]
pub fn app_name(&self) -> &str {
&self.app_name
}
fn validate_tld(tld: &str) -> bool {
!tld.is_empty()
&& tld.len() <= 127
&& tld.split('.').all(|label| {
!label.is_empty()
&& label.len() <= 63
&& label.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
})
}
fn validate_author(author: &str) -> bool {
!author.is_empty()
&& author.len() <= 50
&& author.chars().all(|c| c.is_ascii_alphanumeric() || c.is_whitespace() || c == '-')
}
fn validate_app_name(app_name: &str) -> bool {
!app_name.is_empty()
&& app_name.len() <= 50
&& app_name.chars().all(|c| c.is_ascii_alphanumeric() || c.is_whitespace())
}
#[must_use]
pub fn bundle_id(&self) -> String {
let author = self.author.to_lowercase().replace(' ', "-");
let app_name = self.app_name.replace(' ', "-");
let mut parts = vec![self.tld.as_str(), author.as_str(), app_name.as_str()];
parts.retain(|part| !part.is_empty());
parts.join(".")
}
#[must_use]
pub fn unixy_name(&self) -> String {
self.app_name.to_lowercase().replace(' ', "_")
}
}
#[doc = crate::_tags!(platform)]
#[doc = crate::_doc_location!("sys/env")]
#[doc = crate::_doc!(vendor: "etcetera")]
#[rustfmt::skip]
pub trait AppEnv {
#[must_use]
fn app_home(&self) -> &Path;
#[must_use]
fn app_config(&self) -> PathBuf;
#[must_use]
fn app_data(&self) -> PathBuf;
#[must_use]
fn app_cache(&self) -> PathBuf;
#[must_use]
fn app_state(&self) -> Option<PathBuf>;
#[must_use]
fn app_runtime(&self) -> Option<PathBuf>;
#[must_use]
fn app_in_config(&self, append: &Path) -> PathBuf {
app_in(self.app_config(), append)
}
#[must_use]
fn app_in_data(&self, append: &Path) -> PathBuf {
app_in(self.app_data(), append)
}
#[must_use]
fn app_in_cache(&self, append: &Path) -> PathBuf {
app_in(self.app_cache(), append)
}
#[must_use]
fn app_in_state(&self, append: &Path) -> Option<PathBuf> {
self.app_state().map(|base| app_in(base, append))
}
#[must_use]
fn app_in_runtime(&self, append: &Path) -> Option<PathBuf> {
self.app_runtime().map(|base| app_in(base, append))
}
#[must_use]
fn app_temp(&self) -> PathBuf {
let temp_dir = Env::temp_dir();
if temp_dir.is_absolute() {
temp_dir
} else {
self.app_cache()
}
}
#[must_use]
fn app_in_temp(&self, append: &Path) -> PathBuf {
app_in(self.app_temp(), append)
}
}
#[must_use] #[rustfmt::skip]
fn app_in(mut base: PathBuf, path: &Path) -> PathBuf { base.push(path); base }
#[doc = crate::_tags!(linux)]
#[doc = crate::_doc_location!("sys/env")]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AppXdg {
home: PathBuf,
unixy_name: String,
}
impl AppXdg {
#[must_use]
pub fn new(app_data: Option<AppConfig>) -> Option<Self> {
let home = Env::home_dir()?;
if let Some(app) = app_data {
Some(Self { home, unixy_name: app.unixy_name() })
} else {
Some(Self { home, unixy_name: String::new() })
}
}
fn env_var_or_none(env_var: &str) -> Option<PathBuf> {
Env::var_os(env_var).and_then(|path| {
let path = PathBuf::from(path);
path.is_absolute().then_some(path)
})
}
fn env_var_or_default(&self, env_var: &str, default: impl AsRef<Path>) -> PathBuf {
Self::env_var_or_none(env_var).unwrap_or_else(|| self.home.join(default))
}
}
impl AppEnv for AppXdg {
fn app_home(&self) -> &Path {
&self.home
}
fn app_config(&self) -> PathBuf {
let dir = self.env_var_or_default("XDG_CONFIG_HOME", ".config/");
is![self.unixy_name.is_empty(), dir, dir.join(&self.unixy_name)]
}
fn app_data(&self) -> PathBuf {
let dir = self.env_var_or_default("XDG_DATA_HOME", ".local/share/");
is![self.unixy_name.is_empty(), dir, dir.join(&self.unixy_name)]
}
fn app_cache(&self) -> PathBuf {
let dir = self.env_var_or_default("XDG_CACHE_HOME", ".cache/");
is![self.unixy_name.is_empty(), dir, dir.join(&self.unixy_name)]
}
fn app_state(&self) -> Option<PathBuf> {
let dir = self.env_var_or_default("XDG_STATE_HOME", ".local/state/");
Some(is![self.unixy_name.is_empty(), dir, dir.join(&self.unixy_name)])
}
fn app_runtime(&self) -> Option<PathBuf> {
let dir = Self::env_var_or_none("XDG_RUNTIME_DIR");
is![self.unixy_name.is_empty(), dir, dir.map(|d| d.join(&self.unixy_name))]
}
}
#[doc = crate::_tags!(unix)]
#[doc = crate::_doc_location!("sys/env")]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AppUnix {
home: PathBuf,
unixy_name: String,
}
impl AppUnix {
#[must_use]
pub fn new(app_data: AppConfig) -> Option<Self> {
let home = Env::home_dir()?;
Some(Self { home, unixy_name: app_data.unixy_name() })
}
}
#[rustfmt::skip]
impl AppEnv for AppUnix {
fn app_home(&self) -> &Path { &self.home }
fn app_config(&self) -> PathBuf { self.home.join(&self.unixy_name) }
fn app_data(&self) -> PathBuf { self.app_config().join("data") }
fn app_cache(&self) -> PathBuf { self.app_config().join("cache") }
fn app_state(&self) -> Option<PathBuf> { Some(self.app_config().join("state")) }
fn app_runtime(&self) -> Option<PathBuf> { Some(self.app_config().join("runtime")) }
}
#[doc = crate::_tags!(apple)]
#[doc = crate::_doc_location!("sys/env")]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AppApple {
home: PathBuf,
bundle_id: String,
}
impl AppApple {
#[must_use]
pub fn new(app_data: Option<AppConfig>) -> Option<Self> {
let home = Env::home_dir()?;
if let Some(app) = app_data {
Some(Self { home, bundle_id: app.bundle_id() })
} else {
Some(Self { home, bundle_id: String::new() })
}
}
}
#[rustfmt::skip]
impl AppEnv for AppApple {
fn app_home(&self) -> &Path { &self.home }
fn app_config(&self) -> PathBuf {
let dir = self.home.join("Library/Preferences/");
is![self.bundle_id.is_empty(), dir, dir.join(&self.bundle_id)]
}
fn app_data(&self) -> PathBuf {
let dir = self.home.join("Library/Application Support/");
is![self.bundle_id.is_empty(), dir, dir.join(&self.bundle_id)]
}
fn app_cache(&self) -> PathBuf {
let dir = self.home.join("Library/Caches/");
is![self.bundle_id.is_empty(), dir, dir.join(&self.bundle_id)]
}
fn app_state(&self) -> Option<PathBuf> { None }
fn app_runtime(&self) -> Option<PathBuf> { None }
}
#[doc = crate::_tags!(windows)]
#[doc = crate::_doc_location!("sys/env")]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct AppWindows {
home: PathBuf,
app_path: Option<PathBuf>,
}
impl AppWindows {
#[must_use] #[rustfmt::skip]
pub fn new(app_data: Option<AppConfig>) -> Option<Self> {
let home = Env::home_dir()?;
if let Some(app) = app_data {
Some(Self { home, app_path: Some(PathBuf::from(app.author).join(app.app_name)) })
} else {
Some(Self { home, app_path: None })
}
}
}
#[rustfmt::skip]
impl AppEnv for AppWindows {
fn app_home(&self) -> &Path { &self.home }
fn app_config(&self) -> PathBuf {
let mut dir = self.home.join("AppData").join("Roaming");
is![let Some(app) = &self.app_path, {dir.push(app); dir.push("config"); dir }, dir]
}
fn app_data(&self) -> PathBuf {
let mut dir = self.home.join("AppData").join("Roaming");
is![let Some(app) = &self.app_path, {dir.push(app); dir.push("data"); dir }, dir]
}
fn app_cache(&self) -> PathBuf {
let mut dir = self.home.join("AppData").join("Local");
is![let Some(app) = &self.app_path, {dir.push(app); dir.push("cache"); dir }, dir]
}
fn app_state(&self) -> Option<PathBuf> { None }
fn app_runtime(&self) -> Option<PathBuf> { None }
}