changeset 3:d0fecf87e3bc

move tokenizer to separate class
author sheep@wintermute
date Wed, 14 Apr 2010 11:34:49 +0200
parents 5b63c545fa4a
children c0c786f4bbb5
files jpath.py
diffstat 1 files changed, 41 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- a/jpath.py	Wed Apr 14 11:20:26 2010 +0200
+++ b/jpath.py	Wed Apr 14 11:34:49 2010 +0200
@@ -12,50 +12,51 @@
 import re
 import itertools
 import operator
-import logging
 
-
-class Token:
-    """Base class for tokens."""
-
-    # list of all token classes marked with @Token.use
+class Tokenizer:
+    # list of all token classes marked with @Tokenizer.use
     tokens = {}
 
+    def __init__(self):
+        self.tokens_re = re.compile(r'|'.join(
+            r'(?P<{0}>{1})'.format(token.name, token.regexp)
+            for token in sorted(self.tokens.values(),
+                                key=operator.attrgetter("priority"))
+        ))
+
     @classmethod
-    def use(cls, token_class):
+    def token(cls, token_class):
         """Marks a token class to be used in tokenizer."""
 
         cls.tokens[token_class.name] = token_class
         return cls
 
-    @classmethod
-    def tokenize(cls, jpath):
+    def tokenize(self, jpath):
         """
         Breaks a string into tokens.
 
-        >>> [(t.name, t.value) for t in Token.tokenize("ziew")]
+        >>> [(t.name, t.value) for t in TOKENIZER.tokenize("ziew")]
         [('fragment', 'ziew')]
-        >>> [(t.name, t.value) for t in Token.tokenize("ziew.yawn")]
+        >>> [(t.name, t.value) for t in TOKENIZER.tokenize("ziew.yawn")]
         [('fragment', 'ziew'), ('fragment', 'yawn')]
-        >>> [(t.name, t.value) for t in Token.tokenize('ziew["yawn"]')]
+        >>> [(t.name, t.value) for t in TOKENIZER.tokenize('ziew["yawn"]')]
         [('fragment', 'ziew'), ('element', 'yawn')]
-        >>> [(t.name, t.value) for t in Token.tokenize('ziew[0]')]
+        >>> [(t.name, t.value) for t in TOKENIZER.tokenize('ziew[0]')]
         [('fragment', 'ziew'), ('index', 0)]
-        >>> [(t.name, t.value) for t in Token.tokenize("ziew.yawn+")]
+        >>> [(t.name, t.value) for t in TOKENIZER.tokenize("ziew.yawn+")]
         [('fragment', 'ziew'), ('fragment', 'yawn'), ('error', '+')]
         """
 
-        tokens_re = re.compile(r'|'.join(
-            r'(?P<{0}>{1})'.format(token.name, token.regexp)
-            for token in sorted(cls.tokens.values(),
-                                key=operator.attrgetter("priority"))
-        ))
-        for match in tokens_re.finditer(jpath):
+        for match in self.tokens_re.finditer(jpath):
             name = match.lastgroup
             if match.group(name):
                 params = dict(p for p in match.groupdict().items()
                               if p[1] is not None)
-                yield cls.tokens[name](**params)
+                yield self.tokens[name](**params)
+
+
+class Token:
+    """Base class for tokens."""
 
     @staticmethod
     def apply(tokens, data):
@@ -75,7 +76,7 @@
         return Token.apply(tokens, data[self.value])
 
 
-@Token.use
+@Tokenizer.token
 class FragmentToken(Token):
     """foo.bar.baz"""
 
@@ -87,7 +88,7 @@
         self.value = fragment_name
 
 
-@Token.use
+@Tokenizer.token
 class ElementToken(Token):
     """["foo"]["bar"]["baz"], with quote escaping"""
 
@@ -107,7 +108,7 @@
         self.value = element_name.replace(r'\"', '"')
 
 
-@Token.use
+@Tokenizer.token
 class IndexToken(Token):
     """[0][1][-1]"""
 
@@ -119,7 +120,7 @@
         self.value = int(index_value)
 
 
-@Token.use
+@Tokenizer.token
 class SliceToken(Token):
     """[0:-1:2], may return multiple results"""
 
@@ -138,7 +139,7 @@
         return Token.apply(tokens, data[self.start:self.end:self.step])
 
 
-@Token.use
+@Tokenizer.token
 class WildcardToken(Token):
     """*[*].*, may return multiple results"""
 
@@ -155,6 +156,7 @@
         except ValueError:
             token = NoneToken()
         try:
+            # If it's a dict, iterate over values, not keys.
             data = data.values()
         except AttributeError:
             pass
@@ -169,7 +171,7 @@
                 pass
 
 
-@Token.use
+@Tokenizer.token
 class EllipsisToken(Token):
     """.., may return multiple results"""
 
@@ -187,9 +189,15 @@
             while stack:
                 data = stack.pop()
                 try:
+                    # If it's a dict, iterate over values, not keys.
                     data = data.values()
                 except AttributeError:
                     pass
+                try:
+                    # Skip non-iterable datatypes.
+                    data = iter(data)
+                except TypeError:
+                    continue
                 for item in data:
                     if not isinstance(item, (str, int)):
                         # Strings are nasty, because they can be iterated
@@ -211,7 +219,7 @@
                 pass
 
 
-@Token.use
+@Tokenizer.token
 class ErrorToken(Token):
     """Catchall for everything that didn't match other tokens."""
 
@@ -234,6 +242,10 @@
         yield data
 
 
+# We only ever need one tokenizer
+TOKENIZER = Tokenizer()
+
+
 def get_all(jpath, data):
     """
     Gets list of all the values matching given jpath in the data datastructure.
@@ -264,7 +276,7 @@
     [[1, 2]]
     """
 
-    tokens = list(Token.tokenize(jpath))
+    tokens = list(TOKENIZER.tokenize(jpath))
     if not tokens:
         return [data]
     token, *tokens = tokens