From c105e8a3b4a8f98d8f9ab68964868371f22e504f Mon Sep 17 00:00:00 2001 From: Andrew Halberstadt Date: Mon, 28 Mar 2016 10:52:16 -0400 Subject: [PATCH] Bug 1255450 - [mach] Implement 'wildcard' settings for enabling sections with user-defined options, r=gps Some sections should support user-defined options. For example, in an [alias] section, the option names are not well-defined, rather specified by the user. This patch allows user-defined option names for any section that has a 'section.*' option defined. Even with 'section.*', option types are still well-defined. MozReview-Commit-ID: L34W9v9Fy28 --HG-- extra : rebase_source : 9333f552edead9bf1cf464e28ef8fbbb9bed5597 --- python/mach/docs/settings.rst | 20 ++++++++++++++++++++ python/mach/mach/config.py | 22 ++++++++++++++-------- python/mach/mach/test/test_config.py | 26 ++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/python/mach/docs/settings.rst b/python/mach/docs/settings.rst index bc43ebae99f4..e70a7c473cf5 100644 --- a/python/mach/docs/settings.rst +++ b/python/mach/docs/settings.rst @@ -54,6 +54,26 @@ pairs to add to the setting's metadata. The following keys may be specified in the ``extra`` dict: * ``choices`` - A set of allowed values for the setting. +Wildcards +--------- + +Sometimes a section should allow arbitrarily defined options from the user, such +as the ``alias`` section mentioned above. To define a section like this, use ``*`` +as the option name. For example: + +.. parsed-literal:: + + ('foo.*', 'string') + +This allows configuration files like this: + +.. parsed-literal:: + + [foo] + arbitrary1 = some string + arbitrary2 = some other string + + Accessing Settings ================== diff --git a/python/mach/mach/config.py b/python/mach/mach/config.py index 8e992b5efeab..28925cd4416b 100644 --- a/python/mach/mach/config.py +++ b/python/mach/mach/config.py @@ -213,6 +213,9 @@ class ConfigSettings(collections.Mapping): object.__setattr__(self, '_name', name) object.__setattr__(self, '_settings', settings) + wildcard = any(s == '*' for s in self._settings) + object.__setattr__(self, '_wildcard', wildcard) + @property def options(self): try: @@ -220,11 +223,15 @@ class ConfigSettings(collections.Mapping): except NoSectionError: return [] - def _validate(self, option, value): - if option not in self._settings: - raise KeyError('Option not registered with provider: %s' % option) + def get_meta(self, option): + if option in self._settings: + return self._settings[option] + if self._wildcard: + return self._settings['*'] + raise KeyError('Option not registered with provider: %s' % option) - meta = self._settings[option] + def _validate(self, option, value): + meta = self.get_meta(option) meta['type_cls'].validate(value) if 'choices' in meta and value not in meta['choices']: @@ -242,7 +249,7 @@ class ConfigSettings(collections.Mapping): return self._config.has_option(self._name, k) def __getitem__(self, k): - meta = self._settings[k] + meta = self.get_meta(k) if self._config.has_option(self._name, k): v = meta['type_cls'].from_config(self._config, self._name, k) @@ -255,11 +262,10 @@ class ConfigSettings(collections.Mapping): self._validate(k, v) return v - def __setitem__(self, k, v): self._validate(k, v) + meta = self.get_meta(k) - meta = self._settings[k] if not self._config.has_section(self._name): self._config.add_section(self._name) @@ -416,7 +422,7 @@ class ConfigSettings(collections.Mapping): def option_help(self, section, option): """Obtain the translated help messages for an option.""" - meta = self[section]._settings[option] + meta = self[section].get_meta(option) # Providers should always have an en-US translation. If they don't, # they are coded wrong and this will raise. diff --git a/python/mach/mach/test/test_config.py b/python/mach/mach/test/test_config.py index 7d5f1e18b82a..9ec5201c35f1 100644 --- a/python/mach/mach/test/test_config.py +++ b/python/mach/mach/test/test_config.py @@ -88,6 +88,14 @@ class Provider4(object): ] +@SettingsProvider +class Provider5(object): + config_settings = [ + ('foo.*', 'string'), + ('foo.bar', 'string'), + ] + + class TestConfigSettings(unittest.TestCase): def test_empty(self): s = ConfigSettings() @@ -211,6 +219,24 @@ class TestConfigSettings(unittest.TestCase): foo.abc = 'b' foo.xyz = 'y' + def test_wildcard_options(self): + s = ConfigSettings() + s.register_provider(Provider5) + + foo = s.foo + + self.assertIn('*', foo._settings) + self.assertNotIn('*', foo) + + foo.baz = 'value1' + foo.bar = 'value2' + + self.assertIn('baz', foo) + self.assertEqual(foo.baz, 'value1') + + self.assertIn('bar', foo) + self.assertEqual(foo.bar, 'value2') + def test_file_reading_single(self): temp = NamedTemporaryFile(mode='wt') temp.write(CONFIG1)