@@ -68,63 +68,81 @@ def parse_key(dotted_name: str):
6868
6969 @staticmethod
7070 def from_path (path : Path | None ) -> '_InternalCF | None' :
71- return _InternalCF (path ) if path and path . exists () else None
71+ return _InternalCF (path ) if path else None
7272
7373 def __init__ (self , path : Path ):
74- self .path = path
7574 self .cp = _configparser ()
76- read_files = self .cp .read (path , encoding = 'utf-8' )
77- if len (read_files ) != 1 :
78- raise FileNotFoundError (path )
75+ self .dropin_cp = _configparser ()
76+ self .path = path if path .exists () else None
77+ dropin_dir = Path (f'{ path } .d' )
78+ self .dropin_dir = dropin_dir if dropin_dir .exists () else None
79+ self .dropin_paths = []
80+ if self .dropin_dir :
81+ # dropin configs are applied in alphabetical order
82+ for conf in sorted (self .dropin_dir .iterdir ()):
83+ # only consider .conf files
84+ if conf .suffix .lower () == '.conf' :
85+ self .dropin_paths .append (self .dropin_dir / conf )
86+ self ._read ()
87+
88+ def _read (self ):
89+ if self .path :
90+ self .cp .read (self .path , encoding = 'utf-8' )
91+ if self .dropin_paths :
92+ self .dropin_cp .read (self .dropin_paths , encoding = 'utf-8' )
93+
94+ def _write (self ):
95+ if not self .path :
96+ raise WestNotFound ('No config file exists that can be written' )
97+ with open (self .path , 'w' , encoding = 'utf-8' ) as f :
98+ self .cp .write (f )
7999
80100 def __contains__ (self , option : str ) -> bool :
81101 section , key = _InternalCF .parse_key (option )
82102
83- return section in self .cp and key in self .cp [section ]
103+ if section in self .cp and key in self .cp [section ]:
104+ return True
105+ return section in self .dropin_cp and key in self .dropin_cp [section ]
84106
85107 def get (self , option : str ):
86- return self ._get (option , self .cp .get )
108+ return self ._get (option , self .cp .get , self . dropin_cp . get )
87109
88110 def getboolean (self , option : str ):
89- return self ._get (option , self .cp .getboolean )
111+ return self ._get (option , self .cp .getboolean , self . dropin_cp . getboolean )
90112
91113 def getint (self , option : str ):
92- return self ._get (option , self .cp .getint )
114+ return self ._get (option , self .cp .getint , self . dropin_cp . getint )
93115
94116 def getfloat (self , option : str ):
95- return self ._get (option , self .cp .getfloat )
117+ return self ._get (option , self .cp .getfloat , self . dropin_cp . getfloat )
96118
97- def _get (self , option , getter ):
119+ def _get (self , option , config_getter , dropin_getter ):
98120 section , key = _InternalCF .parse_key (option )
99-
100- try :
101- return getter (section , key )
102- except (configparser .NoOptionError , configparser .NoSectionError ) as err :
103- raise KeyError (option ) from err
121+ if section in self .cp and key in self .cp [section ]:
122+ getter = config_getter
123+ elif section in self .dropin_cp and key in self .dropin_cp [section ]:
124+ getter = dropin_getter
125+ else :
126+ raise KeyError (option )
127+ return getter (section , key )
104128
105129 def set (self , option : str , value : Any ):
106130 section , key = _InternalCF .parse_key (option )
107-
108131 if section not in self .cp :
109132 self .cp [section ] = {}
110-
111133 self .cp [section ][key ] = value
112-
113- with open (self .path , 'w' , encoding = 'utf-8' ) as f :
114- self .cp .write (f )
134+ self ._write ()
115135
116136 def delete (self , option : str ):
117137 section , key = _InternalCF .parse_key (option )
118-
119- if section not in self .cp :
138+ if (section not in self .cp ) or (key not in self .cp [section ]):
120139 raise KeyError (option )
121140
122141 del self .cp [section ][key ]
123142 if not self .cp [section ].items ():
124143 del self .cp [section ]
125144
126- with open (self .path , 'w' , encoding = 'utf-8' ) as f :
127- self .cp .write (f )
145+ self ._write ()
128146
129147class ConfigFile (Enum ):
130148 '''Types of west configuration file.
@@ -362,13 +380,14 @@ def _copy_to_configparser(self, cp: configparser.ConfigParser) -> None:
362380 # function-and-global-state APIs.
363381
364382 def load (cf : _InternalCF ):
365- for section , contents in cf .cp .items ():
366- if section == 'DEFAULT' :
367- continue
368- if section not in cp :
369- cp .add_section (section )
370- for key , value in contents .items ():
371- cp [section ][key ] = value
383+ for cp in [cf .dropin_cp , cf .cp ]:
384+ for section , contents in cp .items ():
385+ if section == 'DEFAULT' :
386+ continue
387+ if section not in cp :
388+ cp .add_section (section )
389+ for key , value in contents .items ():
390+ cp [section ][key ] = value
372391
373392 if self ._system :
374393 load (self ._system )
@@ -415,11 +434,12 @@ def _cf_to_dict(cf: _InternalCF | None) -> dict[str, Any]:
415434 ret : dict [str , Any ] = {}
416435 if cf is None :
417436 return ret
418- for section , contents in cf .cp .items ():
419- if section == 'DEFAULT' :
420- continue
421- for key , value in contents .items ():
422- ret [f'{ section } .{ key } ' ] = value
437+ for cp in [cf .dropin_cp , cf .cp ]:
438+ for section , contents in cp .items ():
439+ if section == 'DEFAULT' :
440+ continue
441+ for key , value in contents .items ():
442+ ret [f'{ section } .{ key } ' ] = value
423443 return ret
424444
425445
0 commit comments