Skip to content

Commit c655a64

Browse files
committed
fix: dedicated env var provider
1 parent 2c355c9 commit c655a64

File tree

1 file changed

+94
-29
lines changed

1 file changed

+94
-29
lines changed

src/settings.rs

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
use std::path::{Path, PathBuf};
1111
use std::sync::{LazyLock, RwLock};
1212

13-
use figment::providers::{Env, Serialized};
13+
use figment::providers::Serialized;
1414
use figment::{Figment, Profile, Provider, value::Map};
15+
use prek_consts::env_vars::EnvVars;
1516
use serde::{Deserialize, Serialize};
1617

1718
/// Global settings instance, initialized lazily.
@@ -162,20 +163,99 @@ impl Provider for PyProjectProvider {
162163
}
163164
}
164165

166+
/// Custom provider for PREK_* environment variables that handles
167+
/// special cases like comma-separated lists and boolean values.
168+
struct PrekEnvProvider;
169+
170+
impl Provider for PrekEnvProvider {
171+
fn metadata(&self) -> figment::Metadata {
172+
figment::Metadata::named("PREK_* environment variables")
173+
}
174+
175+
fn data(&self) -> Result<Map<Profile, figment::value::Dict>, figment::Error> {
176+
use figment::value::{Dict, Value};
177+
178+
let mut dict = Dict::new();
179+
180+
// PREK_SKIP or SKIP - comma-separated list
181+
if let Some(val) = EnvVars::var(EnvVars::PREK_SKIP)
182+
.ok()
183+
.or_else(|| EnvVars::var(EnvVars::SKIP).ok())
184+
{
185+
let items: Vec<Value> = val
186+
.split(',')
187+
.map(|s| s.trim().to_string())
188+
.filter(|s| !s.is_empty())
189+
.map(Value::from)
190+
.collect();
191+
dict.insert("skip".into(), Value::from(items));
192+
}
193+
194+
// PREK_HOME or PRE_COMMIT_HOME (EnvVars::var handles the fallback)
195+
if let Ok(val) = EnvVars::var(EnvVars::PREK_HOME) {
196+
dict.insert("home".into(), Value::from(val));
197+
}
198+
199+
if let Ok(val) = EnvVars::var(EnvVars::PREK_COLOR) {
200+
dict.insert("color".into(), Value::from(val));
201+
}
202+
203+
// PREK_ALLOW_NO_CONFIG - boolish values (EnvVars::var handles PRE_COMMIT fallback)
204+
if let Some(b) = EnvVars::var_as_bool(EnvVars::PREK_ALLOW_NO_CONFIG) {
205+
dict.insert("allow-no-config".into(), Value::from(b));
206+
}
207+
208+
// PREK_NO_CONCURRENCY - boolish values (EnvVars::var handles PRE_COMMIT fallback)
209+
if let Some(b) = EnvVars::var_as_bool(EnvVars::PREK_NO_CONCURRENCY) {
210+
dict.insert("no-concurrency".into(), Value::from(b));
211+
}
212+
213+
if let Some(b) = EnvVars::var_as_bool(EnvVars::PREK_NO_FAST_PATH) {
214+
dict.insert("no-fast-path".into(), Value::from(b));
215+
}
216+
217+
if let Ok(val) = EnvVars::var(EnvVars::PREK_UV_SOURCE) {
218+
dict.insert("uv-source".into(), Value::from(val));
219+
}
220+
221+
if let Some(b) = EnvVars::var_as_bool(EnvVars::PREK_NATIVE_TLS) {
222+
dict.insert("native-tls".into(), Value::from(b));
223+
}
224+
225+
if let Ok(val) = EnvVars::var(EnvVars::PREK_CONTAINER_RUNTIME) {
226+
dict.insert("container-runtime".into(), Value::from(val));
227+
}
228+
229+
let mut map = Map::new();
230+
map.insert(Profile::Default, dict);
231+
Ok(map)
232+
}
233+
}
234+
235+
/// Parse a boolean from a string (supports 1/0, true/false, yes/no, on/off)
236+
fn parse_boolish(val: &str) -> Option<bool> {
237+
const TRUE_LITERALS: [&str; 6] = ["y", "yes", "t", "true", "on", "1"];
238+
const FALSE_LITERALS: [&str; 6] = ["n", "no", "f", "false", "off", "0"];
239+
240+
let val = val.to_lowercase();
241+
if TRUE_LITERALS.contains(&val.as_str()) {
242+
Some(true)
243+
} else if FALSE_LITERALS.contains(&val.as_str()) {
244+
Some(false)
245+
} else {
246+
None
247+
}
248+
}
249+
165250
impl Settings {
166251
/// Initialize settings from the given working directory.
167252
///
168253
/// This should be called early in `main()` before any settings are accessed.
169254
/// If not called, settings will use the current directory when first accessed.
170255
pub fn init(working_dir: &Path) -> Result<(), figment::Error> {
171256
let settings = Self::discover(working_dir)?;
172-
173-
let mut guard = SETTINGS.write().expect("settings lock poisoned");
174-
*guard = settings;
175-
176-
let mut init_guard = INITIALIZED.write().expect("initialized lock poisoned");
177-
*init_guard = true;
178-
257+
*SETTINGS.write().expect("settings lock poisoned") = settings;
258+
*INITIALIZED.write().expect("initialized lock poisoned") = true;
179259
Ok(())
180260
}
181261

@@ -187,15 +267,9 @@ impl Settings {
187267
cli_overrides: CliOverrides,
188268
) -> Result<(), figment::Error> {
189269
let figment = Self::build_figment(working_dir).merge(Serialized::defaults(cli_overrides));
190-
191270
let settings: Settings = figment.extract()?;
192-
193-
let mut guard = SETTINGS.write().expect("settings lock poisoned");
194-
*guard = settings;
195-
196-
let mut init_guard = INITIALIZED.write().expect("initialized lock poisoned");
197-
*init_guard = true;
198-
271+
*SETTINGS.write().expect("settings lock poisoned") = settings;
272+
*INITIALIZED.write().expect("initialized lock poisoned") = true;
199273
Ok(())
200274
}
201275

@@ -214,25 +288,16 @@ impl Settings {
214288
let _ = Self::init(&cwd);
215289
}
216290
}
217-
218-
let guard = SETTINGS.read().expect("settings lock poisoned");
219-
guard.clone()
291+
SETTINGS.read().expect("settings lock poisoned").clone()
220292
}
221293

222294
/// Build the figment for the given working directory.
223295
fn build_figment(working_dir: &Path) -> Figment {
224296
let mut figment = Figment::new()
225297
// Lowest precedence: built-in defaults
226298
.merge(Serialized::defaults(Settings::default()))
227-
// Next: environment variables
228-
.merge(
229-
Env::prefixed("PREK_")
230-
.map(|key| {
231-
// Convert PREK_FOO_BAR to foo-bar for serde
232-
key.as_str().to_lowercase().replace('_', "-").into()
233-
})
234-
.split(","), // Allow comma-separated lists for `skip`
235-
);
299+
// Next: environment variables (custom provider for special handling)
300+
.merge(PrekEnvProvider);
236301

237302
// Walk up to find pyproject.toml
238303
let mut current = Some(working_dir);
@@ -349,7 +414,7 @@ color = "always"
349414
fn test_env_var_loading() {
350415
figment::Jail::expect_with(|jail| {
351416
jail.set_env("PREK_SKIP", "hook1,hook2");
352-
jail.set_env("PREK_NO_CONCURRENCY", "true");
417+
jail.set_env("PREK_NO_CONCURRENCY", "1");
353418
jail.set_env("PREK_COLOR", "never");
354419

355420
let settings = Settings::discover(jail.directory())?;

0 commit comments

Comments
 (0)