1use std::collections::HashSet;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11use crate::trace::trace_lazy;
12
13#[derive(Debug, Clone, Default)]
15pub struct ShellSettings {
16 pub errexit: bool,
18 pub verbose: bool,
20 pub xtrace: bool,
22 pub pipefail: bool,
24 pub nounset: bool,
26 pub noglob: bool,
28 pub allexport: bool,
30}
31
32impl ShellSettings {
33 pub fn new() -> Self {
35 Self::default()
36 }
37
38 pub fn reset(&mut self) {
40 *self = Self::default();
41 }
42
43 pub fn set(&mut self, option: &str, value: bool) {
47 match option {
48 "e" | "errexit" => self.errexit = value,
49 "v" | "verbose" => self.verbose = value,
50 "x" | "xtrace" => self.xtrace = value,
51 "u" | "nounset" => self.nounset = value,
52 "f" | "noglob" => self.noglob = value,
53 "a" | "allexport" => self.allexport = value,
54 "o pipefail" | "pipefail" => self.pipefail = value,
55 _ => {
56 trace_lazy("ShellSettings", || {
57 format!("Unknown shell option: {}", option)
58 });
59 }
60 }
61 }
62
63 pub fn enable(&mut self, option: &str) {
65 self.set(option, true);
66 }
67
68 pub fn disable(&mut self, option: &str) {
70 self.set(option, false);
71 }
72}
73
74pub struct GlobalState {
76 shell_settings: RwLock<ShellSettings>,
78 active_runners: RwLock<HashSet<u64>>,
80 next_runner_id: std::sync::atomic::AtomicU64,
82 signal_handlers_installed: AtomicBool,
84 virtual_commands_enabled: AtomicBool,
86 initial_cwd: RwLock<Option<std::path::PathBuf>>,
88}
89
90impl Default for GlobalState {
91 fn default() -> Self {
92 Self::new()
93 }
94}
95
96impl GlobalState {
97 pub fn new() -> Self {
99 let initial_cwd = std::env::current_dir().ok();
100
101 GlobalState {
102 shell_settings: RwLock::new(ShellSettings::new()),
103 active_runners: RwLock::new(HashSet::new()),
104 next_runner_id: std::sync::atomic::AtomicU64::new(1),
105 signal_handlers_installed: AtomicBool::new(false),
106 virtual_commands_enabled: AtomicBool::new(true),
107 initial_cwd: RwLock::new(initial_cwd),
108 }
109 }
110
111 pub async fn get_shell_settings(&self) -> ShellSettings {
113 self.shell_settings.read().await.clone()
114 }
115
116 pub async fn set_shell_settings(&self, settings: ShellSettings) {
118 *self.shell_settings.write().await = settings;
119 }
120
121 pub async fn with_shell_settings<F>(&self, f: F)
123 where
124 F: FnOnce(&mut ShellSettings),
125 {
126 let mut settings = self.shell_settings.write().await;
127 f(&mut settings);
128 }
129
130 pub async fn enable_shell_option(&self, option: &str) {
132 self.shell_settings.write().await.enable(option);
133 }
134
135 pub async fn disable_shell_option(&self, option: &str) {
137 self.shell_settings.write().await.disable(option);
138 }
139
140 pub async fn register_runner(&self) -> u64 {
142 let id = self
143 .next_runner_id
144 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
145 self.active_runners.write().await.insert(id);
146
147 trace_lazy("GlobalState", || format!("Registered runner {}", id));
148
149 id
150 }
151
152 pub async fn unregister_runner(&self, id: u64) {
154 self.active_runners.write().await.remove(&id);
155
156 trace_lazy("GlobalState", || format!("Unregistered runner {}", id));
157 }
158
159 pub async fn active_runner_count(&self) -> usize {
161 self.active_runners.read().await.len()
162 }
163
164 pub fn are_signal_handlers_installed(&self) -> bool {
166 self.signal_handlers_installed.load(Ordering::SeqCst)
167 }
168
169 pub fn set_signal_handlers_installed(&self, installed: bool) {
171 self.signal_handlers_installed
172 .store(installed, Ordering::SeqCst);
173 }
174
175 pub fn are_virtual_commands_enabled(&self) -> bool {
177 self.virtual_commands_enabled.load(Ordering::SeqCst)
178 }
179
180 pub fn enable_virtual_commands(&self) {
182 self.virtual_commands_enabled.store(true, Ordering::SeqCst);
183 trace_lazy("GlobalState", || "Virtual commands enabled".to_string());
184 }
185
186 pub fn disable_virtual_commands(&self) {
188 self.virtual_commands_enabled.store(false, Ordering::SeqCst);
189 trace_lazy("GlobalState", || "Virtual commands disabled".to_string());
190 }
191
192 pub async fn get_initial_cwd(&self) -> Option<std::path::PathBuf> {
194 self.initial_cwd.read().await.clone()
195 }
196
197 pub async fn reset(&self) {
199 *self.shell_settings.write().await = ShellSettings::new();
201
202 self.active_runners.write().await.clear();
204
205 self.virtual_commands_enabled.store(true, Ordering::SeqCst);
207
208 trace_lazy("GlobalState", || "Global state reset completed".to_string());
211 }
212
213 pub async fn restore_cwd(&self) -> std::io::Result<()> {
215 if let Some(ref initial) = *self.initial_cwd.read().await {
216 if initial.exists() {
217 std::env::set_current_dir(initial)?;
218 }
219 }
220 Ok(())
221 }
222}
223
224static GLOBAL_STATE: std::sync::OnceLock<Arc<GlobalState>> = std::sync::OnceLock::new();
226
227pub fn global_state() -> Arc<GlobalState> {
229 GLOBAL_STATE
230 .get_or_init(|| Arc::new(GlobalState::new()))
231 .clone()
232}
233
234pub async fn reset_global_state() {
236 global_state().reset().await;
237}
238
239pub async fn get_shell_settings() -> ShellSettings {
241 global_state().get_shell_settings().await
242}
243
244pub async fn set_shell_option(option: &str) {
246 global_state().enable_shell_option(option).await;
247}
248
249pub async fn unset_shell_option(option: &str) {
251 global_state().disable_shell_option(option).await;
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257
258 #[test]
259 fn test_shell_settings_default() {
260 let settings = ShellSettings::new();
261 assert!(!settings.errexit);
262 assert!(!settings.verbose);
263 assert!(!settings.xtrace);
264 assert!(!settings.pipefail);
265 assert!(!settings.nounset);
266 }
267
268 #[test]
269 fn test_shell_settings_set() {
270 let mut settings = ShellSettings::new();
271
272 settings.set("e", true);
273 assert!(settings.errexit);
274
275 settings.set("errexit", false);
276 assert!(!settings.errexit);
277
278 settings.set("o pipefail", true);
279 assert!(settings.pipefail);
280 }
281
282 #[tokio::test]
283 async fn test_global_state_runners() {
284 let state = GlobalState::new();
285
286 let id1 = state.register_runner().await;
287 let id2 = state.register_runner().await;
288
289 assert_eq!(state.active_runner_count().await, 2);
290 assert!(id1 != id2);
291
292 state.unregister_runner(id1).await;
293 assert_eq!(state.active_runner_count().await, 1);
294
295 state.unregister_runner(id2).await;
296 assert_eq!(state.active_runner_count().await, 0);
297 }
298
299 #[tokio::test]
300 async fn test_global_state_virtual_commands() {
301 let state = GlobalState::new();
302
303 assert!(state.are_virtual_commands_enabled());
304
305 state.disable_virtual_commands();
306 assert!(!state.are_virtual_commands_enabled());
307
308 state.enable_virtual_commands();
309 assert!(state.are_virtual_commands_enabled());
310 }
311
312 #[tokio::test]
313 async fn test_global_state_reset() {
314 let state = GlobalState::new();
315
316 state.enable_shell_option("errexit").await;
318 state.register_runner().await;
319 state.disable_virtual_commands();
320
321 state.reset().await;
323
324 let settings = state.get_shell_settings().await;
326 assert!(!settings.errexit);
327 assert_eq!(state.active_runner_count().await, 0);
328 assert!(state.are_virtual_commands_enabled());
329 }
330}