env_hooks/
state.rs

1// TODO: Document state flow with `aquamarine` crate
2// flowchart TD
3//     subgraph getEnvStateVarFn
4//         getEnvStateVar1
5//         getEnvStateVar2
6//     end
7//     subgraph getNewEnvStateVarFn
8//         getNewEnvStateVar1
9//         getNewEnvStateVar2
10//     end
11//     subgraph resetEnvVarsFn
12//         resetEnvVars1
13//         resetEnvVars2
14//     end
15//     ShellPromptState --> getCurrentDir@{ shape: "diamond" }
16//     getCurrentDir --> |success| CurrentDirState
17//     CurrentDirState --> findAllMatchingRcs@{ shape: "diamond" }
18//     findAllMatchingRcs --> |no rcs matched| NoRcsState
19//     findAllMatchingRcs --> |rcs matched| RcsState
20//     NoRcsState --> getEnvStateVar1@{ shape: "diamond", label: "getEnvStateVar" }
21//     RcsState --> getEnvStateVar2@{ shape: "diamond", label: "getEnvStateVar" }
22//     getEnvStateVar1 --> |no env state var found| DoneState
23//     getEnvStateVar1 --> |env state var found| ReadyForFullResetState
24//     getEnvStateVar2 --> |no env state var found| NoEnvStateVarState
25//     getEnvStateVar2 --> |env state var found| EnvStateVarState
26//     ReadyForFullResetState --> resetEnvVars1@{ shape: "diamond", label: "resetEnvVars" }
27//     resetEnvVars1 --> |success| DoneState
28//     NoEnvStateVarState --> getNewEnvStateVar1@{ shape: "diamond", label: "getNewEnvStateVar" }
29//     EnvStateVarState --> getNewEnvStateVar2@{ shape: "diamond", label: "getNewEnvStateVar" }
30//     getNewEnvStateVar1 --> |success| NewEnvStateVarState
31//     getNewEnvStateVar2 --> |success| OldAndNewEnvStateVarState
32//     NewEnvStateVarState --> setEnvVars@{ shape: "diamond" }
33//     OldAndNewEnvStateVarState --> resetEnvVars2@{ shape: "diamond", label: "resetEnvVars" }
34//     resetEnvVars2 --> |success| setEnvVars
35//     setEnvVars --> |success| DoneState
36
37use std::{
38    env,
39    ffi::{OsStr, OsString},
40    marker::PhantomData,
41    path::{Path, PathBuf},
42};
43
44use anyhow::Ok;
45
46#[derive(Debug, Clone, Copy, Default)]
47pub struct ShellPromptState;
48
49impl ShellPromptState {
50    pub fn get_current_dir(
51        provided_current_dir: Option<PathBuf>,
52    ) -> anyhow::Result<CurrentDirState> {
53        let current_dir = if let Some(current_dir) = provided_current_dir {
54            current_dir
55        } else {
56            env::current_dir()?
57        };
58        Ok(CurrentDirState { current_dir })
59    }
60}
61
62#[derive(Debug, Clone)]
63pub struct CurrentDirState {
64    current_dir: PathBuf,
65}
66
67impl CurrentDirState {
68    pub fn match_rcs<RC>(
69        self,
70        match_rcs_cb: impl Fn(&Path) -> anyhow::Result<Vec<RC>>,
71    ) -> anyhow::Result<MatchRcs<RC>> {
72        let rcs = match_rcs_cb(&self.current_dir)?;
73        let match_rcs = if rcs.is_empty() {
74            MatchRcs::NoRcs(NoRcsState {
75                phantom: PhantomData,
76            })
77        } else {
78            MatchRcs::Rcs(RcsState { rcs })
79        };
80        Ok(match_rcs)
81    }
82}
83
84#[derive(Debug, Clone)]
85pub enum MatchRcs<RC> {
86    NoRcs(NoRcsState),
87    Rcs(RcsState<RC>),
88}
89
90#[derive(Debug, Clone)]
91pub struct NoRcsState {
92    phantom: PhantomData<()>,
93}
94
95impl NoRcsState {
96    pub fn get_env_state_var(
97        self,
98        env_state_var_key: impl AsRef<OsStr>,
99    ) -> Option<ReadyForFullResetState> {
100        get_env_state_var(env_state_var_key).map(|env_state_var_value| ReadyForFullResetState {
101            env_state_var_value,
102        })
103    }
104}
105
106#[derive(Debug, Clone)]
107pub struct ReadyForFullResetState {
108    env_state_var_value: OsString,
109}
110
111impl ReadyForFullResetState {
112    pub fn reset_env_vars(
113        self,
114        reset_env_vars_cb: impl Fn(OsString) -> anyhow::Result<()>,
115    ) -> anyhow::Result<()> {
116        reset_env_vars_cb(self.env_state_var_value)?;
117        Ok(())
118    }
119}
120
121#[derive(Debug, Clone)]
122pub struct RcsState<RC> {
123    rcs: Vec<RC>,
124}
125
126impl<RC> RcsState<RC> {
127    pub fn get_env_state_var(self, env_state_var_key: impl AsRef<OsStr>) -> GetEnvStateVar<RC> {
128        let rcs = self.rcs;
129        if let Some(env_state_var_value) = get_env_state_var(env_state_var_key) {
130            GetEnvStateVar::EnvStateVar(EnvStateVarState {
131                rcs,
132                env_state_var_value,
133            })
134        } else {
135            GetEnvStateVar::NoEnvStateVar(NoEnvStateVarState { rcs })
136        }
137    }
138}
139
140#[derive(Debug, Clone)]
141pub enum GetEnvStateVar<RC> {
142    NoEnvStateVar(NoEnvStateVarState<RC>),
143    EnvStateVar(EnvStateVarState<RC>),
144}
145
146#[derive(Debug, Clone)]
147pub struct NoEnvStateVarState<RC> {
148    rcs: Vec<RC>,
149}
150
151impl<RC> NoEnvStateVarState<RC> {
152    pub fn set_new_env_state_var(
153        self,
154        set_new_env_state_var_cb: impl Fn(Vec<RC>) -> anyhow::Result<()>,
155    ) -> anyhow::Result<()> {
156        set_new_env_state_var_cb(self.rcs)?;
157        Ok(())
158    }
159}
160
161#[derive(Debug, Clone)]
162pub struct EnvStateVarState<RC> {
163    rcs: Vec<RC>,
164    env_state_var_value: OsString,
165}
166
167impl<RC> EnvStateVarState<RC> {
168    pub fn reset_and_set_new_env_state_var<T>(
169        self,
170        reset_env_vars_cb: impl Fn(Vec<RC>, OsString) -> anyhow::Result<T>,
171        set_new_env_state_var_cb: impl Fn(T) -> anyhow::Result<()>,
172    ) -> anyhow::Result<()> {
173        let state = reset_env_vars_cb(self.rcs, self.env_state_var_value)?;
174        set_new_env_state_var_cb(state)?;
175        Ok(())
176    }
177}
178
179fn get_env_state_var(env_state_var_key: impl AsRef<OsStr>) -> Option<OsString> {
180    env::var_os(env_state_var_key)
181}