[PATCH 1 of 1] ui: support quotes in configlist (issue2147)

Henrik Stuart hg at hstuart.dk
Thu Apr 22 15:48:35 CDT 2010


# HG changeset patch
# User Henrik Stuart <hg at hstuart.dk>
# Date 1271967885 -7200
# Node ID 7dc7f0693fb61e37c8ddfce8479fb29a8585500d
# Parent  3213e8947975d7a8b44f6a11d063eb949fcb6bb5
ui: support quotes in configlist (issue2147)

Several places that use ui.configlist, predominantly in authentication
scenarios need to interface with systems that can contain spaces in usernames
(e.g. when client certificates are usernames, or Windows usernames).

This changeset introduces a parser that supports quoting of strings, and
escape quotation marks that get decoded into a single quotation mark that
adopts the usual behavior one would expect from quoting strings. The Python
library shlex module is not used, on purpose, as that raises if it cannot
match quotation marks in the given input.

diff -r 3213e8947975 -r 7dc7f0693fb6 mercurial/ui.py
--- a/mercurial/ui.py	Wed Apr 21 01:34:12 2010 +0200
+++ b/mercurial/ui.py	Thu Apr 22 22:24:45 2010 +0200
@@ -154,11 +154,78 @@
 
     def configlist(self, section, name, default=None, untrusted=False):
         """Return a list of comma/space separated strings"""
+
+        def _parse_plain(parts, s, offset):
+            whitespace = False
+            while offset < len(s) and (s[offset] == ' ' or s[offset] == ','):
+                whitespace = True
+                offset += 1
+            if offset >= len(s):
+                return None, parts, offset
+            if whitespace:
+                parts.append('')
+            if s[offset] == '"' and not parts[-1]:
+                return _parse_quote, parts, offset + 1
+            parts[-1] += s[offset]
+            return _parse_plain, parts, offset + 1
+
+        def _parse_quote(parts, s, offset):
+            if offset < len(s) and s[offset] == '"': # ""
+                parts.append('')
+                offset += 1
+                while offset < len(s) and s[offset] in [' ', ',']:
+                    offset += 1
+                return _parse_plain, parts, offset
+
+            while offset < len(s) and s[offset] != '"':
+                if s[offset] == '\\' and offset + 1 < len(s) and s[offset + 1] == '"':
+                    offset += 1
+                    parts[-1] += '"'
+                else:
+                    parts[-1] += s[offset]
+                offset += 1
+
+            if offset >= len(s):
+                real_parts = _configlist(parts[-1])
+                if not real_parts:
+                    parts[-1] = '"'
+                else:
+                    real_parts[0] = '"' + real_parts[0]
+                    parts = parts[:-1]
+                    parts.extend(real_parts)
+                return None, parts, offset
+
+            offset += 1
+            while offset < len(s) and s[offset] in [' ', ',']:
+                offset += 1
+
+            if offset < len(s):
+                if offset + 1 == len(s) and s[offset] == '"':
+                    parts[-1] += '"'
+                    offset += 1
+                else:
+                    parts.append('')
+            else:
+                return None, parts, offset
+
+            return _parse_plain, parts, offset
+
+        def _configlist(s):
+            s = s.rstrip(' ,')
+            if not s:
+                return None
+            parser, parts, offset = _parse_plain, [''], 0
+            while parser:
+                parser, parts, offset = parser(parts, s, offset)
+            return parts
+
         result = self.config(section, name, untrusted=untrusted)
         if result is None:
             result = default or []
         if isinstance(result, basestring):
-            result = result.replace(",", " ").split()
+            result = _configlist(result)
+            if result is None:
+                result = default or []
         return result
 
     def has_section(self, section, untrusted=False):
diff -r 3213e8947975 -r 7dc7f0693fb6 tests/test-ui-config.py
--- a/tests/test-ui-config.py	Wed Apr 21 01:34:12 2010 +0200
+++ b/tests/test-ui-config.py	Thu Apr 22 22:24:45 2010 +0200
@@ -11,6 +11,18 @@
     'lists.list2=foo bar baz',
     'lists.list3=alice, bob',
     'lists.list4=foo bar baz alice, bob',
+    'lists.list5=abc d"ef"g "hij def"',
+    'lists.list6="hello world", "how are you?"',
+    'lists.list7=Do"Not"Separate',
+    'lists.list8="Do"Separate',
+    'lists.list9="Do\\"NotSeparate"',
+    'lists.list10=string "with extraneous" quotation mark"',
+    'lists.list11=x, y',
+    'lists.list12="x", "y"',
+    'lists.list13=""" key = "x", "y" """',
+    'lists.list14=,,,,     ',
+    'lists.list15=" just with starting quotation',
+    'lists.list16="longer quotation" with "no ending quotation',
 ])
 
 print repr(testui.configitems('values'))
@@ -36,6 +48,18 @@
 print repr(testui.configlist('lists', 'list3'))
 print repr(testui.configlist('lists', 'list4'))
 print repr(testui.configlist('lists', 'list4', ['foo']))
+print repr(testui.configlist('lists', 'list5'))
+print repr(testui.configlist('lists', 'list6'))
+print repr(testui.configlist('lists', 'list7'))
+print repr(testui.configlist('lists', 'list8'))
+print repr(testui.configlist('lists', 'list9'))
+print repr(testui.configlist('lists', 'list10'))
+print repr(testui.configlist('lists', 'list11'))
+print repr(testui.configlist('lists', 'list12'))
+print repr(testui.configlist('lists', 'list13'))
+print repr(testui.configlist('lists', 'list14'))
+print repr(testui.configlist('lists', 'list15'))
+print repr(testui.configlist('lists', 'list16'))
 print repr(testui.configlist('lists', 'unknown'))
 print repr(testui.configlist('lists', 'unknown', ''))
 print repr(testui.configlist('lists', 'unknown', 'foo'))
diff -r 3213e8947975 -r 7dc7f0693fb6 tests/test-ui-config.py.out
--- a/tests/test-ui-config.py.out	Wed Apr 21 01:34:12 2010 +0200
+++ b/tests/test-ui-config.py.out	Thu Apr 22 22:24:45 2010 +0200
@@ -1,5 +1,5 @@
 [('string', 'string value'), ('bool1', 'true'), ('bool2', 'false')]
-[('list1', 'foo'), ('list2', 'foo bar baz'), ('list3', 'alice, bob'), ('list4', 'foo bar baz alice, bob')]
+[('list1', 'foo'), ('list2', 'foo bar baz'), ('list3', 'alice, bob'), ('list4', 'foo bar baz alice, bob'), ('list5', 'abc d"ef"g "hij def"'), ('list6', '"hello world", "how are you?"'), ('list7', 'Do"Not"Separate'), ('list8', '"Do"Separate'), ('list9', '"Do\\"NotSeparate"'), ('list10', 'string "with extraneous" quotation mark"'), ('list11', 'x, y'), ('list12', '"x", "y"'), ('list13', '""" key = "x", "y" """'), ('list14', ',,,,     '), ('list15', '" just with starting quotation'), ('list16', '"longer quotation" with "no ending quotation')]
 ---
 'string value'
 'true'
@@ -18,6 +18,18 @@
 ['alice', 'bob']
 ['foo', 'bar', 'baz', 'alice', 'bob']
 ['foo', 'bar', 'baz', 'alice', 'bob']
+['abc', 'd"ef"g', 'hij def']
+['hello world', 'how are you?']
+['Do"Not"Separate']
+['Do', 'Separate']
+['Do"NotSeparate']
+['string', 'with extraneous', 'quotation', 'mark"']
+['x', 'y']
+['x', 'y']
+['', ' key = ', 'x"', 'y', '', '"']
+[]
+['"', 'just', 'with', 'starting', 'quotation']
+['longer quotation', 'with', '"no', 'ending', 'quotation']
 []
 []
 ['foo']


More information about the Mercurial-devel mailing list