use std::path::{ Path, PathBuf };
use crate::config_catalog::SettingDef;
use crate::settings_io::read_all_settings;
#[ derive( Debug, Clone, Copy, PartialEq, Eq ) ]
pub enum Layer
{
Env,
Project,
User,
Default,
Absent,
}
impl core::fmt::Display for Layer
{
#[ inline ]
fn fmt( &self, f : &mut core::fmt::Formatter< '_ > ) -> core::fmt::Result
{
f.write_str( match self
{
Self::Env => "env",
Self::Project => "project",
Self::User => "user",
Self::Default => "default",
Self::Absent => "absent",
} )
}
}
#[ derive( Debug ) ]
pub struct ResolvedValue
{
pub value : Option< String >,
pub source : Layer,
}
#[ inline ]
#[ must_use ]
pub fn resolve(
key : &str,
home_dir : &Path,
cwd : &Path,
catalog : &[ SettingDef ],
) -> ResolvedValue
{
if let Some( env_var ) = catalog.iter().find( | d | d.key == key ).and_then( | d | d.env_var )
{
if let Ok( val ) = std::env::var( env_var )
{
if !val.is_empty()
{
return ResolvedValue { value : Some( val ), source : Layer::Env };
}
}
}
if let Some( val ) = find_in_project_config( key, cwd )
{
return ResolvedValue { value : Some( val ), source : Layer::Project };
}
let user_settings = home_dir.join( ".claude" ).join( "settings.json" );
if let Ok( pairs ) = read_all_settings( &user_settings )
{
if let Some( ( _, v ) ) = pairs.into_iter().find( | ( k, _ ) | k == key )
{
return ResolvedValue { value : Some( v ), source : Layer::User };
}
}
if let Some( def ) = catalog.iter().find( | d | d.key == key ).and_then( | d | d.default )
{
return ResolvedValue { value : Some( def.to_string() ), source : Layer::Default };
}
ResolvedValue { value : None, source : Layer::Absent }
}
#[ inline ]
#[ must_use ]
pub fn resolve_all(
home_dir : &Path,
cwd : &Path,
catalog : &[ SettingDef ],
) -> Vec< ( String, ResolvedValue ) >
{
let mut keys : Vec< String > = catalog.iter().map( | d | d.key.to_string() ).collect();
if let Some( project_file ) = find_project_config_file( cwd )
{
if let Ok( pairs ) = read_all_settings( &project_file )
{
for ( k, _ ) in pairs
{
if !keys.iter().any( | s | s == &k ) { keys.push( k ); }
}
}
}
let user_settings = home_dir.join( ".claude" ).join( "settings.json" );
if let Ok( pairs ) = read_all_settings( &user_settings )
{
for ( k, _ ) in pairs
{
if !keys.iter().any( | s | s == &k ) { keys.push( k ); }
}
}
keys.sort();
keys.into_iter()
.map( | k |
{
let resolved = resolve( &k, home_dir, cwd, catalog );
( k, resolved )
} )
.collect()
}
fn find_in_project_config( key : &str, cwd : &Path ) -> Option< String >
{
let file = find_project_config_file( cwd )?;
let pairs = read_all_settings( &file ).ok()?;
pairs.into_iter().find( | ( k, _ ) | k == key ).map( | ( _, v ) | v )
}
fn find_project_config_file( cwd : &Path ) -> Option< PathBuf >
{
let mut dir = cwd.to_path_buf();
loop
{
let candidate = dir.join( ".claude" ).join( "settings.json" );
if candidate.exists()
{
return Some( candidate );
}
if dir.join( ".git" ).exists()
{
return None;
}
match dir.parent()
{
Some( parent ) => dir = parent.to_path_buf(),
None => return None,
}
}
}