Rev 5750: Implement loading a config store from a string or a file. in file:///home/vila/src/bzr/experimental/config/

Vincent Ladeuil v.ladeuil+lp at free.fr
Mon Apr 4 12:12:58 UTC 2011


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

------------------------------------------------------------
revno: 5750
revision-id: v.ladeuil+lp at free.fr-20110404121257-m4htb561v7b4c2ur
parent: v.ladeuil+lp at free.fr-20110401144117-fnikf1os66212c5a
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: config-store
timestamp: Mon 2011-04-04 14:12:57 +0200
message:
  Implement loading a config store from a string or a file. 
-------------- next part --------------
=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2011-04-01 14:37:17 +0000
+++ b/bzrlib/config.py	2011-04-04 12:12:57 +0000
@@ -2029,6 +2029,79 @@
             self.orig[name] = self.get(name, None)
         del self.options[name]
 
+class Store(object):
+    """Abstract interface to persistent storage for configuration options."""
+
+    def __init__(self):
+        self.loaded = False
+
+    def load(self):
+        raise NotImplementedError(self.load)
+
+
+class ConfigObjStore(Store):
+
+    def __init__(self, transport, file_name):
+        """A config Store using ConfigObj for storage.
+
+        :param transport: The transport object where the config file is located.
+
+        :param file_name: The config file basename in the transport directory.
+        """
+        super(ConfigObjStore, self).__init__()
+        self.transport = transport
+        self.file_name = file_name
+        # No transient content is known initially
+        self._content = None
+
+    @classmethod
+    def from_string(cls, str_or_unicode, transport, file_name, save=False):
+        """Create a config store from a string.
+
+        :param str_or_unicode: A string representing the file content. This will
+            be utf-8 encoded internally.
+
+        :param transport: The transport object where the config file is located.
+
+        :param file_name: The configuration file basename.
+
+        :param _save: Whether the file should be saved upon creation.
+        """
+        conf = cls(transport=transport, file_name=file_name)
+        conf._create_from_string(str_or_unicode, save)
+        return conf
+
+    def _create_from_string(self, str_or_unicode, save):
+        # We just keep record the content waiting for load() to be called when
+        # needed
+        self._content = StringIO(str_or_unicode.encode('utf-8'))
+        # Some tests use in-memory configs, some other always need the config
+        # file to exist on disk.
+        if save:
+            self.save()
+
+    def load(self):
+        """Load the store from the associated file."""
+        if self.loaded:
+            return
+        if self._content is not None:
+            co_input = self._content
+        else:
+            # The config files are always stored utf8-encoded
+            co_input =  StringIO(self.transport.get_bytes(self.file_name))
+        try:
+            self._config_obj = ConfigObj(co_input, encoding='utf-8')
+        except configobj.ConfigObjError, e:
+            # FIXME: external_url should really accepts an optional relpath
+            # parameter (bug #750169) :-/ -- vila 2011-04-04
+            # The following will do in the interim but maybe we don't want to
+            # expose a path here but rather a config ID and its associated
+            # object (hand wawing).
+            file_path = os.path.join(self.transport.external_url(),
+                                     self.file_name)
+            raise errors.ParseConfigError(e.errors, file_path)
+        self.loaded = True
+
 
 class cmd_config(commands.Command):
     __doc__ = """Display, set or remove a configuration option.

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2011-03-12 23:58:55 +0000
+++ b/bzrlib/errors.py	2011-04-04 12:12:57 +0000
@@ -1766,12 +1766,12 @@
 
 class ParseConfigError(BzrError):
 
+    _fmt = "Error(s) parsing config file %(filename)s:\n%(errors)s"
+
     def __init__(self, errors, filename):
-        if filename is None:
-            filename = ""
-        message = "Error(s) parsing config file %s:\n%s" % \
-            (filename, ('\n'.join(e.msg for e in errors)))
-        BzrError.__init__(self, message)
+        BzrError.__init__(self)
+        self.filename = filename
+        self.errors = '\n'.join(e.msg for e in errors)
 
 
 class NoEmailInUsername(BzrError):

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2011-04-01 14:37:17 +0000
+++ b/bzrlib/tests/test_config.py	2011-04-04 12:12:57 +0000
@@ -1889,6 +1889,36 @@
         self.assertEquals(config._Created, section.orig['foo'])
 
 
+class TestStore(tests.TestCaseWithTransport):
+
+    # FIXME: parametrize against all valid (store, transport) combinations
+
+    def test_delayed_load(self):
+        self.build_tree_contents([('foo.conf', '')])
+        store = config.ConfigObjStore(self.get_transport(), 'foo.conf')
+        self.assertEquals(False, store.loaded)
+        store.load()
+        self.assertEquals(True, store.loaded)
+
+    def test_from_string_delayed_load(self):
+        store = config.ConfigObjStore.from_string('',
+            self.get_transport(), 'foo.conf')
+        self.assertEquals(False, store.loaded)
+        store.load()
+        self.assertEquals(True, store.loaded)
+        # We use from_string and don't save, so the file shouldn't be created
+        self.failIfExists('foo.conf')
+
+    def test_invalid_content(self):
+        self.build_tree_contents([('foo.conf', 'this is invalid !')])
+        store = config.ConfigObjStore(self.get_transport(), 'foo.conf')
+        self.assertEquals(False, store.loaded)
+        exc = self.assertRaises(errors.ParseConfigError, store.load)
+        self.assertEndsWith(exc.filename, 'foo.conf')
+        # And the load failed
+        self.assertEquals(False, store.loaded)
+
+
 class TestConfigGetOptions(tests.TestCaseWithTransport, TestOptionsMixin):
 
     def setUp(self):



More information about the bazaar-commits mailing list