1use std::fmt;
12use std::io;
13use std::path::PathBuf;
14use std::sync::OnceLock;
15
16use crate::suite::{Suite, SuiteError};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct AppId {
22 id: &'static str,
23 dir_name: &'static str,
24}
25
26impl AppId {
27 pub const fn new(id: &'static str) -> AppId {
29 AppId { id, dir_name: id }
30 }
31
32 pub const fn with_dir(id: &'static str, dir_name: &'static str) -> AppId {
34 AppId { id, dir_name }
35 }
36
37 pub const fn id(&self) -> &'static str {
38 self.id
39 }
40
41 pub const fn dir_name(&self) -> &'static str {
42 self.dir_name
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum Refusal {
49 UserRootUnknown,
51 Io(String),
53}
54
55impl Refusal {
56 pub fn rule(&self) -> &'static str {
58 match self {
59 Refusal::UserRootUnknown => "user-root-unknown",
60 Refusal::Io(_) => "io",
61 }
62 }
63}
64
65impl fmt::Display for Refusal {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 Refusal::Io(e) => write!(f, "run refused ({}): {e}", self.rule()),
69 _ => write!(f, "run refused ({})", self.rule()),
70 }
71 }
72}
73
74impl std::error::Error for Refusal {}
75
76impl From<io::Error> for Refusal {
77 fn from(e: io::Error) -> Self {
78 Refusal::Io(e.to_string())
79 }
80}
81
82#[derive(Debug, Clone)]
84pub enum Mode {
85 Release,
86}
87
88impl Mode {
89 pub fn is_test(&self) -> bool {
91 false
92 }
93}
94
95pub trait ModeInput {
98 fn env(&self, name: &str) -> Option<String>;
100
101 fn per_user_default(&self) -> Result<Suite, SuiteError> {
103 Suite::per_user_default()
104 }
105}
106
107#[derive(Debug, Clone, Copy, Default)]
109pub struct ProcessInput;
110
111impl ModeInput for ProcessInput {
112 fn env(&self, name: &str) -> Option<String> {
113 std::env::var(name).ok()
114 }
115}
116
117#[derive(Debug, Clone)]
119pub struct AppRoots {
120 pub suite: Suite,
122 pub app_dir: PathBuf,
124}
125
126pub fn decide(app: &AppId, input: &dyn ModeInput) -> Result<Mode, Refusal> {
129 let _ = (app, input);
130 Ok(Mode::Release)
131}
132
133pub fn init_or_exit(app: &AppId) -> &'static Mode {
136 static MODE: OnceLock<Mode> = OnceLock::new();
137 if let Some(mode) = MODE.get() {
138 return mode;
139 }
140 match decide(app, &ProcessInput) {
141 Ok(mode) => MODE.get_or_init(|| mode),
142 Err(refusal) => {
143 eprintln!("{refusal}");
144 std::process::exit(2);
145 }
146 }
147}
148
149pub fn roots(app: &AppId, mode: &Mode) -> Result<AppRoots, SuiteError> {
151 roots_with(app, mode, &ProcessInput)
152}
153
154pub fn roots_with(app: &AppId, mode: &Mode, input: &dyn ModeInput) -> Result<AppRoots, SuiteError> {
156 match mode {
157 Mode::Release => {
158 let suite = input.per_user_default()?;
159 let orthic = suite
160 .root()
161 .parent()
162 .ok_or_else(|| SuiteError::NoRoot("the per-user suite root has no parent".into()))?
163 .to_path_buf();
164 Ok(AppRoots {
165 suite,
166 app_dir: orthic.join(app.dir_name),
167 })
168 }
169 }
170}