Rev 5451: Implement config.get_sections() to clarify how sections can be used. in file:///home/vila/src/bzr/experimental/config/

Vincent Ladeuil v.ladeuil+lp at free.fr
Sat Oct 2 22:08:35 BST 2010


At file:///home/vila/src/bzr/experimental/config/

------------------------------------------------------------
revno: 5451
revision-id: v.ladeuil+lp at free.fr-20101002210835-nqy0fqas09e7ijj1
parent: v.ladeuil+lp at free.fr-20101001095208-vupdrq1jtle74gk7
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: config-modify
timestamp: Sat 2010-10-02 23:08:35 +0200
message:
  Implement config.get_sections() to clarify how sections can be used.
  
  * bzrlib/tests/test_config.py:
  (TestConfigGetSections): Expose the various supported section
  use-cases.
  
  * bzrlib/config.py:
  (IniBasedConfig.get_sections, GlobalConfig.get_sections)
  (LocationConfig.get_sections, BranchConfig.get_sections): Properly
  capture the different section usages exposed by the various config
  objects.
-------------- next part --------------
=== modified file 'NEWS'
--- a/NEWS	2010-10-01 09:52:08 +0000
+++ b/NEWS	2010-10-02 21:08:35 +0000
@@ -43,10 +43,6 @@
 API Changes
 ***********
 
-* ``bzrlib.config.IniBasedConfig`` objects now implement ``get_options`` that
-  permit querying them for the options defined and their values.
- (Vincent Ladeuil)
-
 Internals
 *********
 
@@ -54,6 +50,12 @@
   longer include the "log" information for tests which are considered to
   be 'successes' (success, xfail, skip, etc) (John Arbash Meinel)
 
+* ``bzrlib.config.IniBasedConfig`` objects now implement ``get_options`` that
+  permit querying them for the options defined and their values and
+  ``get_sections()`` that return a list of (name, section) for the sections
+  walked by get_user_option() and used by set_user_option().
+  (Vincent Ladeuil)
+
 Testing
 *******
 

=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2010-10-01 09:52:08 +0000
+++ b/bzrlib/config.py	2010-10-02 21:08:35 +0000
@@ -450,6 +450,24 @@
         """Override this to define the section used by the config."""
         return "DEFAULT"
 
+    def get_sections(self, name=None):
+        """Returns an iterator of the sections specified by ``name``.
+
+        :param name: The section name. If None is supplied, the default
+            configurations are yielded.
+
+        :return: A tuple (name, section) for all sections that will we walked
+            by user_get_option() in the 'right' order. The first one is where
+            set_user_option() will update the value.
+        """
+        parser = self._get_parser()
+        if name is not None:
+            yield (name, parser[name])
+        else:
+            # No section name has been given so we fallback to the configobj
+            # itself which holds the variables defined outside of any section.
+            yield (None, parser)
+
     def get_options(self, sections=None):
         """Return an ordered list of (name, value, section, config_id) tuples.
 
@@ -706,6 +724,19 @@
         self._write_config_file()
 
 
+    def get_sections(self, name=None):
+        """See IniBasedConfig.get_sections()."""
+        parser = self._get_parser()
+        # We don't give access to options defined outside of any section, we
+        # used the DEFAULT section by... default.
+        if name in (None, 'DEFAULT'):
+            # This could happen for an empty file where the DEFAULT section
+            # doesn't exist yet. So we force DEFAULT when yielding
+            name = 'DEFAULT'
+            parser[name]= {}
+        yield (name, parser[name])
+
+
 class LocationConfig(LockableConfig):
     """A configuration object that gives the policy for a location."""
 
@@ -783,6 +814,14 @@
                 pass
         return sections
 
+    def get_sections(self, name=None):
+        """See IniBasedConfig.get_sections()."""
+        # We ignore the name here as the only sections handled are named with
+        # the location path and we don't expose embedded sections either.
+        parser = self._get_parser()
+        for name, extra_path in self._get_matching_sections():
+            yield (name, parser[name])
+
     def _get_option_policy(self, section, option_name):
         """Return the policy for the given (section, option_name) pair."""
         # check for the old 'recurse=False' flag
@@ -946,6 +985,12 @@
                 return value
         return None
 
+    def get_sections(self, name=None):
+        """See IniBasedConfig.get_sections()."""
+        for source in self.option_sources:
+            for section in source().get_sections(name):
+                yield section
+
     def get_options(self, sections=None):
         opts = []
         # First the locations options
@@ -1605,8 +1650,8 @@
     """A Config that reads/writes a config file on a Transport.
 
     It is a low-level object that considers config data to be name/value pairs
-    that may be associated with a section.  Assigning meaning to the these
-    values is done at higher levels like TreeConfig.
+    that may be associated with a section.  Assigning meaning to these values
+    is done at higher levels like TreeConfig.
     """
 
     def __init__(self, transport, filename):

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2010-10-01 09:52:08 +0000
+++ b/bzrlib/tests/test_config.py	2010-10-02 21:08:35 +0000
@@ -1471,15 +1471,18 @@
         self.assertIs(None, bzrdir_config.get_default_stack_on())
 
 
-class TestConfigGetOptions(tests.TestCaseWithTransport):
+class TestWithConfigs(tests.TestCaseWithTransport):
 
     def setUp(self):
-        super(TestConfigGetOptions, self).setUp()
+        super(TestWithConfigs, self).setUp()
         self.global_config = config.GlobalConfig()
-        self.tree = self.make_branch_and_tree('.')
+        self.tree = self.make_branch_and_tree('tree')
         self.branch_config = config.BranchConfig(self.tree.branch)
         self.locations_config = config.LocationConfig(self.tree.basedir)
 
+
+class TestConfigGetOptions(TestWithConfigs):
+
     def assertOptions(self, expected, conf):
         actual = list(conf.get_options())
         self.assertEqual(expected, actual)
@@ -1532,6 +1535,72 @@
             self.branch_config)
 
 
+class TestConfigGetSections(TestWithConfigs):
+
+    def assertSectionNames(self, expected, conf, name=None):
+        """Check which sections are returned for a given config.
+
+        If fallback configurations exist their sections can be included.
+
+        :param expected: A list of section names.
+
+        :param conf: The configuration that will be queried.
+
+        :param name: An optional section name that will be passed to
+            get_sections().
+        """
+        sections = list(conf.get_sections(name))
+        self.assertLength(len(expected), sections)
+        self.assertEqual(expected, [name for name, section in sections])
+
+    def test_global_default_section(self):
+        self.assertSectionNames(['DEFAULT'], self.global_config)
+
+    def test_locations_default_section(self):
+        # No sections are defined in an empty file
+        self.assertSectionNames([], self.locations_config)
+
+    def test_locations_named_section(self):
+        self.locations_config.set_user_option('file', 'locations')
+        self.assertSectionNames([self.tree.basedir], self.locations_config)
+
+    def test_locations_matching_sections(self):
+        loc_config = self.locations_config
+        loc_config.set_user_option('file', 'locations')
+        # We need to cheat a bit here to create an option in sections above and
+        # below the 'location' one.
+        parser = loc_config._get_parser()
+        # locations.cong deals with '/' ignoring native os.sep
+        location_names = self.tree.basedir.split('/')
+        parent = '/'.join(location_names[:-1])
+        child = '/'.join(location_names + ['child'])
+        parser[parent] = {}
+        parser[parent]['file'] = 'parent'
+        parser[child] = {}
+        parser[child]['file'] = 'child'
+        self.assertSectionNames([self.tree.basedir, parent], loc_config)
+
+    def test_branch_data_default_section(self):
+        self.assertSectionNames([None],
+                                self.branch_config._get_branch_data_config())
+
+    def test_branch_default_sections(self):
+        # No sections are defined in an empty locations file
+        self.assertSectionNames([None, 'DEFAULT'],
+                                self.branch_config)
+        # Unless we define an option
+        self.branch_config._get_location_config().set_user_option(
+            'file', 'locations')
+        self.assertSectionNames([self.tree.basedir, None, 'DEFAULT'],
+                                self.branch_config)
+
+    def test_global_named_section(self):
+        # We need to cheat as the API doesn't give direct access to sections
+        # other than DEFAULT.
+        self.global_config.set_alias('bazaar', 'bzr')
+        self.assertSectionNames(['ALIASES'], self.global_config, 'ALIASES')
+
+
 class TestAuthenticationConfigFile(tests.TestCase):
     """Test the authentication.conf file matching"""
 



More information about the bazaar-commits mailing list