unidirs 0.1.0

Unified directories for different use cases of an application, providing standard directories for local development, when run as service or when run by a user.
Documentation
use std::env;

use crate::unified::UnifiedDirs;

/// The simple builder is constructed through the [`UnifiedDirs::simple`] method and allows to
/// further configure ways of detecting whether the application is run as a service or by the user.
///
/// [`with`](Self::with) and all the `with_*` functions are called and evaluated in order and
/// immediately (**not** delayed until the call to [`build`](Self::build)). If service mode is
/// detected by any technique, further functions won't be evaluated anymore.
pub struct SimpleBuilder<Q, O, A> {
    service: bool,
    qualifier: Q,
    organization: O,
    application: A,
}

impl<Q, O, A> SimpleBuilder<Q, O, A>
where
    Q: AsRef<str>,
    O: AsRef<str>,
    A: AsRef<str>,
{
    pub(crate) fn new(qualifier: Q, organization: O, application: A) -> Self {
        Self {
            service: false,
            qualifier,
            organization,
            application,
        }
    }

    /// Use certain environment variable names to detect to be in service mode. The value of each
    /// variable doesn't matter, just whether the variable is present.
    ///
    /// Currently the name `SERVCIE` and `DAEMON` indicate the service mode.
    #[must_use]
    pub fn with_env(self) -> Self {
        self.with(|_| env::vars_os().any(|(name, _)| name == "SERVICE" || name == "DAEMON"))
    }

    /// Use certain program arguments to detect to be in service mode.
    ///
    /// Currently the arguments `--service` and `--daemon` indicate the service mode.
    #[must_use]
    pub fn with_args(self) -> Self {
        self.with(|_| env::args_os().any(|name| name == "--service" || name == "--daemon"))
    }

    /// Compare the executing user's account name against the application name to detect the service
    /// mode.
    ///
    /// It is common to create a separate
    #[must_use]
    pub fn with_username(self) -> Self {
        self.with(|builder| whoami::username_os() == builder.application.as_ref())
    }

    /// Define a custom detection logic for the service mode. A positive value means service mode, a
    /// negative value means user mode.
    ///
    /// The provided closure is only called if previous techniques didn't detect a service mode yet.
    ///
    /// # Example
    ///
    /// Consider the application was called with a `SERVICE` environment variable present and the
    /// following builder was used. The closure will not be called as the application was already
    /// detected to be in service mode due to the environment variable.
    ///
    /// ```rust
    /// use std::env;
    /// use unidirs::UnifiedDirs;
    ///
    /// env::set_var("SERVICE", "true");
    ///
    /// let mut called = false;
    /// let dirs = UnifiedDirs::simple("com", "example", "app")
    ///     .with_env()
    ///     .with(|_| {
    ///         called = true;
    ///         true
    ///     })
    ///     .build()
    ///     .unwrap();
    ///
    /// assert!(!called);
    /// ```
    ///
    /// As the `with_*` function are called in order, the same setup but in reverse order will call
    /// the lambda as it is evaluated first:
    ///
    /// ```rust
    /// use std::env;
    /// use unidirs::UnifiedDirs;
    ///
    /// env::set_var("SERVICE", "true");
    ///
    /// let mut called = false;
    /// let dirs = UnifiedDirs::simple("com", "example", "app")
    ///     .with(|_| {
    ///         called = true;
    ///         true
    ///     })
    ///     .with_env() // called after `with` now
    ///     .build()
    ///     .unwrap();
    ///
    /// assert!(called);
    /// ```
    pub fn with(self, f: impl FnOnce(&Self) -> bool) -> Self {
        Self {
            service: self.service || f(&self),
            qualifier: self.qualifier,
            organization: self.organization,
            application: self.application,
        }
    }

    /// Construct the [`UnifiedDirs`] instance with the backend decided by previously configured
    /// techniques.
    ///
    /// - If the application was built in debug mode (or with `debug_assertions` enabled), it will
    ///   always pick [`LocalDirs`](crate::LocalDirs).
    /// - If any of the configured techniques detected that the application is run in service mode,
    ///   the backend will be [`ServiceDirs`](crate::ServiceDirs).
    /// - Otherwise, it'll be [`UserDirs`](crate::UserDirs).
    #[must_use]
    pub fn build(self) -> Option<UnifiedDirs> {
        fn inner(
            service: bool,
            qualifier: &str,
            organization: &str,
            application: &str,
        ) -> Option<UnifiedDirs> {
            if cfg!(debug_assertions) {
                UnifiedDirs::local()
            } else if service {
                Some(UnifiedDirs::service(organization, application))
            } else {
                UnifiedDirs::user(qualifier, organization, application)
            }
        }

        inner(
            self.service,
            self.qualifier.as_ref(),
            self.organization.as_ref(),
            self.application.as_ref(),
        )
    }

    /// Configure and execute the builder with all detection techniques enabled.
    ///
    /// This is a convenience shorthand for manually calling [`with_env`](Self::with_env),
    /// [`with_args`](Self::with_args) and [`with_username`](Self::with_username) followed by
    /// [`build`](Self::build).
    ///
    /// # Example
    ///
    /// ```rust
    /// use unidirs::UnifiedDirs;
    ///
    /// UnifiedDirs::simple("com", "example", "app").default();
    ///
    /// // The `default()` call is a shorthand for:
    ///
    /// UnifiedDirs::simple("com", "example", "app")
    ///     .with_env()
    ///     .with_args()
    ///     .with_username()
    ///     .build();
    /// ```
    pub fn default(self) -> Option<UnifiedDirs> {
        self.with_env().with_args().with_username().build()
    }
}