1010use std:: path:: { Path , PathBuf } ;
1111use std:: sync:: { LazyLock , RwLock } ;
1212
13- use figment:: providers:: { Env , Serialized } ;
13+ use figment:: providers:: Serialized ;
1414use figment:: { Figment , Profile , Provider , value:: Map } ;
15+ use prek_consts:: env_vars:: EnvVars ;
1516use 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+
165250impl 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