Skip to content

Commit 682c916

Browse files
adding support for .d config files
config files in acccording .d folders are considered automatically, e.g. by default for local config './west/config.d' respectively for global config '~/.westconfig.d'
1 parent 67788ff commit 682c916

File tree

2 files changed

+56
-5
lines changed

2 files changed

+56
-5
lines changed

src/west/configuration.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,33 @@ def parse_key(dotted_name: str):
6868

6969
@staticmethod
7070
def from_path(path: Optional[Path]) -> Optional['_InternalCF']:
71-
return _InternalCF(path) if path and path.exists() else None
71+
config = _InternalCF(path) if path and path.exists() else None
72+
if not config:
73+
return None
74+
path_d = Path(f'{path}.d')
75+
if path_d.exists():
76+
# update config with values from config files under .d in
77+
# lexicographic order
78+
for conf in sorted(path_d.iterdir()):
79+
config.cp.read(path_d / conf, encoding='utf-8')
80+
# finally overwrite with values from actual config again
81+
config.cp.read(path, encoding='utf-8')
82+
return config
7283

7384
def __init__(self, path: Path):
7485
self.path = path
7586
self.cp = _configparser()
87+
self._read(path)
88+
89+
def _read(self, path: Path):
7690
read_files = self.cp.read(path, encoding='utf-8')
7791
if len(read_files) != 1:
7892
raise FileNotFoundError(path)
7993

94+
def _write(self):
95+
with open(self.path, 'w', encoding='utf-8') as f:
96+
self.cp.write(f)
97+
8098
def __contains__(self, option: str) -> bool:
8199
section, key = _InternalCF.parse_key(option)
82100

@@ -110,8 +128,7 @@ def set(self, option: str, value: Any):
110128

111129
self.cp[section][key] = value
112130

113-
with open(self.path, 'w', encoding='utf-8') as f:
114-
self.cp.write(f)
131+
self._write()
115132

116133
def delete(self, option: str):
117134
section, key = _InternalCF.parse_key(option)
@@ -123,8 +140,7 @@ def delete(self, option: str):
123140
if not self.cp[section].items():
124141
del self.cp[section]
125142

126-
with open(self.path, 'w', encoding='utf-8') as f:
127-
self.cp.write(f)
143+
self._write()
128144

129145
class ConfigFile(Enum):
130146
'''Types of west configuration file.

tests/test_config.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import os
77
import pathlib
88
import subprocess
9+
import textwrap
910
from typing import Any, Optional
1011

1112
import pytest
@@ -208,6 +209,40 @@ def test_local_creation():
208209
assert 'pytest' not in cfg(f=GLOBAL)
209210
assert cfg(f=LOCAL)['pytest']['key'] == 'val'
210211

212+
TEST_CASES_CONFIG_D = [
213+
# (flag, env_var)
214+
('', 'WEST_CONFIG_LOCAL'),
215+
('--local', 'WEST_CONFIG_LOCAL'),
216+
('--system', 'WEST_CONFIG_SYSTEM'),
217+
('--global', 'WEST_CONFIG_GLOBAL'),
218+
]
219+
220+
@pytest.mark.parametrize("test_case", TEST_CASES_CONFIG_D)
221+
def test_config_d_local(test_case):
222+
flag, env_var = test_case
223+
config_path = os.environ[env_var]
224+
config_d_dir = pathlib.Path(f'{config_path}.d')
225+
config_d_dir.mkdir()
226+
227+
# write value in actual config file
228+
cmd(f'config {flag} pytest.key val')
229+
stdout = cmd(f'config {flag} pytest.key')
230+
assert 'val' == stdout.rstrip()
231+
232+
# create a dropin config under .d
233+
with open(config_d_dir / 'some-file', 'w') as conf:
234+
conf.write(textwrap.dedent('''
235+
[pytest]
236+
key = from-.d
237+
another-key = from-.d
238+
'''))
239+
240+
# dropin config value is only used if option is not set in actual config
241+
stdout = cmd(f'config {flag} pytest.key')
242+
assert 'val' == stdout.rstrip()
243+
stdout = cmd(f'config {flag} pytest.another-key')
244+
assert 'from-.d' == stdout.rstrip()
245+
211246
def test_local_creation_with_topdir():
212247
# Like test_local_creation, with a specified topdir.
213248

0 commit comments

Comments
 (0)