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