Skip to content

Commit 6407cac

Browse files
committed
app: config: Add support for appending to the config string
In some cases, and specifically in the manifest.group-filter and manifest.project-filter options, it is sometimes useful to be able to append to a value instead of replacing it completely. For example, assuming one wants to add to an existing group filter, without this patch the user needs to do: (assuming the group filter is currently +unstable,-optional, and the user wants to add +extras). > west config manifest.group-filter > west config manifest.group-filter +unstable,-optional,+extras With this patch instead: > west config -a manifest.group-filter ,+extras Signed-off-by: Carles Cufi <[email protected]>
1 parent 79aa8c8 commit 6407cac

File tree

2 files changed

+64
-2
lines changed

2 files changed

+64
-2
lines changed

src/west/app/config.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@
4848
To set a value for <name>, type:
4949
west config <name> <value>
5050
51+
To append to a value for <name>, type:
52+
west config -a <name> <value>
53+
A value must exist in the selected configuration file in order to be able
54+
to append to it. The existing value can be empty.
55+
Examples:
56+
west config -a build.cmake-args -- " -DEXTRA_CFLAGS='-Wextra -g0' -DFOO=BAR"
57+
west config -a manifest.group-filter ,+optional
58+
5159
To list all options and their values:
5260
west config -l
5361
@@ -64,7 +72,7 @@
6472

6573
CONFIG_EPILOG = '''\
6674
If the configuration file to use is not set, reads use all three in
67-
precedence order, and writes use the local file.'''
75+
precedence order, and writes (including appends) use the local file.'''
6876

6977
ALL = ConfigFile.ALL
7078
SYSTEM = ConfigFile.SYSTEM
@@ -92,13 +100,14 @@ def do_add_parser(self, parser_adder):
92100
"action to perform (give at most one)"
93101
).add_mutually_exclusive_group()
94102

95-
96103
group.add_argument('-l', '--list', action='store_true',
97104
help='list all options and their values')
98105
group.add_argument('-d', '--delete', action='store_true',
99106
help='delete an option in one config file')
100107
group.add_argument('-D', '--delete-all', action='store_true',
101108
help="delete an option everywhere it's set")
109+
group.add_argument('-a', '--append', action='store_true',
110+
help='append to an existing value')
102111

103112
group = parser.add_argument_group(
104113
"configuration file to use (give at most one)"
@@ -129,13 +138,17 @@ def do_run(self, args, user_args):
129138
elif not args.name:
130139
self.parser.error('missing argument name '
131140
'(to list all options and values, use -l)')
141+
elif args.value is None and args.append:
142+
self.parser.error('-a requires both name and value')
132143

133144
if args.list:
134145
self.list(args)
135146
elif delete:
136147
self.delete(args)
137148
elif args.value is None:
138149
self.read(args)
150+
elif args.append:
151+
self.append(args)
139152
else:
140153
self.write(args)
141154

@@ -180,6 +193,16 @@ def read(self, args):
180193
self.dbg(f'{args.name} is unset')
181194
raise CommandError(returncode=1)
182195

196+
def append(self, args):
197+
self.check_config(args.name)
198+
where = args.configfile or LOCAL
199+
value = self.config.get(args.name, configfile=where)
200+
if value is None:
201+
self.die(f'option {args.name} not found in the {where.name.lower()} '
202+
'configuration file')
203+
args.value = value + args.value
204+
self.write(args)
205+
183206
def write(self, args):
184207
self.check_config(args.name)
185208
what = args.configfile or LOCAL

tests/test_config.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,45 @@ def test_local_creation_with_topdir():
229229
assert 'pytest' not in cfg(f=GLOBAL)
230230
assert cfg(f=LOCAL, topdir=str(topdir))['pytest']['key'] == 'val'
231231

232+
def test_append():
233+
update_testcfg('pytest', 'key', 'system', configfile=SYSTEM)
234+
update_testcfg('pytest', 'key', 'global', configfile=GLOBAL)
235+
update_testcfg('pytest', 'key', 'local', configfile=LOCAL)
236+
# Appending with no configfile specified should modify the local one
237+
cmd('config -a pytest.key ,bar')
238+
239+
# Only the local one will be modified
240+
assert cfg(f=SYSTEM)['pytest']['key'] == 'system'
241+
assert cfg(f=GLOBAL)['pytest']['key'] == 'global'
242+
assert cfg(f=LOCAL)['pytest']['key'] == 'local,bar'
243+
244+
# Test a more complex one, and at a particular configfile level
245+
update_testcfg('build', 'cmake-args', '-DCONF_FILE=foo.conf', configfile=GLOBAL)
246+
assert cfg(f=GLOBAL)['build']['cmake-args'] == '-DCONF_FILE=foo.conf'
247+
248+
# Use a list instead of a string to get quoting right
249+
cmd(['config', '--global', '-a', 'build.cmake-args', '--',
250+
' -DEXTRA_CFLAGS=\'-Wextra -g0\' -DFOO=BAR'])
251+
252+
assert cfg(f=GLOBAL)['build']['cmake-args'] == \
253+
'-DCONF_FILE=foo.conf -DEXTRA_CFLAGS=\'-Wextra -g0\' -DFOO=BAR'
254+
255+
def test_append_novalue():
256+
with pytest.raises(subprocess.CalledProcessError) as exc_info:
257+
cmd('config -a pytest.foo', stderr=subprocess.STDOUT)
258+
# Get the output into a variable to simplify pytest error messages
259+
err_msg = exc_info.value.output.decode("utf-8")
260+
assert '-a requires both name and value' in err_msg
261+
262+
def test_append_notfound():
263+
update_testcfg('pytest', 'key', 'val', configfile=LOCAL)
264+
with pytest.raises(subprocess.CalledProcessError) as exc_info:
265+
cmd('config -a pytest.foo bar', stderr=subprocess.STDOUT)
266+
# Get the output into a variable to simplify pytest error messages
267+
err_msg = exc_info.value.output.decode("utf-8")
268+
assert 'option pytest.foo not found in the local configuration file' in err_msg
269+
270+
232271
def test_delete_basic():
233272
# Basic deletion test: write local, verify global and system deletions
234273
# don't work, then delete local does work.

0 commit comments

Comments
 (0)