Rev 6252: (vila) Implement 'relpath' as a section local option. (Vincent Ladeuil) in file:///srv/pqm.bazaar-vcs.org/archives/thelove/bzr/%2Btrunk/

Patch Queue Manager pqm at pqm.ubuntu.com
Thu Nov 10 09:48:32 UTC 2011


At file:///srv/pqm.bazaar-vcs.org/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 6252 [merge]
revision-id: pqm at pqm.ubuntu.com-20111110094831-vrdth18b7o44qi70
parent: pqm at pqm.ubuntu.com-20111109160934-uzl2bf236oib7u3x
parent: v.ladeuil+lp at free.fr-20111105161211-b4b82psc3mw23i37
committer: Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Thu 2011-11-10 09:48:31 +0000
message:
  (vila) Implement 'relpath' as a section local option. (Vincent Ladeuil)
modified:
  bzrlib/config.py               config.py-20051011043216-070c74f4e9e338e8
  bzrlib/help_topics/en/configuration.txt configuration.txt-20060314161707-868350809502af01
  bzrlib/tests/test_config.py    testconfig.py-20051011041908-742d0c15d8d8c8eb
  doc/developers/configuration.txt configuration.txt-20110408142435-korjxxnskvq44sta-1
  doc/en/release-notes/bzr-2.5.txt bzr2.5.txt-20110708125756-587p0hpw7oke4h05-1
=== modified file 'bzrlib/config.py'
--- a/bzrlib/config.py	2011-10-11 12:01:51 +0000
+++ b/bzrlib/config.py	2011-10-31 22:11:59 +0000
@@ -3008,6 +3008,7 @@
         super(LocationSection, self).__init__(section.id, section.options)
         self.length = length
         self.extra_path = extra_path
+        self.locals = {'relpath': extra_path}
 
     def get(self, name, default=None):
         value = super(LocationSection, self).get(name, default)
@@ -3016,6 +3017,19 @@
             policy = _policy_value.get(policy_name, POLICY_NONE)
             if policy == POLICY_APPENDPATH:
                 value = urlutils.join(value, self.extra_path)
+            # expand section local options right now (since POLICY_APPENDPATH
+            # will never add options references, it's ok to expand after it).
+            chunks = []
+            for is_ref, chunk in iter_option_refs(value):
+                if not is_ref:
+                    chunks.append(chunk)
+                else:
+                    ref = chunk[1:-1]
+                    if ref in self.locals:
+                        chunks.append(self.locals[ref])
+                    else:
+                        chunks.append(chunk)
+            value = ''.join(chunks)
         return value
 
 
@@ -3081,18 +3095,26 @@
             yield section
 
 
+_option_ref_re = lazy_regex.lazy_compile('({[^{}]+})')
+"""Describes an expandable option reference.
+
+We want to match the most embedded reference first.
+
+I.e. for '{{foo}}' we will get '{foo}',
+for '{bar{baz}}' we will get '{baz}'
+"""
+
+def iter_option_refs(string):
+    # Split isolate refs so every other chunk is a ref
+    is_ref = False
+    for chunk  in _option_ref_re.split(string):
+        yield is_ref, chunk
+        is_ref = not is_ref
+
+
 class Stack(object):
     """A stack of configurations where an option can be defined"""
 
-    _option_ref_re = lazy_regex.lazy_compile('({[^{}]+})')
-    """Describes an exandable option reference.
-
-    We want to match the most embedded reference first.
-
-    I.e. for '{{foo}}' we will get '{foo}',
-    for '{bar{baz}}' we will get '{baz}'
-    """
-
     def __init__(self, sections_def, store=None, mutable_section_name=None):
         """Creates a stack of sections with an optional store for changes.
 
@@ -3210,19 +3232,15 @@
         result = string
         # We need to iterate until no more refs appear ({{foo}} will need two
         # iterations for example).
-        while True:
-            raw_chunks = Stack._option_ref_re.split(result)
-            if len(raw_chunks) == 1:
-                # Shorcut the trivial case: no refs
-                return result
+        expanded = True
+        while expanded:
+            expanded = False
             chunks = []
-            # Split will isolate refs so that every other chunk is a ref
-            chunk_is_ref = False
-            for chunk in raw_chunks:
-                if not chunk_is_ref:
+            for is_ref, chunk in iter_option_refs(result):
+                if not is_ref:
                     chunks.append(chunk)
-                    chunk_is_ref = True
                 else:
+                    expanded = True
                     name = chunk[1:-1]
                     if name in _refs:
                         raise errors.OptionExpansionLoop(string, _refs)
@@ -3232,7 +3250,6 @@
                         raise errors.ExpandingUnknownOption(name, string)
                     chunks.append(value)
                     _refs.pop()
-                    chunk_is_ref = False
             result = ''.join(chunks)
         return result
 
@@ -3242,11 +3259,6 @@
             # anything else
             value = env[name]
         else:
-            # FIXME: This is a limited implementation, what we really need is a
-            # way to query the bzr config for the value of an option,
-            # respecting the scope rules (That is, once we implement fallback
-            # configs, getting the option value should restart from the top
-            # config, not the current one) -- vila 20101222
             value = self.get(name, expand=False)
             value = self._expand_options_in_string(value, env, _refs)
         return value

=== modified file 'bzrlib/help_topics/en/configuration.txt'
--- a/bzrlib/help_topics/en/configuration.txt	2011-08-19 22:26:03 +0000
+++ b/bzrlib/help_topics/en/configuration.txt	2011-11-05 16:12:11 +0000
@@ -221,7 +221,7 @@
 ~~~~~~~~~~~~~~
 
 An ini file has three types of contructs: section headers, section
-variables and comments.
+options and comments.
 
 Comments
 ^^^^^^^^
@@ -240,9 +240,9 @@
 
 The only valid section headers for bazaar.conf currently are [DEFAULT] and
 [ALIASES].  Section headers are case sensitive. The default section provides for
-setting variables which can be overridden with the branch config file.
+setting options which can be overridden with the branch config file.
 
-For ``locations.conf``, the variables from the section with the
+For ``locations.conf``, the options from the section with the
 longest matching section header are used to the exclusion of other
 potentially valid section headers. A section header uses the path for
 the branch as the section header. Some examples include::
@@ -251,29 +251,27 @@
     [/home/jdoe/branches/]
 
 
-Section variables
-^^^^^^^^^^^^^^^^^
+Section options
+^^^^^^^^^^^^^^^
 
-A section variable resides within a section. A section variable contains a
-variable name, an equals sign and a value.  For example::
+A section option resides within a section. A section option contains an
+option name, an equals sign and a value.  For example::
 
     email            = John Doe <jdoe at isp.com>
     gpg_signing_key  = Amy Pond <amy at example.com>
 
-A variable can reference other variables **in the same configuration file** by
-enclosing them in curly brackets::
+A option can reference other options by enclosing them in curly brackets::
 
     my_branch_name = feature_x
     my_server      = bzr+ssh://example.com
     push_location   = {my_server}/project/{my_branch_name}
 
-
-Variable policies
-^^^^^^^^^^^^^^^^^
-
-Variables defined in a section affect the named directory or URL plus
-any locations they contain.  Policies can be used to change how a
-variable value is interpreted for contained locations.  Currently
+Option policies
+^^^^^^^^^^^^^^^
+
+Options defined in a section affect the named directory or URL plus
+any locations they contain.  Policies can be used to change how an
+option value is interpreted for contained locations.  Currently
 there are three policies available:
 
  none:
@@ -286,7 +284,7 @@
    for contained locations, any additional path components are
    appended to the value.
 
-Policies are specified by keys with names of the form "$var:policy".
+Policies are specified by keys with names of the form "<option_name>:policy".
 For example, to define the push location for a tree of branches, the
 following could be used::
 
@@ -297,6 +295,33 @@
 With this configuration, the push location for ``/top/location/branch1``
 would be ``sftp://example.com/location/branch1``.
 
+Section local options
+^^^^^^^^^^^^^^^^^^^^^
+
+Some options are defined automatically inside a given section and can be
+refered to in this section only. 
+
+For example, the ``appendpath`` policy can be used like this::
+
+  [/home/vila/src/bzr/bugs]
+  mypush = lp:~vila/bzr
+  mypush:policy=appendpath
+
+Using ``relpath`` to achieve the same result is done like this::
+
+  [/home/vila/src/bzr/bugs]
+  mypush = lp:~vila/bzr/{relpath}
+
+In both cases, when used in a directory like
+``/home/vila/src/bzr/bugs/832013-expand-in-stack`` we'll get::
+
+   $ bzr config mypush
+   lp:~vila/bzr/832013-expand-in-stack
+
+When an option is local to a Section, it cannot be referred to from option
+values in any other section from the same ``Store`` nor from any other
+``Store``.
+
 
 The main configuration file, bazaar.conf
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -337,14 +362,14 @@
 of bzr that requires authentication (smtp for example).
 
 The syntax of the file obeys the same rules as the others except for the
-variable policies which don't apply.
+option policies which don't apply.
 
 For more information on the possible uses of the authentication configuration
 file see :doc:`authentication-help`.
 
 
-Common variable options
------------------------
+Common options
+--------------
 
 debug_flags
 ~~~~~~~~~~~

=== modified file 'bzrlib/tests/test_config.py'
--- a/bzrlib/tests/test_config.py	2011-10-06 07:08:29 +0000
+++ b/bzrlib/tests/test_config.py	2011-10-31 22:11:59 +0000
@@ -3453,6 +3453,35 @@
         self.assertEquals(['bar', 'baz'], self.conf.get('foo'))
 
 
+class TestIterOptionRefs(tests.TestCase):
+    """iter_option_refs is a bit unusual, document some cases."""
+
+    def assertRefs(self, expected, string):
+        self.assertEquals(expected, list(config.iter_option_refs(string)))
+
+    def test_empty(self):
+        self.assertRefs([(False, '')], '')
+
+    def test_no_refs(self):
+        self.assertRefs([(False, 'foo bar')], 'foo bar')
+
+    def test_single_ref(self):
+        self.assertRefs([(False, ''), (True, '{foo}'), (False, '')], '{foo}')
+
+    def test_broken_ref(self):
+        self.assertRefs([(False, '{foo')], '{foo')
+
+    def test_embedded_ref(self):
+        self.assertRefs([(False, '{'), (True, '{foo}'), (False, '}')],
+                        '{{foo}}')
+
+    def test_two_refs(self):
+        self.assertRefs([(False, ''), (True, '{foo}'),
+                         (False, ''), (True, '{bar}'),
+                         (False, ''),],
+                        '{foo}{bar}')
+
+
 class TestStackExpandOptions(tests.TestCaseWithTransport):
 
     def setUp(self):
@@ -3607,6 +3636,88 @@
         self.assertEquals('quux', c.get('bar', expand=True))
 
 
+class TestStackCrossStoresExpand(tests.TestCaseWithTransport):
+
+    def test_cross_global_locations(self):
+        l_store = config.LocationStore()
+        l_store._load_from_string('''
+[/branch]
+lfoo = loc-foo
+lbar = {gbar}
+''')
+        l_store.save()
+        g_store = config.GlobalStore()
+        g_store._load_from_string('''
+[DEFAULT]
+gfoo = {lfoo}
+gbar = glob-bar
+''')
+        g_store.save()
+        stack = config.LocationStack('/branch')
+        self.assertEquals('glob-bar', stack.get('lbar', expand=True))
+        self.assertEquals('loc-foo', stack.get('gfoo', expand=True))
+
+
+class TestStackExpandSectionLocals(tests.TestCaseWithTransport):
+
+    def test_expand_relpath_locally(self):
+        l_store = config.LocationStore()
+        l_store._load_from_string('''
+[/home/user/project]
+lfoo = loc-foo/{relpath}
+''')
+        l_store.save()
+        stack = config.LocationStack('/home/user/project/branch')
+        self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
+
+    def test_expand_relpath_unknonw_in_global(self):
+        g_store = config.GlobalStore()
+        g_store._load_from_string('''
+[DEFAULT]
+gfoo = {relpath}
+''')
+        g_store.save()
+        stack = config.LocationStack('/home/user/project/branch')
+        self.assertRaises(errors.ExpandingUnknownOption,
+                          stack.get, 'gfoo', expand=True)
+
+    def test_expand_local_option_locally(self):
+        l_store = config.LocationStore()
+        l_store._load_from_string('''
+[/home/user/project]
+lfoo = loc-foo/{relpath}
+lbar = {gbar}
+''')
+        l_store.save()
+        g_store = config.GlobalStore()
+        g_store._load_from_string('''
+[DEFAULT]
+gfoo = {lfoo}
+gbar = glob-bar
+''')
+        g_store.save()
+        stack = config.LocationStack('/home/user/project/branch')
+        self.assertEquals('glob-bar', stack.get('lbar', expand=True))
+        self.assertEquals('loc-foo/branch', stack.get('gfoo', expand=True))
+
+    def test_locals_dont_leak(self):
+        """Make sure we chose the right local in presence of several sections.
+        """
+        l_store = config.LocationStore()
+        l_store._load_from_string('''
+[/home/user]
+lfoo = loc-foo/{relpath}
+[/home/user/project]
+lfoo = loc-foo/{relpath}
+''')
+        l_store.save()
+        stack = config.LocationStack('/home/user/project/branch')
+        self.assertEquals('loc-foo/branch', stack.get('lfoo', expand=True))
+        stack = config.LocationStack('/home/user/bar/baz')
+        self.assertEquals('loc-foo/bar/baz', stack.get('lfoo', expand=True))
+
+
+
 class TestStackSet(TestStackWithTransport):
 
     def test_simple_set(self):

=== modified file 'doc/developers/configuration.txt'
--- a/doc/developers/configuration.txt	2011-09-07 15:21:39 +0000
+++ b/doc/developers/configuration.txt	2011-11-05 16:12:11 +0000
@@ -47,6 +47,7 @@
 MutableSection is needed to set or remove an option, ReadOnlySection should
 be used otherwise.
 
+
 Stores
 ------
 
@@ -79,7 +80,7 @@
 ------------------
 
 For some contexts, only some sections from a given store will apply. Defining
-which is what the ``SectionMatcher`` are about.
+which is what the ``SectionMatcher`` objects are about.
 
 The main constraint here is that a ``SectionMatcher`` should delay the loading
 of the associated store as long as possible. The constructor should collect
@@ -92,7 +93,15 @@
 to implement the ``appendpath`` policy for example). If no sections match,
 an empty list is returned.
 
-.. FIXME: Replace the appendpath example if/when it's deprecated ;)
+Options local to a section can also be defined for special purposes and be
+handled by ``Section.get()``. One such option is ``relpath`` which is
+defined in ``LocationSection`` as an alternative to the ``appendpath``
+policy.
+
+For ``appendpath``, the ``LocationSection`` will carry ``extra_path`` as
+``832013-expand-in-stack`` and ``relpath`` will be available as a
+``Section`` local option with the same value. Note that such options can
+only be expanded inside the section that defines them.
 
 Specific section matchers can be implemented by overriding ``get_sections``
 or just ``match``.

=== modified file 'doc/en/release-notes/bzr-2.5.txt'
--- a/doc/en/release-notes/bzr-2.5.txt	2011-11-08 19:09:55 +0000
+++ b/doc/en/release-notes/bzr-2.5.txt	2011-11-10 09:48:31 +0000
@@ -25,6 +25,12 @@
   revisions. I.e.: ``bzr pull -v -Olog_format=short`` will use the ``short``
   format instead of the default ``long`` one. (Vincent Ladeuil, #861472)
 
+* The new config scheme allows an alternative syntax for the 'appenpath'
+  policy relying on option expansion and defining a new 'relpath' option
+  local to a section. Instead of using '<option>:policy=appendpath', the
+  option value can de defined as 'option=xxxx/{relpath}'.
+  (Vincent Ladeuil, #832013)
+
 Improvements
 ************
 




More information about the bazaar-commits mailing list