use std::fmt;
use std::io;
use std::path::PathBuf;
use std::sync::OnceLock;
use crate::suite::{Suite, SuiteError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AppId {
id: &'static str,
dir_name: &'static str,
}
impl AppId {
pub const fn new(id: &'static str) -> AppId {
AppId { id, dir_name: id }
}
pub const fn with_dir(id: &'static str, dir_name: &'static str) -> AppId {
AppId { id, dir_name }
}
pub const fn id(&self) -> &'static str {
self.id
}
pub const fn dir_name(&self) -> &'static str {
self.dir_name
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Refusal {
UserRootUnknown,
Io(String),
}
impl Refusal {
pub fn rule(&self) -> &'static str {
match self {
Refusal::UserRootUnknown => "user-root-unknown",
Refusal::Io(_) => "io",
}
}
}
impl fmt::Display for Refusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Refusal::Io(e) => write!(f, "run refused ({}): {e}", self.rule()),
_ => write!(f, "run refused ({})", self.rule()),
}
}
}
impl std::error::Error for Refusal {}
impl From<io::Error> for Refusal {
fn from(e: io::Error) -> Self {
Refusal::Io(e.to_string())
}
}
#[derive(Debug, Clone)]
pub enum Mode {
Release,
}
impl Mode {
pub fn is_test(&self) -> bool {
false
}
}
pub trait ModeInput {
fn env(&self, name: &str) -> Option<String>;
fn per_user_default(&self) -> Result<Suite, SuiteError> {
Suite::per_user_default()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ProcessInput;
impl ModeInput for ProcessInput {
fn env(&self, name: &str) -> Option<String> {
std::env::var(name).ok()
}
}
#[derive(Debug, Clone)]
pub struct AppRoots {
pub suite: Suite,
pub app_dir: PathBuf,
}
pub fn decide(app: &AppId, input: &dyn ModeInput) -> Result<Mode, Refusal> {
let _ = (app, input);
Ok(Mode::Release)
}
pub fn init_or_exit(app: &AppId) -> &'static Mode {
static MODE: OnceLock<Mode> = OnceLock::new();
if let Some(mode) = MODE.get() {
return mode;
}
match decide(app, &ProcessInput) {
Ok(mode) => MODE.get_or_init(|| mode),
Err(refusal) => {
eprintln!("{refusal}");
std::process::exit(2);
}
}
}
pub fn roots(app: &AppId, mode: &Mode) -> Result<AppRoots, SuiteError> {
roots_with(app, mode, &ProcessInput)
}
pub fn roots_with(app: &AppId, mode: &Mode, input: &dyn ModeInput) -> Result<AppRoots, SuiteError> {
match mode {
Mode::Release => {
let suite = input.per_user_default()?;
let orthic = suite
.root()
.parent()
.ok_or_else(|| SuiteError::NoRoot("the per-user suite root has no parent".into()))?
.to_path_buf();
Ok(AppRoots {
suite,
app_dir: orthic.join(app.dir_name),
})
}
}
}