changeset 3401:d75e838f4b72

wikiutil.parse_quoted_separated_ext: allow multi-key stuff
author Johannes Berg <johannes AT sipsolutions DOT net>
date Sat, 22 Mar 2008 02:33:00 +0100
parents b12aba864312
children 4b94ff6a4073
files MoinMoin/_tests/test_wikiutil.py MoinMoin/wikiutil.py
diffstat 2 files changed, 98 insertions(+), 78 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/_tests/test_wikiutil.py	Sat Mar 22 01:37:49 2008 +0100
+++ b/MoinMoin/_tests/test_wikiutil.py	Sat Mar 22 02:33:00 2008 +0100
@@ -360,14 +360,17 @@
             assert res == expected
 
         for test in tests:
-            yield tuple([_check] + list(test))
+            yield [_check] + list(test)
 
     def testExtendedParserBracketing(self):
         tests = [
             (u'"a", "b", "c"', u',', None, [u'a', u'b', u'c']),
             (u'("a", "b", "c")', u',', None, [[u'a', u'b', u'c']]),
             (u'("a"("b", "c"))', u',', None, [[u'a', [u'b', u'c']]]),
+            (u'("a"("b)))", "c"))', u',', None, [[u'a', [u'b)))', u'c']]]),
+            (u'("a"("b>>> ( ab )>", "c"))', u',', None, [[u'a', [u'b>>> ( ab )>', u'c']]]),
             (u'("a" ("b" "c"))', None, None, [[u'a', [u'b', u'c']]]),
+            (u'("a"("b", "c") ) ', u',', None, [[u'a', [u'b', u'c']]]),
             (u'("a", <"b", ("c")>)', u',', None, [[u'a', [u'b', [u'c']]]]),
         ]
 
@@ -376,7 +379,43 @@
             assert res == expected
 
         for test in tests:
-            yield tuple([_check] + list(test))
+            yield [_check] + list(test)
+
+    def testExtendedParserMultikey(self):
+        tests = [
+            (u'"a", "b", "c"', u',', None, [u'a', u'b', u'c']),
+            (u'a:b, b:c, c:d', u',', u':', [(u'a', u'b'), (u'b', u'c'), (u'c', u'd')]),
+            (u'a:b, b:c, c:d', u',', None, [u'a:b', u'b:c', u'c:d']),
+            (u'a=b, b=c, c=d', u',', None, [u'a=b', u'b=c', u'c=d']),
+            (u'a=b, b=c, c=d', u',', u'=', [(u'a', u'b'), (u'b', u'c'), (u'c', u'd')]),
+            (u'"a"; "b"; "c"', u';', None, [u'a', u'b', u'c']),
+            (u'a:b; b:c; c:d', u';', u':', [(u'a', u'b'), (u'b', u'c'), (u'c', u'd')]),
+            (u'a:b; b:c; c:d', u';', None, [u'a:b', u'b:c', u'c:d']),
+            (u'a=b; b=c; c=d', u';', None, [u'a=b', u'b=c', u'c=d']),
+            (u'a=b; b=c; c=d', u';', u'=', [(u'a', u'b'), (u'b', u'c'), (u'c', u'd')]),
+            (u'"a" "b" "c"', None, None, [u'a', u'b', u'c']),
+            (u'" a " "b" "c"', None, None, [u' a ', u'b', u'c']),
+            (u'"a  " "b" "c"', None, None, [u'a  ', u'b', u'c']),
+            (u'"  a" "b" "c"', None, None, [u'  a', u'b', u'c']),
+            (u'"  a" "b" "c"', None, u':', [u'  a', u'b', u'c']),
+            (u'"a:a" "b:b" "c:b"', None, u':', [u'a:a', u'b:b', u'c:b']),
+            (u'   a:a  ', None, u':', [None, None, None, (u'a', u'a'), None, None]),
+            (u'a a: a', None, u':', [u'a', (u'a', None), u'a']),
+            (u'a a:"b c d" a', None, u':', [u'a', (u'a', u'b c d'), u'a']),
+            (u'a a:"b "" d" a', None, u':', [u'a', (u'a', u'b " d'), u'a']),
+            (u'title:Help* dog cat', None, u':', [(u'title', u'Help*'), u'dog', u'cat']),
+            (u'title:Help* "dog cat"', None, u':', [(u'title', u'Help*'), u'dog cat']),
+            (u'a:b:c d:e:f', None, u':', [(u'a', u'b', u'c'), (u'd', 'e', u'f')]),
+            (u'a:b:c:d', None, u':', [(u'a', u'b', u'c', u'd')]),
+            (u'a:"b:c":d', None, u':', [(u'a', u'b:c', u'd')]),
+        ]
+
+        def _check(args, sep, kwsep, expected):
+            res = wikiutil.parse_quoted_separated_ext(args, sep, kwsep, multikey=True)
+            assert res == expected
+
+        for test in tests:
+            yield [_check] + list(test)
 
     def testExtendedParserBracketingErrors(self):
         UCE = wikiutil.BracketUnexpectedCloseError
@@ -398,7 +437,7 @@
                            brackets=(u'<>', u'()'))
 
         for test in tests:
-            yield tuple([_check] + list(test))
+            yield [_check] + list(test)
 
 class TestArgGetters:
     def testGetBoolean(self):
--- a/MoinMoin/wikiutil.py	Sat Mar 22 01:37:49 2008 +0100
+++ b/MoinMoin/wikiutil.py	Sat Mar 22 02:33:00 2008 +0100
@@ -1266,29 +1266,20 @@
         BracketError.__init__(self, "Missing closing bracket %s" % bracket)
 
 def parse_quoted_separated_ext(args, separator=None, name_value_separator=None,
-                               brackets=None, seplimit=0):
+                               brackets=None, seplimit=0, multikey=False):
     """
     Parses the given string according to the other parameters.
 
-    If name_value_separator evaluates to True, the function parses
-    keyword arguments (name=value) and returns the keyword arguments
-    in the second return value (a dict) and positional arguments that
-    occurred after any keyword argument in the third return value (a
-    list).
-    The first return value always contains the positional arguments,
-    if name_value_separator evalutes to False only it is present.
-
-    Arguments can be quoted with a double-quote ('"') and the quote
-    can be escaped by doubling it, the separator and equal sign (for
-    keyword args) can both be quoted, when keyword args are enabled
-    then the name of a keyword argument can also be quoted.
+    Items can be quoted with a double-quote ('"') and the quote can
+    be escaped by doubling it, the separator and name_value_separator
+    can both be quoted, when name_value_separator is set then the
+    name can also be quoted.
 
     Values that are not given are returned as None, while the
-    empty string as a value can be achieved by quoting it; keys
-    are never returned as None.
+    empty string as a value can be achieved by quoting it.
 
     If a name or value does not start with a quote, then the quote
-    character looses its special meaning for that name or value.
+    looses its special meaning for that name or value.
 
     If multiple separators follow each other, this is treated as
     having None arguments inbetween, that is also true for when
@@ -1308,6 +1299,11 @@
     either of the class BracketMissingCloseError or of the class
     BracketUnexpectedCloseError.
 
+    If multikey is True (along with setting name_value_separator),
+    then the returned tuples for (key, value) pairs can also have
+    multiple keys, e.g.
+        "a=b=c" -> ('a', 'b', 'c')
+
     @param args: arguments to parse
     @param separator: the argument separator, defaults to None, meaning any
         space separates arguments
@@ -1316,9 +1312,10 @@
     @param brackets: a list of two-character strings giving
         opening and closing brackets
     @param seplimit: limits the number of parsed arguments
+    @param multikey: multiple keys allowed for a single value
     @rtype: list
-    @returns: list of unicode strings and, if name_value_separator is set,
-        tuples containing (key, value) pairs
+    @returns: list of unicode strings and tuples containing
+        unicode strings
     """
     idx = 0
     assert name_value_separator is None or name_value_separator != separator
@@ -1327,13 +1324,7 @@
         raise TypeError('args must be unicode')
     max = len(args)
     result = []         # result list
-    curname = u''       # current name, initially value as well (name=value)
-    cur = None          # current value
-    cur_quoted = False  # indicates whether value was quoted,
-                        # needed None vs. u'' handling
-    # MAYBE means that if a bracket follows, it's not valid
-    NO, IFNOTBRACKET, IFNOTEND, IFNOTBRACKETOREND, YES = range(5)
-    cur_present = IFNOTBRACKET # indicates cur/curname validity
+    cur = [None]        # current item
     quoted = False      # we're inside quotes
     skipquote = 0       # next quote is a quoted quote
     noquote = False     # no quotes expected because word didn't start with one
@@ -1364,27 +1355,20 @@
             closing.append(c)
             matchingbracket[o] = c
 
-    def additem(result, curname, cur, cur_quoted, separator_count, nextitemsep):
-        if cur is None:
-            cur = curname
-            curname = None
-        if not cur and not cur_quoted:
-            cur = None
-        if curname is not None:
-            result.append((curname, cur))
-        else:
-            result.append(cur)
-        curname = u''
-        cur = None
+    def additem(result, cur, separator_count, nextitemsep):
+        if len(cur) == 1:
+            result.extend(cur)
+        elif cur:
+            result.append(tuple(cur))
+        cur = [None]
         noquote = False
-        cur_quoted = False
         separator_count += 1
         seplimit_reached = False
         if seplimit and separator_count >= seplimit:
             seplimit_reached = True
             nextitemsep = [n for n in nextitemsep if n in separators]
 
-        return curname, cur, noquote, cur_quoted, separator_count, seplimit_reached, nextitemsep
+        return cur, noquote, separator_count, seplimit_reached, nextitemsep
 
     while idx < max:
         char = args[idx]
@@ -1407,49 +1391,43 @@
             if char in nextitemsep:
                 continue
             idx -= 1
-            if not cur is None:
-                if cur:
-                    cur = cur + spaces
-            elif curname:
-                curname = curname + spaces
+            if len(cur) and cur[-1]:
+                cur[-1] = cur[-1] + spaces
         elif not quoted and char == name_value_separator:
-            if cur is None:
-                cur = u''
-                cur_quoted = False
-                cur_present = YES
+            if multikey or len(cur) == 1:
+                cur.append(None)
             else:
-                cur += name_value_separator
+                if not multikey:
+                    cur[-1] += name_value_separator
+                else:
+                    cur.append(None)
             noquote = False
         elif not quoted and not seplimit_reached and char in separators:
-            if cur_present != NO:
-                (curname, cur, noquote, cur_quoted,
-                 separator_count, seplimit_reached,
-                 nextitemsep) = additem(result, curname, cur, cur_quoted,
-                                        separator_count, nextitemsep)
-            cur_present = IFNOTBRACKET
+            (cur, noquote, separator_count, seplimit_reached,
+             nextitemsep) = additem(result, cur, separator_count, nextitemsep)
         elif not quoted and not noquote and char == '"':
+            if len(cur) and cur[-1] is None:
+                del cur[-1]
+            cur.append(u'')
             quoted = True
-            cur_quoted = True
         elif quoted and not skipquote and char == '"':
             if next == '"':
                 skipquote = 2 # will be decremented right away
             else:
                 quoted = False
         elif not quoted and char in opening:
-            if cur_present and not cur_present in (IFNOTBRACKET, IFNOTBRACKETOREND):
-                (curname, cur, noquote, cur_quoted,
-                 separator_count, seplimit_reached,
-                 nextitemsep) = additem(result, curname, cur, cur_quoted,
-                                        separator_count, nextitemsep)
+            while len(cur) and cur[-1] is None:
+                del cur[-1]
+            (cur, noquote, separator_count, seplimit_reached,
+             nextitemsep) = additem(result, cur, separator_count, nextitemsep)
             bracketstack.append((matchingbracket[char], result))
             result = []
-            cur_present = IFNOTBRACKETOREND
         elif not quoted and char in closing:
-            if cur_present and not cur_present in (IFNOTBRACKET, IFNOTBRACKETOREND):
-                (curname, cur, noquote, cur_quoted,
-                 separator_count, seplimit_reached,
-                 nextitemsep) = additem(result, curname, cur, cur_quoted,
-                                        separator_count, nextitemsep)
+            while len(cur) and cur[-1] is None:
+                del cur[-1]
+            (cur, noquote, separator_count, seplimit_reached,
+             nextitemsep) = additem(result, cur, separator_count, nextitemsep)
+            cur = []
             if not bracketstack:
                 raise BracketUnexpectedCloseError(char)
             expected, oldresult = bracketstack[-1]
@@ -1458,22 +1436,22 @@
             del bracketstack[-1]
             oldresult.append(result)
             result = oldresult
-            cur_present = IFNOTBRACKETOREND
         else:
-            if cur is not None:
-                cur = cur + char
+            if len(cur):
+                if cur[-1] is None:
+                    cur[-1] = char
+                else:
+                    cur[-1] += char
             else:
-                curname = curname + char
+                cur.append(char)
             noquote = True
-            cur_present = YES
 
         idx += 1
 
     if bracketstack:
         raise BracketMissingCloseError(bracketstack[-1][0])
 
-    if cur_present and not cur_present in (IFNOTEND, IFNOTBRACKETOREND):
-        additem(result, curname, cur, cur_quoted, separator_count, nextitemsep)
+    additem(result, cur, separator_count, nextitemsep)
 
     return result
 
@@ -1492,7 +1470,10 @@
                                    seplimit=seplimit)
     for item in l:
         if isinstance(item, tuple):
-            keywords[item[0]] = item[1]
+            key, value = item
+            if key is None:
+                key = u''
+            keywords[key] = value
             positional = trailing
         else:
             positional.append(item)