1use std::collections::HashMap;
25use std::ffi::OsString;
26use std::str::FromStr;
27use std::sync::{OnceLock, RwLock};
28
29static OVERRIDES: OnceLock<RwLock<HashMap<String, String>>> = OnceLock::new();
30
31fn map() -> &'static RwLock<HashMap<String, String>> {
32 OVERRIDES.get_or_init(|| RwLock::new(HashMap::new()))
33}
34
35pub fn normalize_key(key: &str) -> String {
37 if key.starts_with("RLX_") {
38 key.to_string()
39 } else {
40 format!("RLX_{key}")
41 }
42}
43
44pub fn set(key: impl AsRef<str>, value: impl Into<String>) {
47 let key = normalize_key(key.as_ref());
48 if let Ok(mut g) = map().write() {
49 g.insert(key, value.into());
50 }
51}
52
53pub fn unset(key: impl AsRef<str>) {
55 let key = normalize_key(key.as_ref());
56 if let Ok(mut g) = map().write() {
57 g.remove(&key);
58 }
59}
60
61pub fn clear_overrides() {
63 if let Ok(mut g) = map().write() {
64 g.clear();
65 }
66}
67
68pub fn var(key: &str) -> Option<String> {
70 let key = normalize_key(key);
71 if let Ok(g) = map().read() {
72 if let Some(v) = g.get(&key) {
73 return Some(v.clone());
74 }
75 }
76 std::env::var(&key).ok()
77}
78
79pub fn var_os(key: &str) -> Option<OsString> {
81 var(key).map(Into::into)
82}
83
84pub fn flag(key: &str) -> bool {
87 match var(key) {
88 Some(v) => truthy(&v),
89 None => false,
90 }
91}
92
93pub fn flag_or(key: &str, default: bool) -> bool {
96 match var(key) {
97 Some(v) => truthy(&v),
98 None => default,
99 }
100}
101
102pub fn is_unset(key: &str) -> bool {
104 var(key).is_none()
105}
106
107pub fn parse_or<T: FromStr>(key: &str, default: T) -> T {
109 var(key).and_then(|s| s.parse().ok()).unwrap_or(default)
110}
111
112fn truthy(v: &str) -> bool {
113 let s = v.trim();
114 if s.is_empty() {
115 return false;
116 }
117 match s.to_ascii_lowercase().as_str() {
118 "0" | "false" | "off" | "no" => false,
119 "1" | "true" | "yes" | "on" => true,
120 _ if s.chars().all(|c| c.is_ascii_digit()) => s != "0",
121 _ => true, }
123}
124
125#[derive(Debug, Clone, Default)]
127pub struct RlxEnv {
128 pairs: Vec<(String, String)>,
129}
130
131impl RlxEnv {
132 pub fn new() -> Self {
133 Self::default()
134 }
135
136 pub fn set(mut self, key: impl AsRef<str>, value: impl Into<String>) -> Self {
137 self.pairs.push((normalize_key(key.as_ref()), value.into()));
138 self
139 }
140
141 pub fn flag(mut self, key: impl AsRef<str>, on: bool) -> Self {
142 self.pairs.push((
143 normalize_key(key.as_ref()),
144 if on { "1" } else { "0" }.into(),
145 ));
146 self
147 }
148
149 pub fn apply(self) {
151 for (k, v) in self.pairs {
152 set(&k, v);
153 }
154 }
155}
156
157pub struct RuntimeOverrides {
159 saved: Vec<(String, Option<String>)>,
160}
161
162impl RuntimeOverrides {
163 pub fn install(pairs: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>) -> Self {
165 let mut saved = Vec::new();
166 for (key, value) in pairs {
167 let key = normalize_key(key.as_ref());
168 let prev = map().read().ok().and_then(|g| g.get(&key).cloned());
169 saved.push((key.clone(), prev));
170 set(&key, value);
171 }
172 Self { saved }
173 }
174}
175
176impl Drop for RuntimeOverrides {
177 fn drop(&mut self) {
178 for (key, prev) in self.saved.drain(..) {
179 match prev {
180 Some(v) => set(&key, v),
181 None => unset(&key),
182 }
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use std::sync::Mutex;
191
192 static ENV_TEST_LOCK: Mutex<()> = Mutex::new(());
194
195 fn with_clean_overrides(f: impl FnOnce()) {
196 let _guard = ENV_TEST_LOCK.lock().expect("env test lock poisoned");
197 clear_overrides();
198 f();
199 clear_overrides();
200 }
201
202 #[test]
203 fn code_override_wins_over_process_env() {
204 with_clean_overrides(|| {
205 let _g = RuntimeOverrides::install([("VERBOSE", "2")]);
206 assert_eq!(var("RLX_VERBOSE"), Some("2".into()));
207 assert!(flag("RLX_VERBOSE"));
208 });
209 }
210
211 #[test]
212 fn flag_parses_falsy_override() {
213 with_clean_overrides(|| {
214 set("RLX_DISABLE_MPSGRAPH", "0");
215 assert!(!flag("RLX_DISABLE_MPSGRAPH"));
216 });
217 }
218
219 #[test]
220 fn rlx_env_bulk_apply() {
221 with_clean_overrides(|| {
222 RlxEnv::new()
223 .set("MPSGRAPH_MIN_FLOPS", "42")
224 .flag("USE_ICB", true)
225 .apply();
226 assert_eq!(parse_or("RLX_MPSGRAPH_MIN_FLOPS", 0u64), 42);
227 assert!(flag("RLX_USE_ICB"));
228 });
229 }
230}