Hello community,

here is the log from the commit of package python-jmespath for openSUSE:Factory 
checked in at 2016-03-29 14:50:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-jmespath (Old)
 and      /work/SRC/openSUSE:Factory/.python-jmespath.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-jmespath"

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-jmespath/python-jmespath.changes  
2015-05-28 09:57:57.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.python-jmespath.new/python-jmespath.changes     
2016-03-29 14:50:33.000000000 +0200
@@ -1,0 +2,33 @@
+Mon Feb  1 11:24:14 UTC 2016 - toddrme2...@gmail.com
+
+- Update to version 0.9.0
+  * Add support for new lines with tokens in an expression
+  * Add support for `JEP 9 
<http://jmespath.org/proposals/improved-filters.html>`__,
+    which introduces "and" expressions, "unary" expressions, "not" expressions,
+    and "paren" expressions
+  * Fix issue with hardcoded path in ``jp.py`` executable
+    (`issue 90 <https://github.com/jmespath/jmespath.py/issues/90>`__,
+     `issue 88 <https://github.com/jmespath/jmespath.py/issues/88>`__,
+     `issue 82 <https://github.com/jmespath/jmespath.py/issues/82>`__)
+- Update to version 0.8.0
+  * Improve lexing performance (`issue 84 
<https://github.com/jmespath/jmespath.py/pull/84>`__)
+  * Fix parsing error for multiselect lists (`issue 86 
<https://github.com/jmespath/jmespath.py/issues/86>`__)
+  * Fix issue with escaping single quotes in literal strings (`issue 85 
<https://github.com/jmespath/jmespath.py/issues/85>`__)
+  * Add support for providing your own dict cls to support
+    ordered dictionaries (`issue 94 
<https://github.com/jmespath/jmespath.py/pull/94>`__)
+  * Add map() function (`issue 95 
<https://github.com/jmespath/jmespath.py/pull/95>`__)
+
+-------------------------------------------------------------------
+Wed Jan 20 19:42:39 UTC 2016 - rjsch...@suse.com
+
+- Fix build issues for SLE 11
+  + More test dependencies required
+
+-------------------------------------------------------------------
+Wed Jan 20 19:08:32 UTC 2016 - rjsch...@suse.com
+
+- Manage the jp executable with update-alternatives now that
+  python3-jmespath exists
+- spec file improvements
+
+-------------------------------------------------------------------

Old:
----
  jmespath-0.7.1.tar.gz

New:
----
  jmespath-0.9.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-jmespath.spec ++++++
--- /var/tmp/diff_new_pack.p1ENnt/_old  2016-03-29 14:50:34.000000000 +0200
+++ /var/tmp/diff_new_pack.p1ENnt/_new  2016-03-29 14:50:34.000000000 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-jmespath
 #
-# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany.
+# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -19,19 +19,27 @@
 %define baseName jmespath
 
 Name:           python-jmespath
-Version:        0.7.1
+Version:        0.9.0
 Release:        0
 Summary:        Extract elements from JSON document
 License:        MIT
 Group:          Development/Languages/Python
 Url:            https://github.com/boto/jmespath
 Source0:        
https://pypi.python.org/packages/source/j/%{baseName}/%{baseName}-%{version}.tar.gz
-Requires:       python
+Requires:       python-base
 Requires:       python-ply >= 3.4
-BuildRequires:  python
+Requires(post):   update-alternatives
+Requires(postun): update-alternatives
 BuildRequires:  python-devel
-BuildRequires:  python-ply
+BuildRequires:  python-ply >= 3.4
 BuildRequires:  python-setuptools
+# For testing
+BuildRequires:  python-nose
+%if 0%{?suse_version} && 0%{?suse_version} <= 1110
+BuildRequires:  python-ordereddict
+%endif
+BuildRequires:  python-simplejson
+BuildRequires:  python-unittest2
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
 %if 0%{?suse_version} && 0%{?suse_version} <= 1110
 %{!?python_sitelib: %global python_sitelib %(python -c "from 
distutils.sysconfig import get_python_lib; print get_python_lib()")}
@@ -79,17 +87,25 @@
 %install
 python setup.py install --prefix=%{_prefix} --root=%{buildroot} 
--install-scripts=%{_bindir}
 pushd %{buildroot}/%{_bindir}
-ln -s jp.py jp
+mv jp.py jp-%{py_ver}
 popd
 
+%check
+nosetests tests
+
+%post
+"%_sbindir/update-alternatives" --install %{_bindir}/jp jp 
%{_bindir}/jp-%{py_ver} 30
+
+%postun
+if [ $1 -eq 0 ] ; then
+    "%_sbindir/update-alternatives" --remove jp %{_bindir}/jp-%{py_ver}
+fi
+
 %files
 %defattr(-,root,root,-)
 %doc LICENSE.txt README.rst
-%dir %{python_sitelib}/jmespath
-%dir %{python_sitelib}/%{baseName}-%{version}-py%{py_ver}.egg-info
-%{_bindir}/jp
-%{_bindir}/jp.py
-%{python_sitelib}/jmespath/*
-%{python_sitelib}/*egg-info/*
+%{_bindir}/jp-%{py_ver}
+%{python_sitelib}/jmespath/
+%{python_sitelib}/%{baseName}-%{version}-py%{py_ver}.egg-info/
 
 %changelog

++++++ jmespath-0.7.1.tar.gz -> jmespath-0.9.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/PKG-INFO new/jmespath-0.9.0/PKG-INFO
--- old/jmespath-0.7.1/PKG-INFO 2015-04-27 19:29:56.000000000 +0200
+++ new/jmespath-0.9.0/PKG-INFO 2015-10-01 06:12:12.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: jmespath
-Version: 0.7.1
+Version: 0.9.0
 Summary: JSON Matching Expressions
 Home-page: https://github.com/jmespath/jmespath.py
 Author: James Saryerwinnie
@@ -77,6 +77,26 @@
         search multiple documents.  This avoids having to reparse the
         JMESPath expression each time you search a new document.
         
+        Options
+        -------
+        
+        You can provide an instance of ``jmespath.Options`` to control how
+        a JMESPath expression is evaluated.  The most common scenario for
+        using an ``Options`` instance is if you want to have ordered output
+        of your dict keys.  To do this you can use either of these options::
+        
+            >>> import jmespath
+            >>> jmespath.search('{a: a, b: b},
+            ...                 mydata,
+            ...                 
jmespath.Options(dict_cls=collections.OrderedDict))
+        
+        
+            >>> import jmespath
+            >>> parsed = jmespath.compile('{a: a, b: b}')
+            >>> parsed.search('{a: a, b: b},
+            ...               mydata,
+            ...               
jmespath.Options(dict_cls=collections.OrderedDict))
+        
         
         Specification
         =============
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/README.rst 
new/jmespath-0.9.0/README.rst
--- old/jmespath-0.7.1/README.rst       2015-04-27 19:23:22.000000000 +0200
+++ new/jmespath-0.9.0/README.rst       2015-09-30 06:37:03.000000000 +0200
@@ -69,6 +69,26 @@
 search multiple documents.  This avoids having to reparse the
 JMESPath expression each time you search a new document.
 
+Options
+-------
+
+You can provide an instance of ``jmespath.Options`` to control how
+a JMESPath expression is evaluated.  The most common scenario for
+using an ``Options`` instance is if you want to have ordered output
+of your dict keys.  To do this you can use either of these options::
+
+    >>> import jmespath
+    >>> jmespath.search('{a: a, b: b},
+    ...                 mydata,
+    ...                 jmespath.Options(dict_cls=collections.OrderedDict))
+
+
+    >>> import jmespath
+    >>> parsed = jmespath.compile('{a: a, b: b}')
+    >>> parsed.search('{a: a, b: b},
+    ...               mydata,
+    ...               jmespath.Options(dict_cls=collections.OrderedDict))
+
 
 Specification
 =============
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/bin/jp.py new/jmespath-0.9.0/bin/jp.py
--- old/jmespath-0.7.1/bin/jp.py        2015-04-27 19:27:03.000000000 +0200
+++ new/jmespath-0.9.0/bin/jp.py        2015-09-23 07:05:13.000000000 +0200
@@ -3,6 +3,7 @@
 import sys
 import json
 import argparse
+from pprint import pformat
 
 import jmespath
 from jmespath import exceptions
@@ -22,7 +23,7 @@
     if args.ast:
         # Only print the AST
         expression = jmespath.compile(args.expression)
-        sys.stdout.write(str(expression))
+        sys.stdout.write(pformat(expression.parsed))
         sys.stdout.write('\n')
         return 0
     if args.filename:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/jmespath/__init__.py 
new/jmespath-0.9.0/jmespath/__init__.py
--- old/jmespath-0.7.1/jmespath/__init__.py     2015-04-27 19:28:36.000000000 
+0200
+++ new/jmespath-0.9.0/jmespath/__init__.py     2015-10-01 06:11:26.000000000 
+0200
@@ -1,11 +1,12 @@
 from jmespath import parser
+from jmespath.visitor import Options
 
-__version__ = '0.7.1'
+__version__ = '0.9.0'
 
 
 def compile(expression):
     return parser.Parser().parse(expression)
 
 
-def search(expression, data):
-    return parser.Parser().parse(expression).search(data)
+def search(expression, data, options=None):
+    return parser.Parser().parse(expression).search(data, options=options)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/jmespath/ast.py 
new/jmespath-0.9.0/jmespath/ast.py
--- old/jmespath-0.7.1/jmespath/ast.py  2015-04-14 01:16:58.000000000 +0200
+++ new/jmespath-0.9.0/jmespath/ast.py  2015-10-01 06:11:19.000000000 +0200
@@ -62,6 +62,14 @@
     return {"type": "or_expression", "children": [left, right]}
 
 
+def and_expression(left, right):
+    return {"type": "and_expression", "children": [left, right]}
+
+
+def not_expression(expr):
+    return {"type": "not_expression", "children": [expr]}
+
+
 def pipe(left, right):
     return {'type': 'pipe', 'children': [left, right]}
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/jmespath/functions.py 
new/jmespath-0.9.0/jmespath/functions.py
--- old/jmespath-0.7.1/jmespath/functions.py    2015-04-27 19:27:03.000000000 
+0200
+++ new/jmespath-0.9.0/jmespath/functions.py    2015-09-30 06:37:03.000000000 
+0200
@@ -251,6 +251,13 @@
     def _func_join(self, separator, array):
         return separator.join(array)
 
+    @builtin_function({'types': ['expref']}, {'types': ['array']})
+    def _func_map(self, expref, arg):
+        result = []
+        for element in arg:
+            result.append(self.interpreter.visit(expref.expression, element))
+        return result
+
     @builtin_function({"types": ['array-number', 'array-string']})
     def _func_max(self, arg):
         if arg:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/jmespath/lexer.py 
new/jmespath-0.9.0/jmespath/lexer.py
--- old/jmespath-0.7.1/jmespath/lexer.py        2015-04-27 19:23:22.000000000 
+0200
+++ new/jmespath-0.9.0/jmespath/lexer.py        2015-10-01 06:11:19.000000000 
+0200
@@ -1,4 +1,4 @@
-import re
+import string
 import warnings
 from json import loads
 
@@ -6,143 +6,172 @@
 
 
 class Lexer(object):
-    TOKENS = (
-        r'(?P<number>-?\d+)|'
-        r'(?P<unquoted_identifier>([a-zA-Z_][a-zA-Z_0-9]*))|'
-        r'(?P<quoted_identifier>("(?:\\\\|\\"|[^"])*"))|'
-        r'(?P<string_literal>(\'(?:\\\\|\\\'|[^\'])*\'))|'
-        r'(?P<literal>(`(?:\\\\|\\`|[^`])*`))|'
-        r'(?P<filter>\[\?)|'
-        r'(?P<or>\|\|)|'
-        r'(?P<pipe>\|)|'
-        r'(?P<ne>!=)|'
-        r'(?P<rbrace>\})|'
-        r'(?P<eq>==)|'
-        r'(?P<dot>\.)|'
-        r'(?P<star>\*)|'
-        r'(?P<gte>>=)|'
-        r'(?P<lparen>\()|'
-        r'(?P<lbrace>\{)|'
-        r'(?P<lte><=)|'
-        r'(?P<flatten>\[\])|'
-        r'(?P<rbracket>\])|'
-        r'(?P<lbracket>\[)|'
-        r'(?P<rparen>\))|'
-        r'(?P<comma>,)|'
-        r'(?P<colon>:)|'
-        r'(?P<lt><)|'
-        r'(?P<expref>&)|'
-        r'(?P<gt>>)|'
-        r'(?P<current>@)|'
-        r'(?P<skip>[ \t]+)'
-    )
-
-    def __init__(self):
-        self.master_regex = re.compile(self.TOKENS)
+    START_IDENTIFIER = set(string.ascii_letters + '_')
+    VALID_IDENTIFIER = set(string.ascii_letters + string.digits + '_')
+    START_NUMBER = set(string.digits + '-')
+    VALID_NUMBER = set(string.digits)
+    WHITESPACE = set(" \t\n\r")
+    SIMPLE_TOKENS = {
+        '.': 'dot',
+        '*': 'star',
+        ']': 'rbracket',
+        ',': 'comma',
+        ':': 'colon',
+        '@': 'current',
+        '(': 'lparen',
+        ')': 'rparen',
+        '{': 'lbrace',
+        '}': 'rbrace',
+    }
 
     def tokenize(self, expression):
+        self._initialize_for_expression(expression)
+        while self._current is not None:
+            if self._current in self.SIMPLE_TOKENS:
+                yield {'type': self.SIMPLE_TOKENS[self._current],
+                       'value': self._current,
+                       'start': self._position, 'end': self._position + 1}
+                self._next()
+            elif self._current in self.START_IDENTIFIER:
+                start = self._position
+                buff = self._current
+                while self._next() in self.VALID_IDENTIFIER:
+                    buff += self._current
+                yield {'type': 'unquoted_identifier', 'value': buff,
+                       'start': start, 'end': start + len(buff)}
+            elif self._current in self.WHITESPACE:
+                self._next()
+            elif self._current == '[':
+                start = self._position
+                next_char = self._next()
+                if next_char == ']':
+                    self._next()
+                    yield {'type': 'flatten', 'value': '[]',
+                           'start': start, 'end': start + 2}
+                elif next_char == '?':
+                    self._next()
+                    yield {'type': 'filter', 'value': '[?',
+                           'start': start, 'end': start + 2}
+                else:
+                    yield {'type': 'lbracket', 'value': '[',
+                           'start': start, 'end': start + 1}
+            elif self._current == "'":
+                yield self._consume_raw_string_literal()
+            elif self._current == '|':
+                yield self._match_or_else('|', 'or', 'pipe')
+            elif self._current == '&':
+                yield self._match_or_else('&', 'and', 'expref')
+            elif self._current == '`':
+                yield self._consume_literal()
+            elif self._current in self.START_NUMBER:
+                start = self._position
+                buff = self._current
+                while self._next() in self.VALID_NUMBER:
+                    buff += self._current
+                yield {'type': 'number', 'value': int(buff),
+                       'start': start, 'end': start + len(buff)}
+            elif self._current == '"':
+                yield self._consume_quoted_identifier()
+            elif self._current == '<':
+                yield self._match_or_else('=', 'lte', 'lt')
+            elif self._current == '>':
+                yield self._match_or_else('=', 'gte', 'gt')
+            elif self._current == '!':
+                yield self._match_or_else('=', 'ne', 'not')
+            elif self._current == '=':
+                yield self._match_or_else('=', 'eq', 'unknown')
+            else:
+                raise LexerError(lexer_position=self._position,
+                                 lexer_value=self._current,
+                                 message="Unknown token %s" % self._current)
+        yield {'type': 'eof', 'value': '',
+               'start': self._length, 'end': self._length}
+
+    def _initialize_for_expression(self, expression):
         if not expression:
             raise EmptyExpressionError()
-        previous_column = 0
-        for match in self.master_regex.finditer(expression):
-            value = match.group()
-            start = match.start()
-            end = match.end()
-            if match.lastgroup == 'skip':
-                # Ignore whitespace.
-                previous_column = end
-                continue
-            if start != previous_column:
-                bad_value = expression[previous_column:start]
-                # Try to give a good error message.
-                if bad_value == '"':
-                    raise LexerError(
-                        lexer_position=previous_column,
-                        lexer_value=value,
-                        message='Starting quote is missing the ending quote',
-                        expression=expression)
-                raise LexerError(lexer_position=previous_column,
-                                 lexer_value=value,
-                                 message='Unknown character',
-                                 expression=expression)
-            previous_column = end
-            token_type = match.lastgroup
-            handler = getattr(self, '_token_%s' % token_type.lower(), None)
-            if handler is not None:
-                value = handler(value, start, end)
-            yield {'type': token_type, 'value': value,
-                   'start': start, 'end': end}
-        # At the end of the loop make sure we've consumed all the input.
-        # If we haven't then we have unidentified characters.
-        if end != len(expression):
-            msg = "Unknown characters at the end of the expression"
-            raise LexerError(lexer_position=end,
-                             lexer_value='',
-                             message=msg, expression=expression)
+        self._position = 0
+        self._expression = expression
+        self._chars = list(self._expression)
+        self._current = self._chars[self._position]
+        self._length = len(self._expression)
+
+    def _next(self):
+        if self._position == self._length - 1:
+            self._current = None
         else:
-            yield {'type': 'eof', 'value': '',
-                   'start': len(expression), 'end': len(expression)}
-
-    def _token_number(self, value, start, end):
-        return int(value)
-
-    def _token_quoted_identifier(self, value, start, end):
-        try:
-            return loads(value)
-        except ValueError as e:
-            error_message = str(e).split(':')[0]
-            raise LexerError(lexer_position=start,
-                             lexer_value=value,
-                             message=error_message)
-
-    def _token_string_literal(self, value, start, end):
-        return value[1:-1]
-
-    def _token_literal(self, value, start, end):
-        actual_value = value[1:-1]
-        actual_value = actual_value.replace('\\`', '`').lstrip()
-        # First, if it looks like JSON then we parse it as
-        # JSON and any json parsing errors propogate as lexing
-        # errors.
-        if self._looks_like_json(actual_value):
-            try:
-                return loads(actual_value)
-            except ValueError:
+            self._position += 1
+            self._current = self._chars[self._position]
+        return self._current
+
+    def _consume_until(self, delimiter):
+        # Consume until the delimiter is reached,
+        # allowing for the delimiter to be escaped with "\".
+        start = self._position
+        buff = ''
+        self._next()
+        while self._current != delimiter:
+            if self._current == '\\':
+                buff += '\\'
+                self._next()
+            if self._current is None:
                 raise LexerError(lexer_position=start,
-                                 lexer_value=value,
-                                 message="Bad token %s" % value)
-        else:
-            potential_value = '"%s"' % actual_value
+                                 lexer_value=self._expression,
+                                 message="Unclosed %s delimiter" % delimiter)
+            buff += self._current
+            self._next()
+        # Skip the closing delimiter.
+        self._next()
+        return buff
+
+    def _consume_literal(self):
+        start = self._position
+        lexeme = self._consume_until('`').replace('\\`', '`')
+        try:
+            # Assume it is valid JSON and attempt to parse.
+            parsed_json = loads(lexeme)
+        except ValueError:
             try:
-                # There's a shortcut syntax where string literals
-                # don't have to be quoted.  This is only true if the
-                # string doesn't start with chars that could start a valid
-                # JSON value.
-                value = loads(potential_value)
+                # Invalid JSON values should be converted to quoted
+                # JSON strings during the JEP-12 deprecation period.
+                parsed_json = loads('"%s"' % lexeme.lstrip())
                 warnings.warn("deprecated string literal syntax",
                               PendingDeprecationWarning)
-                return value
             except ValueError:
                 raise LexerError(lexer_position=start,
-                                 lexer_value=value,
-                                 message="Bad token %s" % value)
+                                 lexer_value=self._expression,
+                                 message="Bad token %s" % lexeme)
+        token_len = self._position - start
+        return {'type': 'literal', 'value': parsed_json,
+                'start': start, 'end': token_len}
+
+    def _consume_quoted_identifier(self):
+        start = self._position
+        lexeme = '"' + self._consume_until('"') + '"'
+        try:
+            token_len = self._position - start
+            return {'type': 'quoted_identifier', 'value': loads(lexeme),
+                    'start': start, 'end': token_len}
+        except ValueError as e:
+            error_message = str(e).split(':')[0]
+            raise LexerError(lexer_position=start,
+                             lexer_value=lexeme,
+                             message=error_message)
 
-    def _looks_like_json(self, value):
-        # Figure out if the string "value" starts with something
-        # that looks like json.
-        if not value:
-            return False
-        elif value[0] in ['"', '{', '[']:
-            return True
-        elif value in ['true', 'false', 'null']:
-            return True
-        elif value[0] in ['-', '0', '1', '2', '3', '4', '5',
-                          '6', '7', '8', '9']:
-            # Then this is JSON, return True.
-            try:
-                loads(value)
-                return True
-            except ValueError:
-                return False
-        else:
-            return False
+    def _consume_raw_string_literal(self):
+        start = self._position
+        lexeme = self._consume_until("'").replace("\\'", "'")
+        token_len = self._position - start
+        return {'type': 'literal', 'value': lexeme,
+                'start': start, 'end': token_len}
+
+    def _match_or_else(self, expected, match_type, else_type):
+        start = self._position
+        current = self._current
+        next_char = self._next()
+        if next_char == expected:
+            self._next()
+            return {'type': match_type, 'value': current + next_char,
+                    'start': start, 'end': start + 1}
+        return {'type': else_type, 'value': current,
+                'start': start, 'end': start}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/jmespath/parser.py 
new/jmespath-0.9.0/jmespath/parser.py
--- old/jmespath-0.7.1/jmespath/parser.py       2015-04-27 19:27:03.000000000 
+0200
+++ new/jmespath-0.9.0/jmespath/parser.py       2015-10-01 06:11:19.000000000 
+0200
@@ -48,21 +48,27 @@
         'expref': 0,
         'colon': 0,
         'pipe': 1,
-        'eq': 2,
-        'gt': 2,
-        'lt': 2,
-        'gte': 2,
-        'lte': 2,
-        'ne': 2,
-        'or': 5,
-        'flatten': 6,
+        'or': 2,
+        'and': 3,
+        'eq': 5,
+        'gt': 5,
+        'lt': 5,
+        'gte': 5,
+        'lte': 5,
+        'ne': 5,
+        'flatten': 9,
+        # Everything above stops a projection.
         'star': 20,
         'filter': 21,
         'dot': 40,
+        'not': 45,
         'lbrace': 50,
         'lbracket': 55,
         'lparen': 60,
     }
+    # The maximum binding power for a token that can stop
+    # a projection.
+    _PROJECTION_STOP = 10
     # The _MAX_SIZE most recent expressions are cached in
     # _CACHE dict.
     _CACHE = {}
@@ -161,12 +167,21 @@
     def _token_nud_lbrace(self, token):
         return self._parse_multi_select_hash()
 
+    def _token_nud_lparen(self, token):
+        expression = self._expression()
+        self._match('rparen')
+        return expression
+
     def _token_nud_flatten(self, token):
         left = ast.flatten(ast.identity())
         right = self._parse_projection_rhs(
             self.BINDING_POWER['flatten'])
         return ast.projection(left, right)
 
+    def _token_nud_not(self, token):
+        expr = self._expression(self.BINDING_POWER['not'])
+        return ast.not_expression(expr)
+
     def _token_nud_lbracket(self, token):
         if self._current_token() in ['number', 'colon']:
             right = self._parse_index_expression()
@@ -254,15 +269,15 @@
         right = self._expression(self.BINDING_POWER['or'])
         return ast.or_expression(left, right)
 
+    def _token_led_and(self, left):
+        right = self._expression(self.BINDING_POWER['and'])
+        return ast.and_expression(left, right)
+
     def _token_led_lparen(self, left):
         name = left['value']
         args = []
         while not self._current_token() == 'rparen':
-            if self._current_token() == 'current':
-                expression = ast.current_node()
-                self._advance()
-            else:
-                expression = self._expression()
+            expression = self._expression()
             if self._current_token() == 'comma':
                 self._match('comma')
             args.append(expression)
@@ -338,12 +353,13 @@
 
     def _parse_multi_select_list(self):
         expressions = []
-        while not self._current_token() == 'rbracket':
+        while True:
             expression = self._expression()
             expressions.append(expression)
-            if self._current_token() == 'comma':
+            if self._current_token() == 'rbracket':
+                break
+            else:
                 self._match('comma')
-                self._assert_not_token('rbracket')
         self._match('rbracket')
         return ast.multi_select_list(expressions)
 
@@ -369,7 +385,7 @@
 
     def _parse_projection_rhs(self, binding_power):
         # Parse the right hand side of the projection.
-        if self.BINDING_POWER[self._current_token()] < 10:
+        if self.BINDING_POWER[self._current_token()] < self._PROJECTION_STOP:
             # BP of 10 are all the tokens that stop a projection.
             right = ast.identity()
         elif self._current_token() == 'lbracket':
@@ -503,8 +519,8 @@
         self.expression = expression
         self.parsed = parsed
 
-    def search(self, value):
-        interpreter = visitor.TreeInterpreter()
+    def search(self, value, options=None):
+        interpreter = visitor.TreeInterpreter(options)
         result = interpreter.visit(self.parsed, value)
         return result
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/jmespath/visitor.py 
new/jmespath-0.9.0/jmespath/visitor.py
--- old/jmespath-0.7.1/jmespath/visitor.py      2015-04-14 01:16:58.000000000 
+0200
+++ new/jmespath-0.9.0/jmespath/visitor.py      2015-10-01 06:11:19.000000000 
+0200
@@ -33,6 +33,20 @@
         return x is True or x is False
 
 
+class Options(object):
+    """Options to control how a JMESPath function is evaluated."""
+    def __init__(self, dict_cls):
+        #: The class to use when creating a dict.  The interpreter
+        #  may create dictionaries during the evalution of a JMESPath
+        #  expression.  For example, a multi-select hash will
+        #  create a dictionary.  By default we use a dict() type.
+        #  You can set this value to change what dict type is used.
+        #  The most common reason you would change this is if you
+        #  want to set a collections.OrderedDict so that you can
+        #  have predictible key ordering.
+        self.dict_cls = dict_cls
+
+
 class _Expression(object):
     def __init__(self, expression):
         self.expression = expression
@@ -67,14 +81,21 @@
     }
     MAP_TYPE = dict
 
-    def __init__(self):
+    def __init__(self, options=None):
         super(TreeInterpreter, self).__init__()
+        self._options = options
+        self._dict_cls = self.MAP_TYPE
+        if options is not None and options.dict_cls is not None:
+            self._dict_cls = self._options.dict_cls
         self._functions = functions.RuntimeFunctions()
         # Note that .interpreter is a property that uses
         # a weakref so that the cyclic reference can be
         # properly freed.
         self._functions.interpreter = self
 
+    def default_visit(self, node, *args, **kwargs):
+        raise NotImplementedError(node['type'])
+
     def visit_subexpression(self, node, value):
         result = value
         for node in node['children']:
@@ -114,7 +135,7 @@
         comparator_node = node['children'][2]
         collected = []
         for element in base:
-            if self.visit(comparator_node, element):
+            if self._is_true(self.visit(comparator_node, element)):
                 current = self.visit(node['children'][1], element)
                 if current is not None:
                     collected.append(current)
@@ -167,7 +188,7 @@
     def visit_multi_select_dict(self, node, value):
         if value is None:
             return None
-        collected = self.MAP_TYPE()
+        collected = self._dict_cls()
         for child in node['children']:
             collected[child['value']] = self.visit(child, value)
         return collected
@@ -186,6 +207,20 @@
             matched = self.visit(node['children'][1], value)
         return matched
 
+    def visit_and_expression(self, node, value):
+        matched = self.visit(node['children'][0], value)
+        if self._is_false(matched):
+            return matched
+        return self.visit(node['children'][1], value)
+
+    def visit_not_expression(self, node, value):
+        original_result = self.visit(node['children'][0], value)
+        if original_result is 0:
+            # Special case for 0, !0 should be false, not true.
+            # 0 is not a special cased integer in jmespath.
+            return False
+        return not original_result
+
     def visit_pipe(self, node, value):
         result = value
         for node in node['children']:
@@ -223,6 +258,9 @@
         return (value == '' or value == [] or value == {} or value is None or
                 value is False)
 
+    def _is_true(self, value):
+        return not self._is_false(value)
+
 
 class GraphvizVisitor(Visitor):
     def __init__(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/jmespath.egg-info/PKG-INFO 
new/jmespath-0.9.0/jmespath.egg-info/PKG-INFO
--- old/jmespath-0.7.1/jmespath.egg-info/PKG-INFO       2015-04-27 
19:29:56.000000000 +0200
+++ new/jmespath-0.9.0/jmespath.egg-info/PKG-INFO       2015-10-01 
06:12:12.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: jmespath
-Version: 0.7.1
+Version: 0.9.0
 Summary: JSON Matching Expressions
 Home-page: https://github.com/jmespath/jmespath.py
 Author: James Saryerwinnie
@@ -77,6 +77,26 @@
         search multiple documents.  This avoids having to reparse the
         JMESPath expression each time you search a new document.
         
+        Options
+        -------
+        
+        You can provide an instance of ``jmespath.Options`` to control how
+        a JMESPath expression is evaluated.  The most common scenario for
+        using an ``Options`` instance is if you want to have ordered output
+        of your dict keys.  To do this you can use either of these options::
+        
+            >>> import jmespath
+            >>> jmespath.search('{a: a, b: b},
+            ...                 mydata,
+            ...                 
jmespath.Options(dict_cls=collections.OrderedDict))
+        
+        
+            >>> import jmespath
+            >>> parsed = jmespath.compile('{a: a, b: b}')
+            >>> parsed.search('{a: a, b: b},
+            ...               mydata,
+            ...               
jmespath.Options(dict_cls=collections.OrderedDict))
+        
         
         Specification
         =============
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/jmespath.egg-info/SOURCES.txt 
new/jmespath-0.9.0/jmespath.egg-info/SOURCES.txt
--- old/jmespath-0.7.1/jmespath.egg-info/SOURCES.txt    2015-04-27 
19:29:56.000000000 +0200
+++ new/jmespath-0.9.0/jmespath.egg-info/SOURCES.txt    2015-10-01 
06:12:12.000000000 +0200
@@ -15,6 +15,7 @@
 jmespath.egg-info/PKG-INFO
 jmespath.egg-info/SOURCES.txt
 jmespath.egg-info/dependency_links.txt
+jmespath.egg-info/pbr.json
 jmespath.egg-info/top_level.txt
 tests/__init__.py
 tests/test_compliance.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/jmespath.egg-info/pbr.json 
new/jmespath-0.9.0/jmespath.egg-info/pbr.json
--- old/jmespath-0.7.1/jmespath.egg-info/pbr.json       1970-01-01 
01:00:00.000000000 +0100
+++ new/jmespath-0.9.0/jmespath.egg-info/pbr.json       2015-04-21 
08:34:36.000000000 +0200
@@ -0,0 +1 @@
+{"is_release": true, "git_version": "0466cc1"}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/setup.py new/jmespath-0.9.0/setup.py
--- old/jmespath-0.7.1/setup.py 2015-04-27 19:28:36.000000000 +0200
+++ new/jmespath-0.9.0/setup.py 2015-10-01 06:11:26.000000000 +0200
@@ -9,7 +9,7 @@
 
 setup(
     name='jmespath',
-    version='0.7.1',
+    version='0.9.0',
     description='JSON Matching Expressions',
     long_description=io.open('README.rst', encoding='utf-8').read(),
     author='James Saryerwinnie',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/tests/test_compliance.py 
new/jmespath-0.9.0/tests/test_compliance.py
--- old/jmespath-0.7.1/tests/test_compliance.py 2015-04-27 19:23:22.000000000 
+0200
+++ new/jmespath-0.9.0/tests/test_compliance.py 2015-10-01 06:11:19.000000000 
+0200
@@ -6,14 +6,14 @@
 from nose.tools import assert_equal
 
 import jmespath
-from jmespath.visitor import TreeInterpreter
+from jmespath.visitor import TreeInterpreter, Options
 
 
 TEST_DIR = os.path.dirname(os.path.abspath(__file__))
 COMPLIANCE_DIR = os.path.join(TEST_DIR, 'compliance')
 LEGACY_DIR = os.path.join(TEST_DIR, 'legacy')
 NOT_SPECIFIED = object()
-TreeInterpreter.MAP_TYPE = OrderedDict
+OPTIONS = Options(dict_cls=OrderedDict)
 
 
 def test_compliance():
@@ -65,13 +65,13 @@
         raise AssertionError(
             'jmespath expression failed to compile: "%s", error: %s"' %
             (expression, e))
-    actual = parsed.search(given)
+    actual = parsed.search(given, options=OPTIONS)
     expected_repr = json.dumps(expected, indent=4)
     actual_repr = json.dumps(actual, indent=4)
     error_msg = ("\n\n  (%s) The expression '%s' was suppose to give:\n%s\n"
                  "Instead it matched:\n%s\nparsed as:\n%s\ngiven:\n%s" % (
                      filename, expression, expected_repr,
-                     actual_repr, parsed,
+                     actual_repr, pformat(parsed.parsed),
                      json.dumps(given, indent=4)))
     error_msg = error_msg.replace(r'\n', '\n')
     assert_equal(actual, expected, error_msg)
@@ -91,6 +91,6 @@
     else:
         error_msg = ("\n\n  (%s) The expression '%s' was suppose to be a "
                      "syntax error, but it successfully parsed as:\n\n%s" % (
-                         filename, expression, parsed))
+                         filename, expression, pformat(parsed.parsed)))
         error_msg = error_msg.replace(r'\n', '\n')
         raise AssertionError(error_msg)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jmespath-0.7.1/tests/test_parser.py 
new/jmespath-0.9.0/tests/test_parser.py
--- old/jmespath-0.7.1/tests/test_parser.py     2015-04-27 19:23:22.000000000 
+0200
+++ new/jmespath-0.9.0/tests/test_parser.py     2015-10-01 06:11:19.000000000 
+0200
@@ -1,9 +1,10 @@
 #!/usr/bin/env python
 
 import re
-from tests import unittest
+from tests import unittest, OrderedDict
 
 from jmespath import parser
+from jmespath import visitor
 from jmespath import ast
 from jmespath import exceptions
 
@@ -94,6 +95,15 @@
         result = parsed.search({'foo': {'bar': {'baz': 'CORRECT'}, 'qux': 
'qux'}})
         self.assertEqual(result, {"bar": "CORRECT", "qux": "qux"})
 
+    def test_function_call_with_and_statement(self):
+        self.assert_parsed_ast(
+            'f(@ && @)',
+            {'children': [{'children': [{'children': [], 'type': 'current'},
+                                        {'children': [], 'type': 'current'}],
+                           'type': 'and_expression'}],
+             'type': 'function_expression',
+             'value': 'f'})
+
 
 class TestErrorMessages(unittest.TestCase):
 
@@ -144,18 +154,12 @@
     def test_bad_lexer_values(self):
         error_message = (
             'Bad jmespath expression: '
-            'Starting quote is missing the ending quote:\n'
+            'Unclosed " delimiter:\n'
             'foo."bar\n'
             '    ^')
         self.assert_error_message('foo."bar', error_message,
                                   exception=exceptions.LexerError)
 
-    def test_bad_lexer_literal_value_with_json_object(self):
-        error_message = ('Bad jmespath expression: '
-                         'Bad token `{{}`:\n`{{}`\n^')
-        self.assert_error_message('`{{}`', error_message,
-                                  exception=exceptions.LexerError)
-
     def test_bad_unicode_string(self):
         # This error message is straight from the JSON parser
         # and pypy has a slightly different error message,
@@ -326,6 +330,18 @@
         self.assertEqual(parsed.expression, 'foo.bar')
 
 
+class TestParsedResultAddsOptions(unittest.TestCase):
+    def test_can_have_ordered_dict(self):
+        p = parser.Parser()
+        parsed = p.parse('{a: a, b: b, c: c}')
+        options = visitor.Options(dict_cls=OrderedDict)
+        result = parsed.search(
+            {"c": "c", "b": "b", "a": "a"}, options=options)
+        # The order should be 'a', 'b' because we're using an
+        # OrderedDict
+        self.assertEqual(list(result), ['a', 'b', 'c'])
+
+
 class TestRenderGraphvizFile(unittest.TestCase):
     def test_dot_file_rendered(self):
         p = parser.Parser()


Reply via email to