diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 25d0016..f7f7a7c 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -9,22 +9,30 @@ jobs: strategy: matrix: os: [ubuntu-22.04] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + - name: Install poetry + uses: abatilo/actions-poetry@v4 + - name: Setup a local virtual environment (if no poetry.toml file) run: | - pip install pip==20.0.2 - pip install -r requirements.txt - python setup.py bdist_wheel - pip install dist/*.whl + poetry config virtualenvs.create true --local + poetry config virtualenvs.in-project true --local + - uses: actions/cache@v3 + name: Define a cache for the virtual environment based on the dependencies lock file + with: + path: ./.venv + key: venv-${{ hashFiles('poetry.lock') }} + - name: Install the project dependencies + run: poetry install + - name: Test with pytest run: | ./scripts/sync-tests diff --git a/.gitmodules b/.gitmodules index 143e143..b54ef5a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = jmespath.test url = https://github.com/jmespath-community/jmespath.test.git branch = main +[submodule "tests/jmespath.org"] + path = tests/jmespath.org + url = http://github.com/jmespath/jmespath.test.git diff --git a/CHANGELOG.rst b/CHANGELOG.rst deleted file mode 100644 index f477b5f..0000000 --- a/CHANGELOG.rst +++ /dev/null @@ -1,150 +0,0 @@ -1.1.2 -===== -* Fix unevaluated projection after a string slice - (`issue #22 `__) - -1.1.1 -===== -* JEP-11a Lexical Scoping deprecates the let function. - -1.1.0 -===== - -* JEP-11 Lexical Scoping -* JEP-13 Object Manipulation functions -* JEP-14 String functions -* JEP-15 String Slices -* JEP-16 Arithmetic Expressions -* JEP-17 Root Reference -* JEP-18 Grouping -* JEP-19 Evaluation of Pipe Expressions - (`discussion #113 `__) - -1.0.1 -===== - -* Add support for Python 3.11 - (`issue #285 `__) -* Fix boolean correctness for floats - (`issue #281 `__) -* Fix Python 3.9 warning about random sampling used in parser cache. - (`issue #216 `__) -* Fix Python 3.8 warning for type comparisons - (`issue #210 `__) - - -1.0.0 -===== - -* Drop support for Python versions less than 3.7 (including Python 2). - (`issue 268 `__) - - -0.10.0 -====== - -* Python 2.6 and 3.3 have reached end-of-life and have been deprecated. - (`issue 175 `__) -* Fix race condition when clearing cached parsed expressions. - (`issue 197 `__) - - -0.9.5 -===== - -* Fix syntax warnings on python 3.8 - (`issue 187 `__) - - -0.9.4 -===== - -* Fix ``min_by``/``max_by`` with empty lists - (`issue 151 `__) -* Fix reverse type for ``null`` type - (`issue 145 `__) - - -0.9.3 -===== - -* Fix issue where long types in py2 and ``Decimal`` types - were not being evaluated as numbers - (`issue 125 `__) -* Handle numbers in scientific notation in ``to_number()`` function - (`issue 120 `__) -* Fix issue where custom functions would override the function table - of the builtin function class - (`issue 133 `__) - - -0.9.2 -===== - -* Fix regression when using ordering comparators on strings - (`issue 124 `__) - - -0.9.1 -===== - -* Raise LexerError on invalid numbers - (`issue 98 `__) -* Add support for custom functions (#100) - (`issue 100 `__) -* Fix ZeroDivisionError for built-in function avg() on empty lists (#115) - (`issue 115 `__) -* Properly handle non numerical ordering operators (#117) - (`issue 117 `__) - - -0.9.0 -===== - -* Add support for new lines with tokens in an expression -* Add support for `JEP 9 `__, - which introduces "and" expressions, "unary" expressions, "not" expressions, - and "paren" expressions -* Fix issue with hardcoded path in ``jp.py`` executable - (`issue 90 `__, - `issue 88 `__, - `issue 82 `__) - - -0.8.0 -===== - -* Improve lexing performance (`issue 84 `__) -* Fix parsing error for multiselect lists (`issue 86 `__) -* Fix issue with escaping single quotes in literal strings (`issue 85 `__) -* Add support for providing your own dict cls to support - ordered dictionaries (`issue 94 `__) -* Add map() function (`issue 95 `__) - - -0.7.1 -===== - -* Rename ``bin/jp`` to ``bin/jp.py`` -* Fix issue with precedence when parsing wildcard - projections -* Remove ordereddict and simplejson as py2.6 dependencies. - These were never actually used in the jmespath code base, - only in the unit tests. Unittests requirements are handled - via requirements26.txt. - - -0.7.0 -===== - -* Add support for JEP-12, raw string literals -* Support .whl files - -0.6.2 -===== - -* Implement JEP-10, slice projections -* Fix bug with filter projection parsing -* Add ``to_array`` function -* Add ``merge`` function -* Fix error messages for function argument type errors diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index a5021c6..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include README.rst -include LICENSE diff --git a/README.md b/README.md new file mode 100644 index 0000000..7783667 --- /dev/null +++ b/README.md @@ -0,0 +1,249 @@ +# JMESPath Community + +[![image](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jmespath/chat) + +JMESPath (pronounced "james path") allows you to declaratively specify +how to extract elements from a JSON document. + +JMESPath Community is an unofficial community effort to promote +improvements and updates to the JMESPath language specification. + +For example, given this document: + + {"foo": {"bar": "baz"}} + +The jmespath expression `foo.bar` will return "baz". + +JMESPath also supports: + +Referencing elements in a list. Given the data: + + {"foo": {"bar": ["one", "two"]}} + +The expression: `foo.bar[0]` will return "one". You can also reference +all the items in a list using the `*` syntax: + + {"foo": {"bar": [{"name": "one"}, {"name": "two"}]}} + +The expression: `foo.bar[*].name` will return \["one", "two"\]. Negative +indexing is also supported (-1 refers to the last element in the list). +Given the data above, the expression `foo.bar[-1].name` will return +"two". + +The `*` can also be used for hash types: + + {"foo": {"bar": {"name": "one"}, "baz": {"name": "two"}}} + +The expression: `foo.*.name` will return \["one", "two"\]. + +# Installation + +You can install JMESPath Community from PyPI with: + +``` bash +pip install jmespath-community +``` + +# API + +The `jmespath.py` library has two functions that operate on python data +structures. You can use `search` and give it the jmespath expression and +the data: + +``` python +>>> import jmespath +>>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}}) +'baz' +``` + +Similar to the `re` module, you can use the `compile` function to +compile the JMESPath expression and use this parsed expression to +perform repeated searches: + +``` python +>>> import jmespath +>>> expression = jmespath.compile('foo.bar') +>>> expression.search({'foo': {'bar': 'baz'}}) +'baz' +>>> expression.search({'foo': {'bar': 'other'}}) +'other' +``` + +This is useful if you're going to use the same jmespath expression to +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: + +``` python +>>> 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(mydata, +... jmespath.Options(dict_cls=collections.OrderedDict)) +``` + +JMESPath used to support a special case json-value syntax to represent a JSON string +literal, but this was being deprecated following +[JEP-12](https://github.com/jmespath-community/jmespath.spec/blob/main/jep-012-raw-string-literals.md) +and its raw-string literal syntax. + +``` python +>>> import jmespath +>>> jmespath.search("`foo`"', {}) +jmespath.exceptions.LexerError: Bad jmespath expression: Bad token %s `foo`: +`foo` +^ +``` + +While JMESPath Community now fully deprecates this legacy syntax of +providing a JSON literal string with elided double quotes, you can still +opt-in to parse legacy syntax, by specifying the +`enable_legacy_literals` flag to the `Options` object. + +``` python +>>> import jmespath +>>> jmespath.search("`foo`"', +... mydata, +... jmespath.Options(enable_legacy_literals=True)) +'foo' + + +>>> import jmespath +>>> parsed = jmespath.compile("`foo`", +... jmespath.Options(enable_legacy_literals=True)) +>>> parsed.search(mydata) +'foo' +``` + +### Custom Functions + +The JMESPath language has numerous [built-in +functions](https://jmespath.site/main/#functions), but it is also +possible to add your own custom functions. Keep in mind that custom +function support in jmespath.py is experimental and the API may change +based on feedback. + +**If you have a custom function that you've found useful, consider +submitting it to jmespath.site and propose that it be added to the +JMESPath language.** You can submit proposals +[here](https://github.com/jmespath-community/jmespath.spec/issues). + +To create custom functions: + +- Create a subclass of `jmespath.functions.Functions`. +- Create a method with the name `_func_`. +- Apply the `jmespath.functions.signature` decorator that indicates + the expected types of the function arguments. +- Provide an instance of your subclass in a `jmespath.Options` object. + +Below are a few examples: + +``` python +import jmespath +from jmespath import functions + +# 1. Create a subclass of functions.Functions. +# The function.Functions base class has logic +# that introspects all of its methods and automatically +# registers your custom functions in its function table. +class CustomFunctions(functions.Functions): + + # 2 and 3. Create a function that starts with _func_ + # and decorate it with @signature which indicates its + # expected types. + # In this example, we're creating a jmespath function + # called "unique_letters" that accepts a single argument + # with an expected type "string". + @functions.signature({'types': ['string']}) + def _func_unique_letters(self, s): + # Given a string s, return a sorted + # string of unique letters: 'ccbbadd' -> 'abcd' + return ''.join(sorted(set(s))) + + # Here's another example. This is creating + # a jmespath function called "my_add" that expects + # two arguments, both of which should be of type number. + @functions.signature({'types': ['number']}, {'types': ['number']}) + def _func_my_add(self, x, y): + return x + y + +# 4. Provide an instance of your subclass in a Options object. +options = jmespath.Options(custom_functions=CustomFunctions()) + +# Provide this value to jmespath.search: +# This will print 3 +print( + jmespath.search( + 'my_add(`1`, `2`)', {}, options=options) +) + +# This will print "abcd" +print( + jmespath.search( + 'foo.bar | unique_letters(@)', + {'foo': {'bar': 'ccbbadd'}}, + options=options) +) +``` + +Again, if you come up with useful functions that you think make sense in +the JMESPath language (and make sense to implement in all JMESPath +libraries, not just python), please let us know at +[jmespath.site](https://github.com/jmespath-community/jmespath.spec/discussions). + +# Specification + +If you'd like to learn more about the JMESPath language, you can check +out the [JMESPath tutorial](https://jmespath.site/main/#tutorial). Also +check out the [JMESPath examples +page](https://jmespath.site/main/#examples) for examples of more complex +jmespath queries. + +The grammar is specified using ABNF, as described in +[RFC4234](http://www.ietf.org/rfc/rfc4234.txt). You can find the most up +to date [grammar for JMESPath +here](https://jmespath.site/main/#spec-grammar). + +You can read the full [JMESPath specification +here](https://jmespath.site/main/#specification). + +# Testing + +In addition to the unit tests for the jmespath modules, there is a +`tests/compliance` directory that contains .json files with test cases. +This allows other implementations to verify they are producing the +correct output. Each json file is grouped by feature. + +# Contributing + +Clone this repository and run the following commands: + +```sh +pip install pipx +source ~/.bashrc +pipx install poetry + +git submodule update --init --checkout --recursive --force +./scripts/sync-tests + +poetry install +poetry build +poetry run pytest +``` + +# Discuss + +Join us on our [Gitter channel](https://gitter.im/jmespath/chat) if you +want to chat or if you have any questions. diff --git a/README.rst b/README.rst deleted file mode 100644 index 83d8c43..0000000 --- a/README.rst +++ /dev/null @@ -1,252 +0,0 @@ -JMESPath Community -================== - - -.. image:: https://badges.gitter.im/Join Chat.svg - :target: https://gitter.im/jmespath/chat - - -JMESPath (pronounced "james path") allows you to declaratively specify how to -extract elements from a JSON document. - -JMESPath Community is an unofficial community effort to promote improvements -and updates to the JMESPath language specification. - -For example, given this document:: - - {"foo": {"bar": "baz"}} - -The jmespath expression ``foo.bar`` will return "baz". - -JMESPath also supports: - -Referencing elements in a list. Given the data:: - - {"foo": {"bar": ["one", "two"]}} - -The expression: ``foo.bar[0]`` will return "one". -You can also reference all the items in a list using the ``*`` -syntax:: - - {"foo": {"bar": [{"name": "one"}, {"name": "two"}]}} - -The expression: ``foo.bar[*].name`` will return ["one", "two"]. -Negative indexing is also supported (-1 refers to the last element -in the list). Given the data above, the expression -``foo.bar[-1].name`` will return "two". - -The ``*`` can also be used for hash types:: - - {"foo": {"bar": {"name": "one"}, "baz": {"name": "two"}}} - -The expression: ``foo.*.name`` will return ["one", "two"]. - - -Installation -============ - -You can install JMESPath Community from PyPI with: - -.. code:: bash - - pip install jmespath-community - - -API -=== - -The ``jmespath.py`` library has two functions -that operate on python data structures. You can use ``search`` -and give it the jmespath expression and the data: - -.. code:: python - - >>> import jmespath - >>> path = jmespath.search('foo.bar', {'foo': {'bar': 'baz'}}) - 'baz' - -Similar to the ``re`` module, you can use the ``compile`` function -to compile the JMESPath expression and use this parsed expression -to perform repeated searches: - -.. code:: python - - >>> import jmespath - >>> expression = jmespath.compile('foo.bar') - >>> expression.search({'foo': {'bar': 'baz'}}) - 'baz' - >>> expression.search({'foo': {'bar': 'other'}}) - 'other' - -This is useful if you're going to use the same jmespath expression to -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: - -.. code:: python - - >>> 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(mydata, - ... jmespath.Options(dict_cls=collections.OrderedDict)) - - -JMESPath used to support a special case `json-value` syntax to represent a -JSON string literal, but this was being deprecated following -`JEP-12 -`__ -and its `raw-string` literal syntax. - -.. code:: python - - >>> import jmespath - >>> jmespath.search("`foo`"', {}) - jmespath.exceptions.LexerError: Bad jmespath expression: Bad token %s `foo`: - `foo` - ^ - -While JMESPath Community now fully deprecates this legacy syntax of providing -a JSON literal string with elided double quotes, you can still opt-in to parse -legacy syntax, by specifying the ``enable_legacy_literals`` flag to the -``Options`` object. - -.. code:: python - - >>> import jmespath - >>> jmespath.search("`foo`"', - ... mydata, - ... jmespath.Options(enable_legacy_literals=True)) - 'foo' - - - >>> import jmespath - >>> parsed = jmespath.compile("`foo`", - ... jmespath.Options(enable_legacy_literals=True)) - >>> parsed.search(mydata) - 'foo' - - -Custom Functions -~~~~~~~~~~~~~~~~ - -The JMESPath language has numerous -`built-in functions -`__, but it is -also possible to add your own custom functions. Keep in mind that -custom function support in jmespath.py is experimental and the API may -change based on feedback. - -**If you have a custom function that you've found useful, consider submitting -it to jmespath.site and propose that it be added to the JMESPath language.** -You can submit proposals -`here `__. - -To create custom functions: - -* Create a subclass of ``jmespath.functions.Functions``. -* Create a method with the name ``_func_``. -* Apply the ``jmespath.functions.signature`` decorator that indicates - the expected types of the function arguments. -* Provide an instance of your subclass in a ``jmespath.Options`` object. - -Below are a few examples: - -.. code:: python - - import jmespath - from jmespath import functions - - # 1. Create a subclass of functions.Functions. - # The function.Functions base class has logic - # that introspects all of its methods and automatically - # registers your custom functions in its function table. - class CustomFunctions(functions.Functions): - - # 2 and 3. Create a function that starts with _func_ - # and decorate it with @signature which indicates its - # expected types. - # In this example, we're creating a jmespath function - # called "unique_letters" that accepts a single argument - # with an expected type "string". - @functions.signature({'types': ['string']}) - def _func_unique_letters(self, s): - # Given a string s, return a sorted - # string of unique letters: 'ccbbadd' -> 'abcd' - return ''.join(sorted(set(s))) - - # Here's another example. This is creating - # a jmespath function called "my_add" that expects - # two arguments, both of which should be of type number. - @functions.signature({'types': ['number']}, {'types': ['number']}) - def _func_my_add(self, x, y): - return x + y - - # 4. Provide an instance of your subclass in a Options object. - options = jmespath.Options(custom_functions=CustomFunctions()) - - # Provide this value to jmespath.search: - # This will print 3 - print( - jmespath.search( - 'my_add(`1`, `2`)', {}, options=options) - ) - - # This will print "abcd" - print( - jmespath.search( - 'foo.bar | unique_letters(@)', - {'foo': {'bar': 'ccbbadd'}}, - options=options) - ) - -Again, if you come up with useful functions that you think make -sense in the JMESPath language (and make sense to implement in all -JMESPath libraries, not just python), please let us know at -`jmespath.site `__. - - -Specification -============= - -If you'd like to learn more about the JMESPath language, you can check out -the `JMESPath tutorial `__. Also check -out the `JMESPath examples page `__ for -examples of more complex jmespath queries. - -The grammar is specified using ABNF, as described in -`RFC4234 `_. -You can find the most up to date -`grammar for JMESPath here `__. - -You can read the full -`JMESPath specification here `__. - - -Testing -======= - -In addition to the unit tests for the jmespath modules, -there is a ``tests/compliance`` directory that contains -.json files with test cases. This allows other implementations -to verify they are producing the correct output. Each json -file is grouped by feature. - - -Discuss -======= - -Join us on our `Gitter channel `__ -if you want to chat or if you have any questions. diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index fd63973..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,153 +0,0 @@ -# Makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build - -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -# the i18n builder cannot share the environment and doctrees with the others -I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . - -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext - -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " texinfo to make Texinfo files" - @echo " info to make Texinfo files and run them through makeinfo" - @echo " gettext to make PO message catalogs" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" - -clean: - -rm -rf $(BUILDDIR)/* - -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/JamesPath.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/JamesPath.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/JamesPath" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/JamesPath" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - $(MAKE) -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -texinfo: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo - @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." - @echo "Run \`make' in that directory to run these through makeinfo" \ - "(use \`make info' here to do that automatically)." - -info: - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo - @echo "Running Texinfo files through makeinfo..." - make -C $(BUILDDIR)/texinfo info - @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." - -gettext: - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale - @echo - @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 369603a..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,97 +0,0 @@ -# -*- coding: utf-8 -*- -import guzzle_sphinx_theme - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'JMESPath' -copyright = u'2014, James Saryerwinnie' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.8' -# The full version, including alpha/beta/rc tags. -release = '1.1.2' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'guzzle_sphinx_theme.GuzzleStyle' - -html_translator_class = 'guzzle_sphinx_theme.HTMLTranslator' -html_theme_path = guzzle_sphinx_theme.html_theme_path() -html_theme = 'guzzle_sphinx_theme' -extensions.append("guzzle_sphinx_theme") - - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -html_theme_options = html_theme_options = { - # Set the name of the project to appear in the nav menu - "project_nav_name": "JMESPath", - - # Set your GitHub user and repo to enable GitHub stars links - "github_user": "boto", - "github_repo": "jmespath", - - # Set to true to bind left and right key events to turn the page - "bind_key_events": 1, -} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# Output file base name for HTML help builder. -htmlhelp_basename = 'JMESPathdoc' diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index a9dac50..0000000 --- a/docs/index.rst +++ /dev/null @@ -1,34 +0,0 @@ -.. JamesPath documentation master file, created by - sphinx-quickstart on Tue Feb 19 14:49:37 2013. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -JMESPath -======== - -JSON Matching Expression paths. JMESPath allows you -to declaratively specify how to extract elements from a JSON document. - -For example, given this document:: - - {"foo": {"bar": "baz"}} - -The jmespath expression ``foo.bar`` will return "baz". - - -Contents: - -.. toctree:: - :maxdepth: 2 - - specification - proposals - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/docs/proposals.rst b/docs/proposals.rst deleted file mode 100644 index c09c148..0000000 --- a/docs/proposals.rst +++ /dev/null @@ -1,16 +0,0 @@ -================== -JMESPath Proposals -================== - -This document lists all of the proposed JMESPath syntax and functionality -changes. Proposals are marked as either "draft", "accepted", or "rejected". - -.. toctree:: - :maxdepth: 1 - - proposals/nested-expressions - proposals/improved-identifiers - proposals/filter-expressions - proposals/pipes - proposals/functions - proposals/exptype diff --git a/docs/proposals/array-slices.rst b/docs/proposals/array-slices.rst deleted file mode 100644 index 7a67dcb..0000000 --- a/docs/proposals/array-slices.rst +++ /dev/null @@ -1,109 +0,0 @@ -======================= -Array Slice Expressions -======================= - -:JEP: 5 -:Author: Michael Dowling -:Status: draft -:Created: 08-Dec-2013 - -Abstract -======== - -This document proposes modifying the JMESPath grammar to support array slicing -for accessing specific portions of an array. - -Motivation -========== - -The current JMESPath grammar does not allow plucking out specific portions of -an array. - -The following examples are possible with array slicing notation utilizing an -optional start position, optional stop position, and optional step that can be -less than or greater than 0: - -1. Extracting every N indices (e.g., only even ``[::2]``, only odd ``[1::2]``, - etc) -2. Extracting only elements after a given start position: ``[2:]`` -3. Extracting only elements before a given stop position: ``[:5]`` -4. Extracting elements between a given start and end position: ``[2::5]`` -5. Only the last 5 elements: ``[-5:]`` -6. The last five elements in reverse order: ``[:-5:-1]`` -7. Reversing the order of an array: ``[::-1]`` - -Syntax -====== - -This syntax introduces Python style array slicing that allows a start position, -stop position, and step. This syntax also proposes following the same semantics -as python slices. - -:: - - [start:stop:step] - -Each part of the expression is optional. You can omit the start position, stop -position, or step. No more than three values can be provided in a slice -expression. - -The step value determines how my indices to skip after each element is plucked -from the array. A step of 1 (the default step) will not skip any indices. A -step value of 2 will skip every other index while plucking values from an -array. A step value of -1 will extract values in reverse order from the array. -A step value of -2 will extract values in reverse order from the array while, -skipping every other index. - -Slice expressions adhere to the following rules: - -1. If a negative start position is given, it is calculated as the total length - of the array plus the given start position. -2. If no start position is given, it is assumed to be 0 if the given step is - greater than 0 or the end of the array if the given step is less than 0. -3. If a negative stop position is given, it is calculated as the total length - of the array plus the given stop position. -4. If no stop position is given, it is assumed to be the length of the array if - the given step is greater than 0 or 0 if the given step is less than 0. -5. If the given step is omitted, it it assumed to be 1. -6. If the given step is 0, an error must be raised. -7. If the element being sliced is not an array, the result must be ``null``. -8. If the element being sliced is an array and yields no results, the result - must be an empty array. - - -Modified Grammar -================ - -The following modified JMESPath grammar supports array slicing. - -:: - - expression = sub-expression / index-expression / or-expression / identifier / "*" - expression =/ multi-select-list / multi-select-hash - sub-expression = expression "." expression - or-expression = expression "||" expression - index-expression = expression bracket-specifier / bracket-specifier - multi-select-list = "[" ( expression *( "," expression ) ) "]" - multi-select-hash = "{" ( keyval-expr *( "," keyval-expr ) ) "}" - keyval-expr = identifier ":" expression - bracket-specifier = "[" (number / "*" / slice-expression) "]" / "[]" - slice-expression = ":" - slice-expression =/ number ":" number ":" number - slice-expression =/ number ":" - slice-expression =/ number ":" ":" number - slice-expression =/ ":" number - slice-expression =/ ":" number ":" number - slice-expression =/ ":" ":" number - number = [-]1*digit - digit = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "0" - identifier = 1*char - identifier =/ quote 1*(unescaped-char / escaped-quote) quote - escaped-quote = escape quote - unescaped-char = %x30-10FFFF - escape = %x5C ; Back slash: \ - quote = %x22 ; Double quote: '"' - char = %x30-39 / ; 0-9 - %x41-5A / ; A-Z - %x5F / ; _ - %x61-7A / ; a-z - %x7F-10FFFF diff --git a/docs/proposals/exptype.rst b/docs/proposals/exptype.rst deleted file mode 100644 index ba90eae..0000000 --- a/docs/proposals/exptype.rst +++ /dev/null @@ -1,273 +0,0 @@ -================ -Expression Types -================ - -:JEP: 8 -:Author: James Saryerwinnie -:Status: accepted -:Created: 02-Mar-2013 - -Abstract -======== - -This JEP proposes grammar modifications to JMESPath to allow for -expression references within functions. This allows for functions -such as ``sort_by``, ``max_by``, ``min_by``. These functions take -an argument that resolves to an expression type. This enables -functionality such as sorting an array based on an expression that -is evaluated against every array element. - - -Motivation -========== - -A useful feature that is common in other expression languages is the -ability to sort a JSON object based on a particular key. For example, -given a JSON object: - -.. code:: json - - { - "people": [ - {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"}, - {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"}, - {"age": 30, "age_str": "30", "bool": true, "name": "c"}, - {"age": 50, "age_str": "50", "bool": false, "name": "d"}, - {"age": 10, "age_str": "10", "bool": true, "name": 3} - ] - } - -It is not currently possible to sort the ``people`` array by the ``age`` key. -Also, ``sort`` is not defined for the ``object`` type, so it's not currently -possible to even sort the ``people`` array. In order to sort the ``people`` array, -we need to know what key to use when sorting the array. - -This concept of sorting based on a key can be generalized. Instead of -requiring a key name, an expression can be provided that each element -would be evaluated against. In the simplest case, this expression would just -be an ``identifier``, but more complex expressions could be used such as -``foo.bar.baz``. - -A simple way to accomplish this might be to create a function like this: - -.. code:: python - - sort_by(array arg1, expression) - - # Called like: - - sort_by(people, age) - sort_by(people, to_number(age_str)) - -However, there's a problem with the ``sort_by`` function as defined above. -If we follow the function argument resolution process we get: - -.. code:: python - - sort_by(people, age) - - # 1. resolve people - arg1 = search(people, ) -> [{"age": ...}, {...}] - - # 2. resolve age - arg2 = search(age, ) -> null - - sort_by([{"age": ...}, {...}], null) - -The second argument is evaluated against the current node and the expression -``age`` will resolve to ``null`` because the input data has no ``age`` key. -There needs to be some way to specify that an expression should evaluate to -an expression type:: - - arg = search(, ) -> - -Then the function definition of ``sort_by`` would be:: - - sort_by(array arg1, expression arg2) - - -Specification -============= - -The following grammar rules will be updated to:: - - function-arg = expression / - current-node / - "&" expression - -Evaluating an expression reference should return an object of type -"expression". The list of data types supported by a function will now be: - -* number (integers and double-precision floating-point format in JSON) -* string -* boolean (``true`` or ``false``) -* array (an ordered, sequence of values) -* object (an unordered collection of key value pairs) -* null -* expression (denoted by ``&expression``) - -Function signatures can now be specified using this new ``expression`` type. -Additionally, a function signature can specify the return type of the -expression. Similarly how arrays can specify a type within a list using the -``array[type]`` syntax, expressions can specify their resolved type using -``expression->type`` syntax. - -Note that any valid expression is allowed after ``&``, so the following -expressions are valid: - -.. code:: python - - sort_by(people, &foo.bar.baz) - sort_by(people, &foo.bar[0].baz) - sort_by(people, &to_number(foo[0].bar)) - - -Additional Functions --------------------- - -The following functions will be added: - -sort_by -~~~~~~~ - -:: - - sort_by(array elements, expression->number|expression->string expr) - -Sort an array using an expression ``expr`` as the sort key. -Below are several examples using the ``people`` array (defined above) as the -given input. ``sort_by`` follows the same sorting logic as the ``sort`` -function. - - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``sort_by(people, &age)[].age`` - - [10, 20, 30, 40, 50] - * - ``sort_by(people, &age)[0]`` - - {"age": 10, "age_str": "10", "bool": true, "name": 3} - * - ``sort_by(people, &to_number(age_str))[0]`` - - {"age": 10, "age_str": "10", "bool": true, "name": 3} - - -max_by -~~~~~~ - -:: - - max_by(array elements, expression->number expr) - -Return the maximum element in an array using the expression ``expr`` as the -comparison key. The entire maximum element is returned. -Below are several examples using the ``people`` array (defined above) as the -given input. - - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``max_by(people, &age)`` - - {"age": 50, "age_str": "50", "bool": false, "name": "d"}, - * - ``max_by(people, &age).age`` - - 50 - * - ``max_by(people, &to_number(age_str))`` - - {"age": 50, "age_str": "50", "bool": false, "name": "d"}, - * - ``max_by(people, &age_str)`` - - - * - ``max_by(people, age)`` - - - - -min_by -~~~~~~ - -:: - - min_by(array elements, expression->number expr) - -Return the minimum element in an array using the expression ``expr`` as the -comparison key. The entire maximum element is returned. -Below are several examples using the ``people`` array (defined above) as the -given input. - - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``min_by(people, &age)`` - - {"age": 10, "age_str": "10", "bool": true, "name": 3} - * - ``min_by(people, &age).age`` - - 10 - * - ``min_by(people, &to_number(age_str))`` - - {"age": 10, "age_str": "10", "bool": true, "name": 3} - * - ``min_by(people, &age_str)`` - - - * - ``min_by(people, age)`` - - - - -Alternatives ------------- - -There were a number of alternative proposals considered. Below outlines -several of these alternatives. - -Logic in Argument Resolver -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The first proposed choice (which was originally in JEP-3 but later removed) was -to not have any syntactic construct for specifying functions, and to allow the -function signature to dictate whether or not an argument was resolved. The -signature for ``sort_by`` would be:: - - sort_by(array arg1, any arg2) - arg1 -> resolved - arg2 -> not resolved - -Then the argument resolver would introspect the argument specification of a -function to determine what to do. Roughly speaking, the pseudocode would be:: - - call-function(current-data) - arglist = [] - for each argspec in functions-argspec: - if argspect.should_resolve: - arglist <- resolve(argument, current-data) - else - arglist <- argument - type-check(arglist) - return invoke-function(arglist) - -However, there are several reasons not to do this: - -* This imposes a specific implementation. This implementation would be - challenging in a bytecode VM, as the CALL bytecode will typically - resolve arguments onto the stack and allow the function to then - pop arguments off the stack and perform its own arity validation. -* This deviates from the "standard" model of how functions are - traditionally implemented. - - -Specifying Expressions as Strings -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Another proposed alternative was to allow the expression to be -a string type and to give functions the capability to parse/eval -expressions. The ``sort_by`` function would look like this:: - - sort_by(people, `age`) - sort_by(people, `foo.bar.baz`) - -The main reasons this proposal was not chosen was because: - -* This complicates the implementations. For implementations that walk the AST - inline, this means AST nodes need access to the parser. For external tree - visitors, the visitor needs access to the parser. -* This moves what *could* by a compile time error into a run time error. The - evaluation of the expression string happens when the function is invoked. diff --git a/docs/proposals/filter-expressions.rst b/docs/proposals/filter-expressions.rst deleted file mode 100644 index ad13b84..0000000 --- a/docs/proposals/filter-expressions.rst +++ /dev/null @@ -1,260 +0,0 @@ -================== -Filter Expressions -================== - -:JEP: 7 -:Author: James Saryerwinnie -:Status: accepted -:Created: 16-Dec-2013 - - -Abstract -======== - -This JEP proposes grammar modifications to JMESPath to allow for filter -expressions. A filtered expression allows list elements to be selected -based on matching expressions. A literal expression -is also introduced (from JEP 3) so that it is possible to match elements -against literal values. - - -Motivation -========== - -A common request when querying JSON objects is the ability to select -elements based on a specific value. For example, given a JSON object:: - - {"foo": [{"state": "WA", "value": 1}, - {"state": "WA", "value": 2}, - {"state": "CA", "value": 3}, - {"state": "CA", "value": 4}]} - -A user may want to select all objects in the ``foo`` list that have -a ``state`` key of ``WA``. There is currently no way to do this -in JMESPath. This JEP will introduce a syntax that allows this:: - - foo[?state == `WA`] - -Additionally, a user may want to project additional expressions onto the values -matched from a filter expression. For example, given the data above, select -the ``value`` key from all objects that have a ``state`` of ``WA``:: - - foo[?state == `WA`].value - -would return ``[1, 2]``. - - -Specification -============= - -The updated grammar for filter expressions:: - - bracket-specifier = "[" (number / "*") "]" / "[]" - bracket-specifier =/ "[?" list-filter-expression "]" - list-filter-expression = expression comparator expression - comparator = "<" / "<=" / "==" / ">=" / ">" / "!=" - expression =/ literal - literal = "`" json-value "`" - literal =/ "`" 1*(unescaped-literal / escaped-literal) "`" - unescaped-literal = %x20-21 / ; space ! - %x23-5A / ; # - [ - %x5D-5F / ; ] ^ _ - %x61-7A ; a-z - %x7C-10FFFF ; |}~ ... - escaped-literal = escaped-char / (escape %x60) - -The ``json-value`` rule is any valid json value. While it's recommended -that implementations use an existing JSON parser to parse the -``json-value``, the grammar is added below for completeness:: - - json-value = "false" / "null" / "true" / json-object / json-array / - json-number / json-quoted-string - json-quoted-string = %x22 1*(unescaped-literal / escaped-literal) %x22 - begin-array = ws %x5B ws ; [ left square bracket - begin-object = ws %x7B ws ; { left curly bracket - end-array = ws %x5D ws ; ] right square bracket - end-object = ws %x7D ws ; } right curly bracket - name-separator = ws %x3A ws ; : colon - value-separator = ws %x2C ws ; , comma - ws = *(%x20 / ; Space - %x09 / ; Horizontal tab - %x0A / ; Line feed or New line - %x0D ; Carriage return - ) - json-object = begin-object [ member *( value-separator member ) ] end-object - member = quoted-string name-separator json-value - json-array = begin-array [ json-value *( value-separator json-value ) ] end-array - json-number = [ minus ] int [ frac ] [ exp ] - decimal-point = %x2E ; . - digit1-9 = %x31-39 ; 1-9 - e = %x65 / %x45 ; e E - exp = e [ minus / plus ] 1*DIGIT - frac = decimal-point 1*DIGIT - int = zero / ( digit1-9 *DIGIT ) - minus = %x2D ; - - plus = %x2B ; + - zero = %x30 ; 0 - - -Comparison Operators --------------------- - -The following operations are supported: - -* ``==``, tests for equality. -* ``!=``, tests for inequality. -* ``<``, less than. -* ``<=``, less than or equal to. -* ``>``, greater than. -* ``>=``, greater than or equal to. - -The behavior of each operation is dependent on the type of each evaluated -expression. - -The comparison semantics for each operator are defined below based on -the corresponding JSON type: - -Equality Operators -~~~~~~~~~~~~~~~~~~ - -For ``string/number/true/false/null`` types, equality is an exact match. A -``string`` is equal to another ``string`` if they they have the exact sequence -of code points. The literal values ``true/false/null`` are only equal to their -own literal values. Two JSON objects are equal if they have the same set -of keys (for each key in the first JSON object there exists a key with equal -value in the second JSON object). Two JSON arrays are equal if they have -equal elements in the same order (given two arrays ``x`` and ``y``, -for each ``i`` in ``x``, ``x[i] == y[i]``). - -Ordering Operators -~~~~~~~~~~~~~~~~~~ - -Ordering operators ``>, >=, <, <=`` are **only** valid for numbers. -Evaluating any other type with a comparison operator will yield a ``null`` -value, which will result in the element being excluded from the result list. -For example, given:: - - search('foo[?a "foobar" - `"foobar"` -> "foobar" - `123` -> 123 - `"123"` -> "123" - `123.foo` -> "123.foo" - `true` -> true - `"true"` -> "true" - `truee` -> "truee" - -Literal expressions aren't allowed on the right hand side of a subexpression:: - - foo[*].`literal` - -but they are allowed on the left hand side:: - - `{"foo": "bar"}`.foo - -They may also be included in other expressions outside of a filter expressions. -For example:: - - {value: foo.bar, type: `multi-select-hash`} - - -Rationale -========= - -The proposed filter expression syntax was chosen such that there is sufficient -expressive power for any type of filter one might need to perform while at the -same time being as minimal as possible. To help illustrate this, below are a -few alternate syntax that were considered. - -In the simplest case where one might filter a key based on a literal value, -a possible filter syntax would be:: - - foo[bar == baz] - -or in general terms: ``[identifier comparator literal-value]``. However this -has several issues: - -* It is not possible to filter based on two expressions (get all elements whose - ``foo`` key equals its ``bar`` key. -* The literal value is on the right hand side, making it hard to troubleshoot - if the identifier and literal value are swapped: ``foo[baz == bar]``. -* Without some identifying token unary filters would not be possible as they - would be ambiguous. Is the expression ``[foo]`` filtering all elements with - a foo key with a truth value or is it a multiselect-list selecting the - ``foo`` key from each hash? Starting a filter expression with a token such - as ``[?`` make it clear that this is a filter expression. -* This makes the syntax for filtering against literal JSON arrays and objects - hard to visually parse. "Filter all elements whose ``foo`` key is a single - list with a single integer value of 2: ``[foo == [2]]``. -* Adding literal expressions makes them useful even outside of a filter - expression. For example, in a ``multi-select-hash``, you can create - arbitrary key value pairs: ``{a: foo.bar, b: `some string`}``. - - -This JEP is purposefully minimal. There are several extensions that can be -added in future: - -* Support any arbitrary expression within the ``[? ... ]``. This would - enable constructs such as or expressions within a filter. This would - allow unary expressions. - -In order for this to be useful we need to define what corresponds to true and -false values, e.g. an empty list is a false value. Additionally, "or -expressions" would need to change its semantics to branch based on the -true/false value of an expression instead of whether or not the expression -evaluates to null. - -This is certainly a direction to take in the future, adding arbitrary -expressions in a filter would be a backwards compatible change, so it's not -part of this JEP. - -* Allow filter expressions as top level expressions. This would potentially - just return ``true/false`` for any value that it matched. - -This might be useful if you can combine this with something that can accept -a list to use as a mask for filtering other elements. diff --git a/docs/proposals/functions.rst b/docs/proposals/functions.rst deleted file mode 100644 index ab910be..0000000 --- a/docs/proposals/functions.rst +++ /dev/null @@ -1,707 +0,0 @@ -========= -Functions -========= - -:JEP: 3 -:Author: Michael Dowling, James Saryerwinnie -:Status: Draft -:Created: 27-Nov-2013 - -Abstract -======== - -This document proposes modifying the -`JMESPath grammar `__ -to support function expressions. - -Motivation -========== - -Functions allow users to easily transform and filter data in JMESPath -expressions. As JMESPath is currently implemented, functions would be very useful -in ``multi-select-list`` and ``multi-select-hash`` expressions to format the -output of an expression to contain data that might not have been in the -original JSON input. Combined with filtered expressions, functions -would be a powerful mechanism to perform any kind of special comparisons for -things like ``length()``, ``contains()``, etc. - -Data Types -========== - -In order to support functions, a type system is needed. The JSON types are used: - -* number (integers and double-precision floating-point format in JSON) -* string -* boolean (``true`` or ``false``) -* array (an ordered, sequence of values) -* object (an unordered collection of key value pairs) -* null - -Syntax Changes -============== - -Functions are defined in the ``function-expression`` rule below. A function -expression is an ``expression`` itself, and is valid any place an -``expression`` is allowed. - -The grammar will require the following grammar additions: - -:: - - function-expression = identifier "(" *(function-arg *("," function-arg ) ) ")" - function-arg = expression / number / current-node - current-node = "@" - -``expression`` will need to be updated to add the ``function-expression`` production: - -:: - - expression = sub-expression / index-expression / or-expression / identifier / "*" - expression =/ multi-select-list / multi-select-hash - expression =/ literal / function-expression - -A function can accept any number of arguments, and each argument can be an -expression. Each function must define a signature that specifies the number -and allowed types of its expected arguments. Functions can be variadic. - - -current-node ------------- - -The ``current-node`` token can be used to represent the current node being -evaluated. The ``current-node`` token is useful for functions that require the -current node being evaluated as an argument. For example, the following -expression creates an array containing the total number of elements in the -``foo`` object followed by the value of ``foo["bar"]``. - -:: - - foo[].[count(@), bar] - -JMESPath assumes that all function arguments operate on the current node unless -the argument is a ``literal`` or ``number`` token. Because of this, an -expression such as ``@.bar`` would be equivalent to just ``bar``, so the -current node is only allowed as a bare expression. - - -current-node state -~~~~~~~~~~~~~~~~~~ - -At the start of an expression, the value of the current node is the data -being evaluated by the JMESPath expression. As an expression is evaluated, the -value the the current node represents MUST change to reflect the node currently -being evaluated. When in a projection, the current node value MUST be changed -to the node currently being evaluated by the projection. - - -Function Evaluation -=================== - -Functions are evaluated in applicative order. Each argument must be an -expression, each argument expression must be evaluated before evaluating the -function. The function is then called with the evaluated function arguments. -The result of the ``function-expression`` is the result returned by the -function call. If a ``function-expression`` is evaluated for a function that -does not exist, the JMESPath implementation must indicate to the caller that an -``unknown-function`` error occurred. How and when this error is raised is -implementation specific, but implementations should indicate to the caller that -this specific error occurred. - -Functions can either have a specific arity or be variadic with a minimum -number of arguments. If a ``function-expression`` is encountered where the -arity does not match or the minimum number of arguments for a variadic function -is not provided, then implementations must indicate to the caller than an -``invalid-arity`` error occurred. How and when this error is raised is -implementation specific. - -Each function signature declares the types of its input parameters. If any -type constraints are not met, implementations must indicate that an -``invalid-type`` error occurred. - -In order to accommodate type constraints, functions are provided to convert -types to other types (``to_string``, ``to_number``) which are defined below. -No explicit type conversion happens unless a user specifically uses one of -these type conversion functions. - -Function expressions are also allowed as the child element of a sub expression. -This allows functions to be used with projections, which can enable functions -to be applied to every element in a projection. For example, given the input -data of ``["1", "2", "3", "notanumber", true]``, the following expression can -be used to convert (and filter) all elements to numbers:: - - search([].to_number(@), ``["1", "2", "3", "notanumber", true]``) -> [1, 2, 3] - -This provides a simple mechanism to explicitly convert types when needed. - - -Built-in Functions -================== - -JMESPath has various built-in functions that operate on different -data types, documented below. Each function below has a signature -that defines the expected types of the input and the type of the returned -output:: - - return_type function_name(type $argname) - return_type function_name2(type1|type2 $argname) - -If a function can accept multiple types for an input value, then the -multiple types are separated with ``|``. If the resolved arguments do not -match the types specified in the signature, an ``invalid-type`` error occurs. - -The ``array`` type can further specify requirements on the type of the elements -if they want to enforce homogeneous types. The subtype is surrounded by -``[type]``, for example, the function signature below requires its input -argument resolves to an array of numbers:: - - return_type foo(array[number] $argname) - -As a shorthand, the type ``any`` is used to indicate that the argument can be -of any type (``array|object|number|string|boolean|null``). - -The first function below, ``abs`` is discussed in detail to demonstrate the -above points. Subsequent function definitions will not include these details -for brevity, but the same rules apply. - -.. note:: - - All string related functions are defined on the basis of Unicode code - points; they do not take normalization into account. - - -abs ---- - -:: - - number abs(number $value) - -Returns the absolute value of the provided argument. The signature indicates -that a number is returned, and that the input argument ``$value`` **must** -resolve to a number, otherwise a ``invalid-type`` error is triggered. - -Below is a worked example. Given:: - - {"foo": -1, "bar": "2"} - -Evaluating ``abs(foo)`` works as follows: - -1. Evaluate the input argument against the current data:: - - search(foo, {"foo": -11, "bar": 2"}) -> -1 - -2. Validate the type of the resolved argument. In this case - ``-1`` is of type ``number`` so it passes the type check. - -3. Call the function with the resolved argument:: - - abs(-1) -> 1 - -4. The value of ``1`` is the resolved value of the function expression - ``abs(foo)``. - - -Below is the same steps for evaluating ``abs(bar)``: - -1. Evaluate the input argument against the current data:: - - search(foo, {"foo": -1, "bar": 2"}) -> "2" - -2. Validate the type of the resolved argument. In this case - ``"2`` is of type ``string`` so the immediate indicate that - an ``invalid-type`` error occurred. - - -As a final example, here is the steps for evaluating ``abs(to_number(bar))``: - -1. Evaluate the input argument against the current data:: - - search(to_number(bar), {"foo": -1, "bar": "2"}) - -2. In order to evaluate the above expression, we need to evaluate - ``to_number(bar)``:: - - search(bar, {"foo": -1, "bar": "2"}) -> "2" - # Validate "2" passes the type check for to_number, which it does. - to_number("2") -> 2 - -3. Now we can evaluate the original expression:: - - search(to_number(bar), {"foo": -1, "bar": "2"}) -> 2 - -4. Call the function with the final resolved value:: - - abs(2) -> 2 - -5. The value of ``2`` is the resolved value of the function expression - ``abs(to_number(bar))``. - - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``abs(1)`` - - 1 - * - ``abs(-1)`` - - 1 - * - ``abs(`abc`)`` - - ```` - - -avg ---- - -:: - - number avg(array[number] $elements) - -Returns the average of the elements in the provided array. - -An empty array will produce a return value of null. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``[10, 15, 20]`` - - ``avg(@)`` - - 15 - * - ``[10, false, 20]`` - - ``avg(@)`` - - ```` - * - ``[false]`` - - ``avg(@)`` - - ```` - * - ``false`` - - ``avg(@)`` - - ```` - - -contains --------- - -:: - - boolean contains(array|string $subject, array|object|string|number|boolean $search) - -Returns ``true`` if the given ``$subject`` contains the provided ``$search`` -string. - -If ``$subject`` is an array, this function returns true if one of the elements -in the array is equal to the provided ``$search`` value. - -If the provided ``$subject`` is a string, this function returns true if -the string contains the provided ``$search`` argument. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - n/a - - ``contains(`foobar`, `foo`)`` - - ``true`` - * - n/a - - ``contains(`foobar`, `not`)`` - - ``false`` - * - n/a - - ``contains(`foobar`, `bar`)`` - - ``true`` - * - n/a - - ``contains(`false`, `bar`)`` - - ```` - * - n/a - - ``contains(`foobar`, 123)`` - - ``false`` - * - ``["a", "b"]`` - - ``contains(@, `a`)`` - - ``true`` - * - ``["a"]`` - - ``contains(@, `a`)`` - - ``true`` - * - ``["a"]`` - - ``contains(@, `b`)`` - - ``false`` - -ceil ----- - -:: - - number ceil(number $value) - -Returns the next highest integer value by rounding up if necessary. - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``ceil(`1.001`)`` - - 2 - * - ``ceil(`1.9`)`` - - 2 - * - ``ceil(`1`)`` - - 1 - * - ``ceil(`abc`)`` - - ``null`` - -floor ------ - -:: - - number floor(number $value) - -Returns the next lowest integer value by rounding down if necessary. - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``floor(`1.001`)`` - - 1 - * - ``floor(`1.9`)`` - - 1 - * - ``floor(`1`)`` - - 1 - - -join ----- - -:: - - string join(string $glue, array[string] $stringsarray) - -Returns all of the elements from the provided ``$stringsarray`` array joined -together using the ``$glue`` argument as a separator between each. - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``["a", "b"]`` - - ``join(`, `, @)`` - - "a, b" - * - ``["a", "b"]`` - - ``join(``, @)`` - - "ab" - * - ``["a", false, "b"]`` - - ``join(`, `, @)`` - - ```` - * - ``[false]`` - - ``join(`, `, @)`` - - ```` - - -keys ----- - -:: - - array keys(object $obj) - -Returns an array containing the keys of the provided object. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``{"foo": "baz", "bar": "bam"}`` - - ``keys(@)`` - - ``["foo", "bar"]`` - * - ``{}`` - - ``keys(@)`` - - ``[]`` - * - ``false`` - - ``keys(@)`` - - ```` - * - ``[b, a, c]`` - - ``keys(@)`` - - ```` - - -length ------- - -:: - - number length(string|array|object $subject) - -Returns the length of the given argument using the following types rules: - -1. string: returns the number of code points in the string -2. array: returns the number of elements in the array -3. object: returns the number of key-value pairs in the object - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - n/a - - ``length(`abc`)`` - - 3 - * - "current" - - ``length(@)`` - - 7 - * - "current" - - ``length(not_there)`` - - ```` - * - ``["a", "b", "c"]`` - - ``length(@)`` - - 3 - * - ``[]`` - - ``length(@)`` - - 0 - * - ``{}`` - - ``length(@)`` - - 0 - * - ``{"foo": "bar", "baz": "bam"}`` - - ``length(@)`` - - 2 - - -max ---- - -:: - - number max(array[number] $collection) - -Returns the highest found number in the provided array argument. - -An empty array will produce a return value of null. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``[10, 15]`` - - ``max(@)`` - - 15 - * - ``[10, false, 20]`` - - ``max(@)`` - - ```` - - -min ---- - -:: - - number min(array[number] $collection) - -Returns the lowest found number in the provided ``$collection`` argument. - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``[10, 15]`` - - ``min(@)`` - - 10 - * - ``[10, false, 20]`` - - ``min(@)`` - - ```` - - -sort ----- - -:: - - array sort(array $list) - -This function accepts an array ``$list`` argument and returns the sorted -elements of the ``$list`` as an array. - -The array must be a list of strings or numbers. Sorting strings is based on -code points. Locale is not taken into account. - - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``[b, a, c]`` - - ``sort(@)`` - - ``[a, b, c]`` - * - ``[1, a, c]`` - - ``sort(@)`` - - ``[1, a, c]`` - * - ``[false, [], null]`` - - ``sort(@)`` - - ``[[], null, false]`` - * - ``[[], {}, false]`` - - ``sort(@)`` - - ``[{}, [], false]`` - * - ``{"a": 1, "b": 2}`` - - ``sort(@)`` - - ``null`` - * - ``false`` - - ``sort(@)`` - - ``null`` - - -to_string ---------- - -:: - - string to_string(string|number|array|object|boolean $arg) - -* string - Returns the passed in value. -* number/array/object/boolean - The JSON encoded value of the object. The - JSON encoder should emit the encoded JSON value without adding any additional - new lines. - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``null`` - - ``to_string(`2`)`` - - ``"2"`` - - -to_number ---------- - -:: - - number to_number(string|number $arg) - -* string - Returns the parsed number. Any string that conforms to the - ``json-number`` production is supported. -* number - Returns the passed in value. -* array - null -* object - null -* boolean - null - - -type ----- - -:: - - string type(array|object|string|number|boolean|null $subject) - -Returns the JavaScript type of the given ``$subject`` argument as a string -value. - -The return value MUST be one of the following: - -* number -* string -* boolean -* array -* object -* null - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - "foo" - - ``type(@)`` - - "string" - * - ``true`` - - ``type(@)`` - - "boolean" - * - ``false`` - - ``type(@)`` - - "boolean" - * - ``null`` - - ``type(@)`` - - "null" - * - 123 - - ``type(@)`` - - number - * - 123.05 - - ``type(@)`` - - number - * - ``["abc"]`` - - ``type(@)`` - - "array" - * - ``{"abc": "123"}`` - - ``type(@)`` - - "object" - - -values ------- - -:: - - array values(object $obj) - -Returns the values of the provided object. - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``{"foo": "baz", "bar": "bam"}`` - - ``values(@)`` - - ``["baz", "bam"]`` - * - ``["a", "b"]`` - - ``values(@)`` - - ```` - * - ``false`` - - ``values(@)`` - - ```` - - -Compliance Tests -================ - -A ``functions.json`` will be added to the compliance test suite. -The test suite will add the following new error types: - -* unknown-function -* invalid-arity -* invalid-type - -The compliance does not specify **when** the errors are raised, as this will -depend on implementation details. For an implementation to be compliant they -need to indicate that an error occurred while attempting to evaluate the -JMESPath expression. - -History -======= - -* This JEP originally proposed the literal syntax. The literal portion of this - JEP was removed and added instead to JEP 7. -* This JEP originally specified that types matches should return null. This - has been updated to specify that an invalid type error should occur instead. diff --git a/docs/proposals/improved-identifiers.rst b/docs/proposals/improved-identifiers.rst deleted file mode 100644 index e863327..0000000 --- a/docs/proposals/improved-identifiers.rst +++ /dev/null @@ -1,234 +0,0 @@ -==================== -Improved Identifiers -==================== - -:JEP: 6 -:Author: James Saryerwinnie -:Status: draft -:Created: 14-Dec-2013 -:Last Updated: 15-Dec-2013 - - -Abstract -======== - -This JEP proposes grammar modifications to JMESPath in order to improve -identifiers used in JMESPath. In doing so, several inconsistencies in the -identifier grammar rules will be fixed, along with an improved grammar for -specifying unicode identifiers in a way that is consistent with JSON -strings. - - -Motivation -========== - -There are two ways to currently specify an identifier, the unquoted rule:: - - identifier = 1*char - -and the quoted rule:: - - identifier =/ quote 1*(unescaped-char / escaped-quote) quote - -The ``char`` rule contains a set of characters that do **not** have to be -quoted:: - - char = %x30-39 / ; 0-9 - %x41-5A / ; A-Z - %x5F / ; _ - %x61-7A / ; a-z - %x7F-10FFFF - -There is an ambiguity between the ``%x30-39`` rule and the ``number`` rule:: - - number = ["-"]1*digit - digit = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "0" - -It's ambiguous which rule to use. Given a string "123", it's not clear whether -this should be parsed as an identifier or a number. Existing implementations -**aren't** following this rule (because it's ambiguous) so the grammar should -be updated to remove the ambiguity, specifically, an unquoted identifier can -only start with the characters ``[a-zA-Z_]``. - -Unicode -------- - -JMESPath supports unicode through the ``char`` and ``unescaped-char`` rule:: - - unescaped-char = %x30-10FFFF - char = %x30-39 / ; 0-9 - %x41-5A / ; A-Z - %x5F / ; _ - %x61-7A / ; a-z - %x7F-10FFFF - -However, JSON supports a syntax for escaping unicode characters. Any -character in the Basic Multilingual Plane (BMP) can be escaped with:: - - char = escape (%x75 4HEXDIG ) ; \uXXXX - -Similar to the way that XPath supports numeric character references used -in XML (``&#nnnn``), JMESPath should support the same escape sequences -used in JSON. JSON also supports a 12 character escape sequence for -characters outside of the BMP, by encoding the UTF-16 surrogate pair. -For example, the code point ``U+1D11E`` can be represented -as ``"\uD834\uDD1E"``. - - -Escape Sequences ----------------- - -Consider the following JSON object:: - - {"foo\nbar": "baz"} - -A JMESPath expression should be able to retrieve the value of baz. With -the current grammar, one must rely on the environment's ability to input -control characters such as the newline (``%x0A``). This can be problematic -in certain environments. For example, in python, this is not a problem:: - - >>> jmespath_expression = "foo\nbar" - -Python will interpret the sequence ``"\n"`` (``%x5C %x6E``) as the newline -character ``%x0A``. However, consider Bash:: - - $ foo --jmespath-expression "foo\nbar" - -In this situation, bash will not interpret the ``"\n"`` (``%x5C %x6E``) sequence. - - -Specification -============= - -The ``char`` rule contains a set of characters that do **not** have to be -quoted. The new set of characters that do not have to quoted will be:: - - unquoted-string = (%x41-5A / %x61-7A / %x5F) *(%x30-39 / %x41-5A / %x5F / %x61-7A) - -In order for an identifier to not be quoted, it must start with ``[A-Za-z_]``, -then must be followed by zero or more ``[0-9A-Za-z_]``. - -The unquoted rule is updated to account for all JSON supported escape -sequences:: - - quoted-string =/ quote 1*(unescaped-char / escaped-char) quote - -The full rule for an identifier is:: - - identifier = unquoted-string / quoted-string - unquoted-string = (%x41-5A / %x61-7A / %x5F) *( ; a-zA-Z_ - %x30-39 / ; 0-9 - %x41-5A / ; A-Z - %x5F / ; _ - %x61-7A) ; a-z - quoted-string = quote 1*(unescaped-char / escaped-char) quote - unescaped-char = %x20-21 / %x23-5B / %x5D-10FFFF - escape = %x5C ; Back slash: \ - quote = %x22 ; Double quote: '"' - escaped-char = escape ( - %x22 / ; " quotation mark U+0022 - %x5C / ; \ reverse solidus U+005C - %x2F / ; / solidus U+002F - %x62 / ; b backspace U+0008 - %x66 / ; f form feed U+000C - %x6E / ; n line feed U+000A - %x72 / ; r carriage return U+000D - %x74 / ; t tab U+0009 - %x75 4HEXDIG ) ; uXXXX U+XXXX - - -Rationale -========= - -Adopting the same string rules as JSON strings will allow users familiar with -JSON semantics to understand how JMESPath identifiers will work. - -This change also provides a nice consistency for the literal syntax proposed -in JEP 3. With this model, the supported literal strings can be the same -as quoted identifiers. - -This also will allow the grammar to grow in a consistent way if JMESPath -adds support for filtering based on literal values. For example (note that -this is just a suggested syntax, not a formal proposal), given the data:: - - {"foo": [{"✓": "✓"}, {"✓": "✗"}]} - -You can now have the following JMESPath expressions:: - - foo[?"✓" = `✓`] - foo[?"\u2713" = `\u2713`] - -As a general property, any supported JSON string is now a supported quoted -identifier. - - -Impact -====== - -For any implementation that was parsing digits as an identifier, identifiers -starting with digits will no longer be valid, e.g. ``foo.0.1.2``. - -There are several compliance tests that will have to be updated as a result -of this JEP. They were arguably wrong to begin with. - -basic.json ----------- - -The following needs to be changed because identifiers starting -with a number must now be quoted:: - - - "expression": "foo.1", - + "expression": "foo.\"1\"", - "result": ["one", "two", "three"] - }, - { - - "expression": "foo.1[0]", - + "expression": "foo.\"1\"[0]", - "result": "one" - }, - -Similarly, the following needs to be changed because an unquoted -identifier cannot start with ``-``:: - - - "expression": "foo.-1", - + "expression": "foo.\"-1\"", - "result": "bar" - } - - -escape.json ------------ - -The escape.json has several more interesting cases that need to be updated. -This has to do with the updated escaping rules. Each one will be explained. - -:: - - - "expression": "\"foo\nbar\"", - + "expression": "\"foo\\nbar\"", - "result": "newline" - }, - - -This has to be updated because a JSON parser will interpret the ``\n`` sequence -as the newline character. The newline character is **not** allowed in a -JMESPath identifier (note that the newline character ``%0A`` is not in any -rule). In order for a JSON parser to create a sequence of ``%x5C %x6E``, the -JSON string must be ``\\n`` (``%x5C %x5C %x6E``). - -:: - - - "expression": "\"c:\\\\windows\\path\"", - + "expression": "\"c:\\\\\\\\windows\\\\path\"", - "result": "windows" - }, - - -The above example is a more pathological case of escaping. In this example, we -have a string that represents a windows path "c:\\windowpath". There are two -levels of escaping happening here, one at the JSON parser, and one at the -JMESPath parser. The JSON parser will take the sequence -``"\"c:\\\\\\\\windows\\\\path\""`` and create the string -``"\"c:\\\\windows\\path\""``. The JMESPath parser will take the string -``"\"c:\\\\windows\\path\"'`` and, applying its own escaping rules, will -look for a key named ``c:\\windows\path``. diff --git a/docs/proposals/nested-expressions.rst b/docs/proposals/nested-expressions.rst deleted file mode 100644 index 97028fb..0000000 --- a/docs/proposals/nested-expressions.rst +++ /dev/null @@ -1,221 +0,0 @@ -================== -Nested Expressions -================== - -:JEP: 1 -:Author: Michael Dowling -:Status: accepted -:Created: 27-Nov-2013 - -Abstract -======== - -This document proposes modifying the `JMESPath grammar `_ -to support arbitrarily nested expressions within ``multi-select-list`` and -``multi-select-hash`` expressions. - -Motivation -========== - -This JMESPath grammar currently does not allow arbitrarily nested expressions -within ``multi-select-list`` and ``multi-select-hash`` expressions. This -prevents nested branching expressions, nested ``multi-select-list`` expressions -within other multi expressions, and nested ``or-expression``s within any -multi-expression. - -By allowing any expression to be nested within a ``multi-select-list`` and -``multi-select-hash`` expression, we can trim down several grammar rules and -provide customers with a much more flexible expression DSL. - -Supporting arbitrarily nested expressions within other expressions requires: - -* Updating the grammar to remove ``non-branched-expr`` -* Updating compliance tests to add various permutations of the grammar to - ensure implementations are compliant. -* Updating the JMESPath documentation to reflect the ability to arbitrarily - nest expressions. - -Nested Expression Examples -========================== - -Nested branch expressions -------------------------- - -Given: - -.. code-block:: json - - { - "foo": { - "baz": [ - { - "bar": "abc" - }, { - "bar": "def" - } - ], - "qux": ["zero"] - } - } - -With: ``foo.[baz[*].bar, qux[0]]`` - -Result: - -.. code-block:: json - - [ - [ - "abc", - "def" - ], - "zero" - ] - -Nested branch expressions with nested mutli-select --------------------------------------------------- - -Given: - -.. code-block:: json - - { - "foo": { - "baz": [ - { - "bar": "a", - "bam": "b", - "boo": "c" - }, { - "bar": "d", - "bam": "e", - "boo": "f" - } - ], - "qux": ["zero"] - } - } - -With: ``foo.[baz[*].[bar, boo], qux[0]]`` - -Result: - -.. code-block:: json - - [ - [ - [ - "a", - "c" - ], - [ - "d", - "f" - ] - ], - "zero" - ] - -Nested or expressions ---------------------- - -Given: - -.. code-block:: json - - { - "foo": { - "baz": [ - { - "bar": "a", - "bam": "b", - "boo": "c" - }, { - "bar": "d", - "bam": "e", - "boo": "f" - } - ], - "qux": ["zero"] - } - } - -With: ``foo.[baz[*].not_there || baz[*].bar, qux[0]]`` - -Result: - -.. code-block:: json - - [ - [ - "a", - "d" - ], - "zero" - ] - -No breaking changes -------------------- - -Because there are no breaking changes from this modification, existing -multi-select expressions will still work unchanged: - -Given: - -.. code-block:: json - - { - "foo": { - "baz": { - "abc": 123, - "bar": 456 - } - } - } - -With: ``foo.[baz, baz.bar]`` - -Result: - -.. code-block:: json - - [ - { - "abc": 123, - "bar": 456 - }, - 456 - ] - -Modified Grammar -================ - -The following modified JMESPath grammar supports arbitrarily nested expressions -and is specified using ABNF, as described in `RFC4234`_ - -:: - - expression = sub-expression / index-expression / or-expression / identifier / "*" - expression =/ multi-select-list / multi-select-hash - sub-expression = expression "." expression - or-expression = expression "||" expression - index-expression = expression bracket-specifier / bracket-specifier - multi-select-list = "[" ( expression *( "," expression ) ) "]" - multi-select-hash = "{" ( keyval-expr *( "," keyval-expr ) ) "}" - keyval-expr = identifier ":" expression - bracket-specifier = "[" (number / "*") "]" - number = [-]1*digit - digit = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "0" - identifier = 1*char - identifier =/ quote 1*(unescaped-char / escaped-quote) quote - escaped-quote = escape quote - unescaped-char = %x30-10FFFF - escape = %x5C ; Back slash: \ - quote = %x22 ; Double quote: '"' - char = %x30-39 / ; 0-9 - %x41-5A / ; A-Z - %x5F / ; _ - %x61-7A / ; a-z - %x7F-10FFFF - -.. _RFC4234: http://tools.ietf.org/html/rfc4234 diff --git a/docs/proposals/pipes.rst b/docs/proposals/pipes.rst deleted file mode 100644 index 965b357..0000000 --- a/docs/proposals/pipes.rst +++ /dev/null @@ -1,200 +0,0 @@ -================ -Pipe Expressions -================ - -:JEP: 4 -:Author: Michael Dowling -:Status: accepted -:Created: 07-Dec-2013 - -Abstract -======== - -This document proposes adding support for piping expressions into subsequent -expressions. - -Motivation -========== - -The current JMESPath grammar allows for projections at various points in an -expression. However, it is not currently possible to operate on the result of -a projection as a list. - -The following example illustrates that it is not possible to operate on the -result of a projection (e.g., take the first match of a projection). - -Given: - -.. code-block:: json - - { - "foo": { - "a": { - "bar": [1, 2, 3] - }, - "b": { - "bar": [4, 5, 6] - } - } - } - -Expression: - -:: - - foo.*.bar[0] - -The result would be element 0 of each ``bar``: - -.. code-block:: json - - [1, 4] - -With the addition of filters, we could pass the result of one expression to -another, operating on the result of a projection (or any expression). - -Expression: - -:: - - foo.*.bar | [0] - -Result: - -.. code-block:: json - - [1, 2, 3] - -Not only does this give us the ability to operate on the result of a -projection, but pipe expressions can also be useful for breaking down a complex -expression into smaller, easier to comprehend, parts. - -Modified Grammar -================ - -The following modified JMESPath grammar supports piped expressions. - -:: - - expression = sub-expression / index-expression / or-expression / identifier / "*" - expression =/ multi-select-list / multi-select-hash / pipe-expression - sub-expression = expression "." expression - pipe-expression = expression "|" expression - or-expression = expression "||" expression - index-expression = expression bracket-specifier / bracket-specifier - multi-select-list = "[" ( expression *( "," expression ) ) "]" - multi-select-hash = "{" ( keyval-expr *( "," keyval-expr ) ) "}" - keyval-expr = identifier ":" expression - bracket-specifier = "[" (number / "*") "]" / "[]" - number = [-]1*digit - digit = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "0" - identifier = 1*char - identifier =/ quote 1*(unescaped-char / escaped-quote) quote - escaped-quote = escape quote - unescaped-char = %x30-10FFFF - escape = %x5C ; Back slash: \ - quote = %x22 ; Double quote: '"' - char = %x30-39 / ; 0-9 - %x41-5A / ; A-Z - %x5F / ; _ - %x61-7A / ; a-z - %x7F-10FFFF - -.. _RFC4234: http://tools.ietf.org/html/rfc4234 - -.. note:: - - ``pipe-expression`` has a higher precedent than the ``or-operator`` - -Compliance Tests -================ - -.. code-block:: json - - [{ - "given": { - "foo": { - "bar": { - "baz": "one" - }, - "other": { - "baz": "two" - }, - "other2": { - "baz": "three" - }, - "other3": { - "notbaz": ["a", "b", "c"] - }, - "other4": { - "notbaz": ["d", "e", "f"] - } - } - }, - "cases": [ - { - "expression": "foo.*.baz | [0]", - "result": "one" - }, - { - "expression": "foo.*.baz | [1]", - "result": "two" - }, - { - "expression": "foo.*.baz | [2]", - "result": "three" - }, - { - "expression": "foo.bar.* | [0]", - "result": "one" - }, - { - "expression": "foo.*.notbaz | [*]", - "result": [["a", "b", "c"], ["d", "e", "f"]] - }, - { - "expression": "foo | bar", - "result": {"baz": "one"} - }, - { - "expression": "foo | bar | baz", - "result": "one" - }, - { - "expression": "foo|bar| baz", - "result": "one" - }, - { - "expression": "not_there | [0]", - "result": null - }, - { - "expression": "not_there | [0]", - "result": null - }, - { - "expression": "[foo.bar, foo.other] | [0]", - "result": {"baz": "one"} - }, - { - "expression": "{\"a\": foo.bar, \"b\": foo.other} | a", - "result": {"baz": "one"} - }, - { - "expression": "{\"a\": foo.bar, \"b\": foo.other} | b", - "result": {"baz": "two"} - }, - { - "expression": "{\"a\": foo.bar, \"b\": foo.other} | *.baz", - "result": ["one", "two"] - }, - { - "expression": "foo.bam || foo.bar | baz", - "result": "one" - }, - { - "expression": "foo | not_there || bar", - "result": {"baz": "one"} - } - ] - }] diff --git a/docs/specification.rst b/docs/specification.rst deleted file mode 100644 index 854fe54..0000000 --- a/docs/specification.rst +++ /dev/null @@ -1,1615 +0,0 @@ -====================== -JMESPath Specification -====================== - -.. warning:: - - This page is deprecated and will be removed in the near future. - Go to http://jmespath.org/specification.html for the latest - JMESPath specification. - - -This document describes the specification for jmespath. -In the specification, examples are shown through the use -of a ``search`` function. The syntax for this function is:: - - search(, ) -> - -For simplicity, the jmespath expression and the JSON document are -not quoted. For example:: - - search(foo, {"foo": "bar"}) -> "bar" - -In this specification, ``null`` is used as a return value whenever an -expression does not match. ``null`` is the generic term that maps to the JSON -``null`` value. Implementations can replace the ``null`` value with the -language equivalent value. - - -Grammar -======= - -The grammar is specified using ABNF, as described in `RFC4234`_ - -:: - - expression = sub-expression / index-expression / or-expression / identifier - expression =/ "*" / multi-select-list / multi-select-hash / literal - expression =/ function-expression / pipe-expression - sub-expression = expression "." ( identifier / - multi-select-list / - multi-select-hash / - function-expression / - "*" ) - or-expression = expression "||" expression - pipe-expression = expression "|" expression - index-expression = expression bracket-specifier / bracket-specifier - multi-select-list = "[" ( expression *( "," expression ) ) "]" - multi-select-hash = "{" ( keyval-expr *( "," keyval-expr ) ) "}" - keyval-expr = identifier ":" expression - bracket-specifier = "[" (number / "*" / slice-expression) "]" / "[]" - bracket-specifier =/ "[?" list-filter-expr "]" - list-filter-expr = expression comparator expression - slice-expression = [number] ":" [number] [ ":" [number] ] - comparator = "<" / "<=" / "==" / ">=" / ">" / "!=" - function-expression = unquoted-string ( - no-args / - one-or-more-args ) - no-args = "(" ")" - one-or-more-args = "(" ( function-arg *( "," function-arg ) ) ")" - function-arg = expression / current-node / expression-type - current-node = "@" - expression-type = "&" expression - - literal = "`" json-value "`" - literal =/ "`" 1*(unescaped-literal / escaped-literal) "`" - unescaped-literal = %x20-21 / ; space ! - %x23-5A / ; # - [ - %x5D-5F / ; ] ^ _ - %x61-7A ; a-z - %x7C-10FFFF ; |}~ ... - escaped-literal = escaped-char / (escape %x60) - number = ["-"]1*digit - digit = %x30-39 - identifier = unquoted-string / quoted-string - unquoted-string = (%x41-5A / %x61-7A / %x5F) *( ; a-zA-Z_ - %x30-39 / ; 0-9 - %x41-5A / ; A-Z - %x5F / ; _ - %x61-7A) ; a-z - quoted-string = quote 1*(unescaped-char / escaped-char) quote - unescaped-char = %x20-21 / %x23-5B / %x5D-10FFFF - escape = %x5C ; Back slash: \ - quote = %x22 ; Double quote: '"' - escaped-char = escape ( - %x22 / ; " quotation mark U+0022 - %x5C / ; \ reverse solidus U+005C - %x2F / ; / solidus U+002F - %x62 / ; b backspace U+0008 - %x66 / ; f form feed U+000C - %x6E / ; n line feed U+000A - %x72 / ; r carriage return U+000D - %x74 / ; t tab U+0009 - %x75 4HEXDIG ) ; uXXXX U+XXXX - - ; The ``json-value`` is any valid JSON value with the one exception that the - ; ``%x60`` character must be escaped. While it's encouraged that implementations - ; use any existing JSON parser for this grammar rule (after handling the escaped - ; literal characters), the grammar rule is shown below for completeness:: - - json-value = false / null / true / json-object / json-array / - json-number / json-quoted-string - false = %x66.61.6c.73.65 ; false - null = %x6e.75.6c.6c ; null - true = %x74.72.75.65 ; true - json-quoted-string = %x22 1*(unescaped-literal / escaped-literal) %x22 - begin-array = ws %x5B ws ; [ left square bracket - begin-object = ws %x7B ws ; { left curly bracket - end-array = ws %x5D ws ; ] right square bracket - end-object = ws %x7D ws ; } right curly bracket - name-separator = ws %x3A ws ; : colon - value-separator = ws %x2C ws ; , comma - ws = *(%x20 / ; Space - %x09 / ; Horizontal tab - %x0A / ; Line feed or New line - %x0D ; Carriage return - ) - json-object = begin-object [ member *( value-separator member ) ] end-object - member = quoted-string name-separator json-value - json-array = begin-array [ json-value *( value-separator json-value ) ] end-array - json-number = [ minus ] int [ frac ] [ exp ] - decimal-point = %x2E ; . - digit1-9 = %x31-39 ; 1-9 - e = %x65 / %x45 ; e E - exp = e [ minus / plus ] 1*DIGIT - frac = decimal-point 1*DIGIT - int = zero / ( digit1-9 *DIGIT ) - minus = %x2D ; - - plus = %x2B ; + - zero = %x30 ; 0 - - -Identifiers -=========== - - -:: - - identifier = unquoted-string / quoted-string - unquoted-string = (%x41-5A / %x61-7A / %x5F) *( ; a-zA-Z_ - %x30-39 / ; 0-9 - %x41-5A / ; A-Z - %x5F / ; _ - %x61-7A) ; a-z - quoted-string = quote 1*(unescaped-char / escaped-char) quote - unescaped-char = %x20-21 / %x23-5B / %x5D-10FFFF - escape = %x5C ; Back slash: \ - quote = %x22 ; Double quote: '"' - escaped-char = escape ( - %x22 / ; " quotation mark U+0022 - %x5C / ; \ reverse solidus U+005C - %x2F / ; / solidus U+002F - %x62 / ; b backspace U+0008 - %x66 / ; f form feed U+000C - %x6E / ; n line feed U+000A - %x72 / ; r carriage return U+000D - %x74 / ; t tab U+0009 - %x75 4HEXDIG ) ; uXXXX U+XXXX - -An ``identifier`` is the most basic expression and can be used to extract a single -element from a JSON document. The return value for an ``identifier`` is the -value associated with the identifier. If the ``identifier`` does not exist in -the JSON document, than a ``null`` value is returned. - -From the grammar rule listed above identifiers can be one of more characters, -and must start with ``A-Za-z_``. - -An identifier can also be quoted. This is necessary when an identifier has -characters not specified in the ``unquoted-string`` grammar rule. -In this situation, an identifier is specified with a double quote, followed by -any number of ``unescaped-char`` or ``escaped-char`` characters, followed by a -double quote. The ``quoted-string`` rule is the same grammar rule as a JSON -string, so any valid string can be used between double quoted, include JSON -supported escape sequences, and six character unicode escape sequences. - -Note that any identifier that does not start with ``A-Za-z_`` **must** -be quoted. - - -Examples --------- - -:: - - search(foo, {"foo": "value"}) -> "value" - search(bar, {"foo": "value"}) -> null - search(foo, {"foo": [0, 1, 2]}) -> [0, 1, 2] - search("with space", {"with space": "value"}) -> "value" - search("special chars: !@#", {"special chars: !@#": "value"}) -> "value" - search("quote\"char", {"quote\"char": "value"}) -> "value" - search("\u2713", {"\u2713": "value"}) -> "value" - - -SubExpressions -============== - -:: - - sub-expression = expression "." ( identifier / - multi-select-list / - multi-select-hash / - function-expression / - "*" ) - -A subexpression is a combination of two expressions separated by the '.' char. -A subexpression is evaluated as follows: - -* Evaluate the expression on the left with the original JSON document. -* Evaluate the expression on the right with the result of the left expression - evaluation. - -In pseudocode:: - - left-evaluation = search(left-expression, original-json-document) - result = search(right-expression, left-evaluation) - - -A subexpression is itself an expression, so there can be multiple levels of -subexpressions: ``grandparent.parent.child``. - - -Examples --------- - -Given a JSON document: ``{"foo": {"bar": "baz"}}``, and a jmespath expression: -``foo.bar``, the evaluation process would be:: - - left-evaluation = search("foo", {"foo": {"bar": "baz"}}) -> {"bar": "baz"} - result = search("bar": {"bar": "baz"}) -> "baz" - -The final result in this example is ``"baz"``. - -Additional examples:: - - search(foo.bar, {"foo": {"bar": "value"}}) -> "value" - search(foo.bar, {"foo": {"baz": "value"}}) -> null - search(foo.bar.baz, {"foo": {"bar": {"baz": "value"}}}) -> "value" - - -Index Expressions -================= - -:: - - index-expression = expression bracket-specifier / bracket-specifier - bracket-specifier = "[" (number / "*" / slice-expression) "]" / "[]" - slice-expression = [number] ":" [number] [ ":" [number] ] - -An index expression is used to access elements in a list. Indexing is 0 based, -the index of 0 refers to the first element of the list. A negative number is a -valid index. A negative number indicates that indexing is relative to the end -of the list, specifically:: - - negative-index == (length of array) + negative-index - -Given an array of length ``N``, an index of ``-1`` would be equal to a positive -index of ``N - 1``, which is the last element of the list. If an index -expression refers to an index that is greater than the length of the array, a -value of ``null`` is returned. - -For the grammar rule ``expression bracket-specifier`` the ``expression`` is -first evaluated, and then return value from the ``expression`` is given as -input to the ``bracket-specifier``. - -Using a "*" character within a ``bracket-specifier`` is discussed below in the -``wildcard expressions`` section. - -Slices ------- - -:: - - slice-expression = [number] ":" [number] [ ":" [number] ] - -A slice expression allows you to select a contiguous subset of an array. A -slice has a ``start``, ``stop``, and ``step`` value. The general form of a -slice is ``[start:stop:step]``, but each component is optional and can -be omitted. - -.. note:: - - Slices in JMESPath have the same semantics as python slices. - -Given a ``start``, ``stop``, and ``step`` value, the sub elements in an array -are extracted as follows: - -* The first element in the extracted array is the index denoted by ``start``. -* The last element in the extracted array is the index denoted by ``end - 1``. -* The ``step`` value determines how many indices to skip after each element - is selected from the array. An array of 1 (the default step) will not skip - any indices. A step value of 2 will skip every other index while extracting - elements from an array. A step value of -1 will extract values in reverse - order from the array. - - -Slice expressions adhere to the following rules: - -* If a negative start position is given, it is calculated as the total length - of the array plus the given start position. -* If no start position is given, it is assumed to be 0 if the given step is - greater than 0 or the end of the array if the given step is less than 0. -* If a negative stop position is given, it is calculated as the total length - of the array plus the given stop position. -* If no stop position is given, it is assumed to be the length of the array if - the given step is greater than 0 or 0 if the given step is less than 0. -* If the given step is omitted, it it assumed to be 1. -* If the given step is 0, an error MUST be raised. -* If the element being sliced is not an array, the result is ``null``. -* If the element being sliced is an array and yields no results, the result - MUST be an empty array. - - -Examples --------- - -:: - - search([0:4:1], [0, 1, 2, 3]) -> [0, 1, 2, 3] - search([0:4], [0, 1, 2, 3]) -> [0, 1, 2, 3] - search([0:3], [0, 1, 2, 3]) -> [0, 1, 2] - search([:2], [0, 1, 2, 3]) -> [0, 1] - search([::2], [0, 1, 2, 3]) -> [0, 2] - search([::-1], [0, 1, 2, 3]) -> [3, 2, 1, 0] - search([-2:], [0, 1, 2, 3]) -> [2, 3] - - -Flatten Operator ----------------- - -When the character sequence ``[]`` is provided as a bracket specifier, then -a flattening operation occurs on the current result. The flattening operator -will merge sublists in the current result into a single list. The flattening -operator has the following semantics: - -* Create an empty result list. -* Iterate over the elements of the current result. -* If the current element is not a list, add to the end of the result list. -* If the current element is a list, add each element of the current element - to the end of the result list. -* The result list is now the new current result. - -Once the flattening operation has been performed, subsequent operations -are projected onto the flattened list with the same semantics as a -wildcard expression. Thus the difference between ``[*]`` and ``[]`` is that -``[]`` will first flatten sublists in the current result. - - -Examples --------- - -:: - - search([0], ["first", "second", "third"]) -> "first" - search([-1], ["first", "second", "third"]) -> "third" - search([100], ["first", "second", "third"]) -> null - search(foo[0], {"foo": ["first", "second", "third"]) -> "first" - search(foo[100], {"foo": ["first", "second", "third"]) -> null - search(foo[0][0], {"foo": [[0, 1], [1, 2]]}) -> 0 - - -Or Expressions -============== - -:: - - or-expression = expression "||" expression - -An or expression will evaluate to either the left expression or the right -expression. If the evaluation of the left expression is not false it is used as -the return value. If the evaluation of the right expression is not false it is -used as the return value. If neither the left or right expression are -non-null, then a value of null is returned. A false value corresponds to any -of the following conditions:: - -* Empty list: ``[]`` -* Empty object: ``{}`` -* Empty string: ``""`` -* False boolean: ``false`` -* Null value: ``null`` - -A true value corresponds to any value that is not false. - - -Examples --------- - -:: - - search(foo || bar, {"foo": "foo-value"}) -> "foo-value" - search(foo || bar, {"bar": "bar-value"}) -> "bar-value" - search(foo || bar, {"foo": "foo-value", "bar": "bar-value"}) -> "foo-value" - search(foo || bar, {"baz": "baz-value"}) -> null - search(foo || bar || baz, {"baz": "baz-value"}) -> "baz-value" - search(override || mylist[-1], {"mylist": ["one", "two"]}) -> "two" - search(override || mylist[-1], {"mylist": ["one", "two"], "override": "yes"}) -> "yes" - - -MultiSelect List -================ - -:: - - multi-select-list = "[" ( expression *( "," expression ) "]" - -A multiselect expression is used to extract a subset of elements from a JSON -hash. There are two version of multiselect, one in which the multiselect -expression is enclosed in ``{...}`` and one which is enclosed in ``[...]``. -This section describes the ``[...]`` version. Within the start and closing -characters is one or more non expressions separated by a comma. Each -expression will be evaluated against the JSON document. Each returned element -will be the result of evaluating the expression. A ``multi-select-list`` with -``N`` expressions will result in a list of length ``N``. Given a multiselect -expression ``[expr-1,expr-2,...,expr-n]``, the evaluated expression will return -``[evaluate(expr-1), evaluate(expr-2), ..., evaluate(expr-n)]``. - -Examples --------- - -:: - - search([foo,bar], {"foo": "a", "bar": "b", "baz": "c"}) -> ["a", "b"] - search([foo,bar[0]], {"foo": "a", "bar": ["b"], "baz": "c"}) -> ["a", "b"] - search([foo,bar.baz], {"foo": "a", "bar": {"baz": "b"}}) -> ["a", "b"] - search([foo,baz], {"foo": "a", "bar": "b"}) -> ["a", null] - - -MultiSelect Hash -================ - -:: - - multi-select-hash = "{" ( keyval-expr *( "," keyval-expr ) "}" - keyval-expr = identifier ":" expression - -A ``multi-select-hash`` expression is similar to a ``multi-select-list`` -expression, except that a hash is created instead of a list. A -``multi-select-hash`` expression also requires key names to be provided, as -specified in the ``keyval-expr`` rule. Given the following rule:: - - keyval-expr = identifier ":" expression - -The ``identifier`` is used as the key name and the result of evaluating the -``expression`` is the value associated with the ``identifier`` key. - -Each ``keyval-expr`` within the ``multi-select-hash`` will correspond to a -single key value pair in the created hash. - - -Examples --------- - -Given a ``multi-select-hash`` expression ``{foo: one.two, bar: bar}`` and the -data ``{"bar": "bar", {"one": {"two": "one-two"}}}``, the expression is -evaluated as follows: - -1. A hash is created: ``{}`` -2. A key ``foo`` is created whose value is the result of evaluating ``one.two`` - against the provided JSON document: ``{"foo": evaluate(one.two, )}`` -3. A key ``bar`` is created whose value is the result of evaluating the - expression ``bar`` against the provided JSON document. - -The final result will be: ``{"foo": "one-two", "bar": "bar"}``. - -Additional examples: - -:: - - search({foo: foo, bar: bar}, {"foo": "a", "bar": "b", "baz": "c"}) - -> {"foo": "a", "bar": "b"} - search({foo: foo, firstbar: bar[0]}, {"foo": "a", "bar": ["b"]}) - -> {"foo": "a", "firstbar": "b"} - search({foo: foo, "bar.baz": bar.baz}, {"foo": "a", "bar": {"baz": "b"}}) - -> {"foo": "a", "bar.baz": "b"} - search({foo: foo, baz: baz}, {"foo": "a", "bar": "b"}) - -> {"foo": "a", "bar": null} - - -Wildcard Expressions -==================== - -:: - - expression =/ "*" - bracket-specifier = "[" "*" "]" - -A wildcard expression is a expression of either ``*`` or ``[*]``. A wildcard -expression can return multiple elements, and the remaining expressions are -evaluated against each returned element from a wildcard expression. The -``[*]`` syntax applies to a list type and the ``*`` syntax applies to a hash -type. - -The ``[*]`` syntax (referred to as a list wildcard expression) will return all -the elements in a list. Any subsequent expressions will be evaluated against -each individual element. Given an expression ``[*].child-expr``, and a list of -N elements, the evaluation of this expression would be ``[child-expr(el-0), -child-expr(el-2), ..., child-expr(el-N)]``. This is referred to as a -**projection**, and the ``child-expr`` expression is projected onto the -elements of the resulting list. - -Once a projection has been created, all subsequent expressions are projected -onto the resulting list. - -The ``*`` syntax (referred to as a hash wildcard expression) will return a list -of the hash element's values. Any subsequent expression will be evaluated -against each individual element in the list (this is also referred to as a -**projection**). - -Note that if any subsequent expression after a wildcard expression returns a -``null`` value, it is omitted from the final result list. - -A list wildcard expression is only valid for the JSON array type. If a list -wildcard expression is applied to any other JSON type, a value of ``null`` is -returned. - -Similarly, a hash wildcard expression is only valid for the JSON object type. -If a hash wildcard expression is applied to any other JSON type, a value of -``null`` is returned. - -Examples --------- - -:: - - search([*].foo, [{"foo": 1}, {"foo": 2}, {"foo": 3}]) -> [1, 2, 3] - search([*].foo, [{"foo": 1}, {"foo": 2}, {"bar": 3}]) -> [1, 2] - search('*.foo', {"a": {"foo": 1}, "b": {"foo": 2}, "c": {"bar": 1}}) -> [1, 2] - - -Literal Expressions -=================== - -:: - - literal = "`" json-value "`" - literal =/ "`" 1*(unescaped-literal / escaped-literal) "`" - unescaped-literal = %x20-21 / ; space ! - %x23-5A / ; # - [ - %x5D-5F / ; ] ^ _ - %x61-7A ; a-z - %x7C-10FFFF ; |}~ ... - escaped-literal = escaped-char / (escape %x60) - -A literal expression is an expression that allows arbitrary JSON objects to be -specified. This is useful in filter expressions as well as multi select hashes -(to create arbitrary key value pairs), but is allowed anywhere an expression is -allowed. The specification includes the ABNF for JSON, implementations should -use an existing JSON parser to parse literal values. Note that the ``\``` -character must now be escaped in a ``json-value`` which means implementations -need to handle this case before passing the resulting string to a JSON parser. - -Note the second literal rule. This is used to specify a string such that -double quotes do not have to be included. This means that the literal -expression ``\`"foo"\``` is equivalent to ``\`foo\```. - - -Examples --------- - -:: - - search(`foo`, "anything") -> "foo" - search(`"foo"`, "anything") -> "foo" - search(`[1, 2]`, "anything") -> [1, 2] - search(`true`, "anything") -> true - search(`{"a": "b"}`.a, "anything") -> "b" - search({first: a, type: `mytype`}, {"a": "b", "c": "d"}) -> {"first": "b", "type": "mytype"} - - -Filter Expressions -================== - -:: - - list-filter-expr = expression comparator expression - comparator = "<" / "<=" / "==" / ">=" / ">" / "!=" - -A filter expression provides a way to select JSON elements based on a -comparison to another expression. A filter expression is evaluated as follows: -for each element in an array evaluate the ``list-filter-expr`` against the -element. If the expression evaluates to ``true``, the item (in its entirety) is -added to the result list. Otherwise it is excluded from the result list. A -filter expression is only defined for a JSON array. Attempting to evaluate a -filter expression against any other type will return ``null``. - -Comparison Operators --------------------- - -The following operations are supported: - -* ``==``, tests for equality. -* ``!=``, tests for inequality. -* ``<``, less than. -* ``<=``, less than or equal to. -* ``>``, greater than. -* ``>=``, greater than or equal to. - -The behavior of each operation is dependent on the type of each evaluated -expression. - -The comparison semantics for each operator are defined below based on -the corresponding JSON type: - -Equality Operators -~~~~~~~~~~~~~~~~~~ - -For ``string/number/true/false/null`` types, equality is an exact match. A -``string`` is equal to another ``string`` if they they have the exact sequence -of code points. The literal values ``true/false/null`` are only equal to their -own literal values. Two JSON objects are equal if they have the same set of -keys and values (given two JSON objects ``x`` and ``y``, for each key value -pair ``(i, j)`` in ``x``, there exists an equivalent pair ``(i, j)`` in ``y``). -Two JSON arrays are equal if they have equal elements in the same order (given -two arrays ``x`` and ``y``, for each ``i`` from ``0`` until ``length(x)``, -``x[i] == y[i]``). - -Ordering Operators -~~~~~~~~~~~~~~~~~~ - -Ordering operators ``>, >=, <, <=`` are **only** valid for numbers. -Evaluating any other type with a comparison operator will yield a ``null`` -value, which will result in the element being excluded from the result list. -For example, given:: - - search('foo[?a [{"bar": 10}] - search([?bar==`10`], [{"bar": 1}, {"bar": 10}]}) -> [{"bar": 10}] - search(foo[?a==b], {"foo": [{"a": 1, "b": 2}, {"a": 2, "b": 2}]}) -> [{"a": 2, "b": 2}] - - -.. _RFC4234: http://tools.ietf.org/html/rfc4234 - - -Functions Expressions -===================== - -:: - - function-expression = unquoted-string ( - no-args / - one-or-more-args ) - no-args = "(" ")" - one-or-more-args = "(" ( function-arg *( "," function-arg ) ) ")" - function-arg = expression / current-node / expression-type - current-node = "@" - expression-type = "&" expression - - -Functions allow users to easily transform and filter data in JMESPath -expressions. - -Data Types ----------- - -In order to support functions, a type system is needed. The JSON types are used: - -* number (integers and double-precision floating-point format in JSON) -* string -* boolean (``true`` or ``false``) -* array (an ordered, sequence of values) -* object (an unordered collection of key value pairs) -* null - -There is also an additional type that is not a JSON type that's used in -JMESPath functions: - -* expression (denoted by ``&expression``) - -current-node ------------- - -The ``current-node`` token can be used to represent the current node being -evaluated. The ``current-node`` token is useful for functions that require the -current node being evaluated as an argument. For example, the following -expression creates an array containing the total number of elements in the -``foo`` object followed by the value of ``foo["bar"]``. - -:: - - foo[].[count(@), bar] - -JMESPath assumes that all function arguments operate on the current node unless -the argument is a ``literal`` or ``number`` token. Because of this, an -expression such as ``@.bar`` would be equivalent to just ``bar``, so the -current node is only allowed as a bare expression. - - -current-node state -~~~~~~~~~~~~~~~~~~ - -At the start of an expression, the value of the current node is the data -being evaluated by the JMESPath expression. As an expression is evaluated, the -value the the current node represents MUST change to reflect the node currently -being evaluated. When in a projection, the current node value must be changed -to the node currently being evaluated by the projection. - -Function Evaluation -------------------- - -Functions are evaluated in applicative order. Each argument must be an -expression, each argument expression must be evaluated before evaluating the -function. The function is then called with the evaluated function arguments. -The result of the ``function-expression`` is the result returned by the -function call. If a ``function-expression`` is evaluated for a function that -does not exist, the JMESPath implementation must indicate to the caller that an -``unknown-function`` error occurred. How and when this error is raised is -implementation specific, but implementations should indicate to the caller that -this specific error occurred. - -Functions can either have a specific arity or be variadic with a minimum -number of arguments. If a ``function-expression`` is encountered where the -arity does not match or the minimum number of arguments for a variadic function -is not provided, then implementations must indicate to the caller than an -``invalid-arity`` error occurred. How and when this error is raised is -implementation specific. - -Each function signature declares the types of its input parameters. If any -type constraints are not met, implementations must indicate that an -``invalid-type`` error occurred. - -In order to accommodate type constraints, functions are provided to convert -types to other types (``to_string``, ``to_number``) which are defined below. -No explicit type conversion happens unless a user specifically uses one of -these type conversion functions. - -Function expressions are also allowed as the child element of a sub expression. -This allows functions to be used with projections, which can enable functions -to be applied to every element in a projection. For example, given the input -data of ``["1", "2", "3", "notanumber", true]``, the following expression can -be used to convert (and filter) all elements to numbers:: - - search([].to_number(@), ``["1", "2", "3", "notanumber", true]``) -> [1, 2, 3] - -This provides a simple mechanism to explicitly convert types when needed. - -Built-in Functions -================== - -JMESPath has various built-in functions that operate on different -data types, documented below. Each function below has a signature -that defines the expected types of the input and the type of the returned -output:: - - return_type function_name(type $argname) - return_type function_name2(type1|type2 $argname) - -If a function can accept multiple types for an input value, then the -multiple types are separated with ``|``. If the resolved arguments do not -match the types specified in the signature, an ``invalid-type`` error occurs. - -The ``array`` type can further specify requirements on the type of the elements -if they want to enforce homogeneous types. The subtype is surrounded by -``[type]``, for example, the function signature below requires its input -argument resolves to an array of numbers:: - - return_type foo(array[number] $argname) - -As a shorthand, the type ``any`` is used to indicate that the argument can be -of any type (``array|object|number|string|boolean|null``). - -Similarly how arrays can specify a type within a list using the -``array[type]`` syntax, expressions can specify their resolved type using -``expression->type`` syntax. This means that the resolved type of the function -argument must be an expression that itself will resolve to ``type``. - -The first function below, ``abs`` is discussed in detail to demonstrate the -above points. Subsequent function definitions will not include these details -for brevity, but the same rules apply. - -.. note:: - - All string related functions are defined on the basis of Unicode code - points; they do not take normalization into account. - - -abs ---- - -:: - - number abs(number $value) - -Returns the absolute value of the provided argument. The signature indicates -that a number is returned, and that the input argument ``$value`` **must** -resolve to a number, otherwise a ``invalid-type`` error is triggered. - -Below is a worked example. Given:: - - {"foo": -1, "bar": "2"} - -Evaluating ``abs(foo)`` works as follows: - -1. Evaluate the input argument against the current data:: - - search(foo, {"foo": -1, "bar": 2"}) -> -1 - -2. Validate the type of the resolved argument. In this case - ``-1`` is of type ``number`` so it passes the type check. - -3. Call the function with the resolved argument:: - - abs(-1) -> 1 - -4. The value of ``1`` is the resolved value of the function expression - ``abs(foo)``. - - -Below is the same steps for evaluating ``abs(bar)``: - -1. Evaluate the input argument against the current data:: - - search(bar, {"foo": -1, "bar": 2"}) -> "2" - -2. Validate the type of the resolved argument. In this case - ``"2"`` is of type ``string`` so we immediately indicate that - an ``invalid-type`` error occurred. - - -As a final example, here is the steps for evaluating ``abs(to_number(bar))``: - -1. Evaluate the input argument against the current data:: - - search(to_number(bar), {"foo": -1, "bar": "2"}) - -2. In order to evaluate the above expression, we need to evaluate - ``to_number(bar)``:: - - search(bar, {"foo": -1, "bar": "2"}) -> "2" - # Validate "2" passes the type check for to_number, which it does. - to_number("2") -> 2 - - Note that `to_number`_ is defined below. - -3. Now we can evaluate the original expression:: - - search(to_number(bar), {"foo": -1, "bar": "2"}) -> 2 - -4. Call the function with the final resolved value:: - - abs(2) -> 2 - -5. The value of ``2`` is the resolved value of the function expression - ``abs(to_number(bar))``. - - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``abs(1)`` - - 1 - * - ``abs(-1)`` - - 1 - * - ``abs(`abc`)`` - - ```` - - -avg ---- - -:: - - number avg(array[number] $elements) - -Returns the average of the elements in the provided array. - -An empty array will produce a return value of null. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``[10, 15, 20]`` - - ``avg(@)`` - - 15 - * - ``[10, false, 20]`` - - ``avg(@)`` - - ```` - * - ``[false]`` - - ``avg(@)`` - - ```` - * - ``false`` - - ``avg(@)`` - - ```` - - -contains --------- - -:: - - boolean contains(array|string $subject, any $search) - -Returns ``true`` if the given ``$subject`` contains the provided ``$search`` -string. - -If ``$subject`` is an array, this function returns true if one of the elements -in the array is equal to the provided ``$search`` value. - -If the provided ``$subject`` is a string, this function returns true if -the string contains the provided ``$search`` argument. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - n/a - - ``contains(`foobar`, `foo`)`` - - ``true`` - * - n/a - - ``contains(`foobar`, `not`)`` - - ``false`` - * - n/a - - ``contains(`foobar`, `bar`)`` - - ``true`` - * - n/a - - ``contains(`false`, `bar`)`` - - ```` - * - n/a - - ``contains(`foobar`, 123)`` - - ``false`` - * - ``["a", "b"]`` - - ``contains(@, `a`)`` - - ``true`` - * - ``["a"]`` - - ``contains(@, `a`)`` - - ``true`` - * - ``["a"]`` - - ``contains(@, `b`)`` - - ``false`` - -ceil ----- - -:: - - number ceil(number $value) - -Returns the next highest integer value by rounding up if necessary. - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``ceil(`1.001`)`` - - 2 - * - ``ceil(`1.9`)`` - - 2 - * - ``ceil(`1`)`` - - 1 - * - ``ceil(`abc`)`` - - ``null`` - - -ends_with ---------- - -:: - - boolean ends_with(string $subject, string $prefix) - -Returns ``true`` if the ``$subject`` ends with the ``$prefix``, otherwise this -function returns ``false``. - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``foobarbaz`` - - ``ends_with(@, ``baz``)`` - - ``true`` - * - ``foobarbaz`` - - ``ends_with(@, ``foo``)`` - - ``false`` - * - ``foobarbaz`` - - ``ends_with(@, ``z``)`` - - ``true`` - - -floor ------ - -:: - - number floor(number $value) - -Returns the next lowest integer value by rounding down if necessary. - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``floor(`1.001`)`` - - 1 - * - ``floor(`1.9`)`` - - 1 - * - ``floor(`1`)`` - - 1 - - -join ----- - -:: - - string join(string $glue, array[string] $stringsarray) - -Returns all of the elements from the provided ``$stringsarray`` array joined -together using the ``$glue`` argument as a separator between each. - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``["a", "b"]`` - - ``join(`, `, @)`` - - "a, b" - * - ``["a", "b"]`` - - ``join(````, @)`` - - "ab" - * - ``["a", false, "b"]`` - - ``join(`, `, @)`` - - ```` - * - ``[false]`` - - ``join(`, `, @)`` - - ```` - - -keys ----- - -:: - - array keys(object $obj) - -Returns an array containing the keys of the provided object. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``{"foo": "baz", "bar": "bam"}`` - - ``keys(@)`` - - ``["foo", "bar"]`` - * - ``{}`` - - ``keys(@)`` - - ``[]`` - * - ``false`` - - ``keys(@)`` - - ```` - * - ``[b, a, c]`` - - ``keys(@)`` - - ```` - - -length ------- - -:: - - number length(string|array|object $subject) - -Returns the length of the given argument using the following types rules: - -1. string: returns the number of code points in the string -2. array: returns the number of elements in the array -3. object: returns the number of key-value pairs in the object - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - n/a - - ``length(`abc`)`` - - 3 - * - "current" - - ``length(@)`` - - 7 - * - "current" - - ``length(not_there)`` - - ```` - * - ``["a", "b", "c"]`` - - ``length(@)`` - - 3 - * - ``[]`` - - ``length(@)`` - - 0 - * - ``{}`` - - ``length(@)`` - - 0 - * - ``{"foo": "bar", "baz": "bam"}`` - - ``length(@)`` - - 2 - - -max ---- - -:: - - number max(array[number]|array[string] $collection) - -Returns the highest found number in the provided array argument. - -An empty array will produce a return value of null. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``[10, 15]`` - - ``max(@)`` - - 15 - * - ``["a", "b"]`` - - ``max(@)`` - - "b" - * - ``["a", 2, "b"]`` - - ``max(@)`` - - ```` - * - ``[10, false, 20]`` - - ``max(@)`` - - ```` - - -max_by ------- - -:: - - max_by(array elements, expression->number|expression->string expr) - -Return the maximum element in an array using the expression ``expr`` as the -comparison key. The entire maximum element is returned. -Below are several examples using the ``people`` array (defined above) as the -given input. - - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``max_by(people, &age)`` - - ``{"age": 50, "age_str": "50", "bool": false, "name": "d"}`` - * - ``max_by(people, &age).age`` - - 50 - * - ``max_by(people, &to_number(age_str))`` - - ``{"age": 50, "age_str": "50", "bool": false, "name": "d"}`` - * - ``max_by(people, &age_str)`` - - - * - ``max_by(people, age)`` - - - - -min ---- - -:: - - number min(array[number]|array[string] $collection) - -Returns the lowest found number in the provided ``$collection`` argument. - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``[10, 15]`` - - ``min(@)`` - - 10 - * - ``["a", "b"]`` - - ``min(@)`` - - "a" - * - ``["a", 2, "b"]`` - - ``min(@)`` - - ```` - * - ``[10, false, 20]`` - - ``min(@)`` - - ```` - - -min_by ------- - -:: - - min_by(array elements, expression->number|expression->string expr) - -Return the minimum element in an array using the expression ``expr`` as the -comparison key. The entire maximum element is returned. -Below are several examples using the ``people`` array (defined above) as the -given input. - - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``min_by(people, &age)`` - - ``{"age": 10, "age_str": "10", "bool": true, "name": 3}`` - * - ``min_by(people, &age).age`` - - 10 - * - ``min_by(people, &to_number(age_str))`` - - ``{"age": 10, "age_str": "10", "bool": true, "name": 3}`` - * - ``min_by(people, &age_str)`` - - ```` - * - ``min_by(people, age)`` - - ```` - - -.. _not_null: - -not_null --------- - -:: - - any not_null(any $argument [, any $...]) - -Returns the first argument that does not resolve to ``null``. This function -accepts one or more arguments, and will evaluate them in order until a -non null argument is encountered. If all arguments values resolve to ``null``, -then a value of ``null`` is returned. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``{"a": null, "b": null, "c": [], "d": "foo"}`` - - ``not_null(no_exist, a, b, c, d)`` - - [] - * - ``{"a": null, "b": null, "c": [], "d": "foo"}`` - - ``not_null(a, b, `null`, d, c)`` - - ``"foo"`` - * - ``{"a": null, "b": null, "c": [], "d": "foo"}`` - - ``not_null(a, b)`` - - ``null`` - - -reverse -------- - -:: - - array reverse(string|array $argument) - -Reverses the order of the ``$argument``. - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``[0, 1, 2, 3, 4]`` - - ``reverse(@)`` - - ``[4, 3, 2, 1, 0]`` - * - ``[] - - ``reverse(@)`` - - ``[]`` - * - ``["a", "b", "c", 1, 2, 3]`` - - ``reverse(@)`` - - ``[3, 2, 1, "c", "b", "a"]`` - * - ``"abcd`` - - ``reverse(@)`` - - ``dcba`` - - -sort ----- - -:: - - array sort(array[number]|array[string] $list) - -This function accepts an array ``$list`` argument and returns the sorted -elements of the ``$list`` as an array. - -The array must be a list of strings or numbers. Sorting strings is based on -code points. Locale is not taken into account. - - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``[b, a, c]`` - - ``sort(@)`` - - ``[a, b, c]`` - * - ``[1, a, c]`` - - ``sort(@)`` - - ``[1, a, c]`` - * - ``[false, [], null]`` - - ``sort(@)`` - - ``[[], null, false]`` - * - ``[[], {}, false]`` - - ``sort(@)`` - - ``[{}, [], false]`` - * - ``{"a": 1, "b": 2}`` - - ``sort(@)`` - - ``null`` - * - ``false`` - - ``sort(@)`` - - ``null`` - - -sort_by -------- - -:: - - sort_by(array elements, expression->number|expression->string expr) - -Sort an array using an expression ``expr`` as the sort key. -Below are several examples using the ``people`` array (defined above) as the -given input. ``sort_by`` follows the same sorting logic as the ``sort`` -function. - - -.. list-table:: Examples - :header-rows: 1 - - * - Expression - - Result - * - ``sort_by(people, &age)[].age`` - - ``[10, 20, 30, 40, 50]`` - * - ``sort_by(people, &age)[0]`` - - ``{"age": 10, "age_str": "10", "bool": true, "name": 3}`` - * - ``sort_by(people, &to_number(age_str))[0]`` - - ``{"age": 10, "age_str": "10", "bool": true, "name": 3}`` - - -starts_with ------------ - -:: - - boolean starts_with(string $subject, string $prefix) - -Returns ``true`` if the ``$subject`` starts with the ``$prefix``, otherwise -this function returns ``false``. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``foobarbaz`` - - ``starts_with(@, ``foo``)`` - - ``true`` - * - ``foobarbaz`` - - ``starts_with(@, ``baz``)`` - - ``false`` - * - ``foobarbaz`` - - ``starts_with(@, ``f``)`` - - ``true`` - - -sum ---- - -:: - - number sum(array[number] $collection) - -Returns the sum of the provided array argument. - -An empty array will produce a return value of 0. - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``[10, 15]`` - - ``sum(@)`` - - 25 - * - ``[10, false, 20]`` - - ``max(@)`` - - ```` - * - ``[10, false, 20]`` - - ``sum([].to_number(@))`` - - 30 - * - ``[]`` - - ``sum(@)`` - - 0 - - -to_string ---------- - -:: - - string to_string(any $arg) - -* string - Returns the passed in value. -* number/array/object/boolean - The JSON encoded value of the object. The - JSON encoder should emit the encoded JSON value without adding any additional - new lines. - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``null`` - - ``to_string(`2`)`` - - ``"2"`` - - -to_number ---------- - -:: - - number to_number(any $arg) - -* string - Returns the parsed number. Any string that conforms to the - ``json-number`` production is supported. Note that the floating number - support will be implementation specific, but implementations should support - at least IEEE 754-2008 binary64 (double precision) numbers, as this is - generally available and widely used. -* number - Returns the passed in value. -* array - null -* object - null -* boolean - null -* null - null - - -type ----- - -:: - - string type(array|object|string|number|boolean|null $subject) - -Returns the JavaScript type of the given ``$subject`` argument as a string -value. - -The return value MUST be one of the following: - -* number -* string -* boolean -* array -* object -* null - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - "foo" - - ``type(@)`` - - "string" - * - ``true`` - - ``type(@)`` - - "boolean" - * - ``false`` - - ``type(@)`` - - "boolean" - * - ``null`` - - ``type(@)`` - - "null" - * - 123 - - ``type(@)`` - - number - * - 123.05 - - ``type(@)`` - - number - * - ``["abc"]`` - - ``type(@)`` - - "array" - * - ``{"abc": "123"}`` - - ``type(@)`` - - "object" - - -values ------- - -:: - - array values(object $obj) - -Returns the values of the provided object. - - -.. list-table:: Examples - :header-rows: 1 - - * - Given - - Expression - - Result - * - ``{"foo": "baz", "bar": "bam"}`` - - ``values(@)`` - - ``["baz", "bam"]`` - * - ``["a", "b"]`` - - ``values(@)`` - - ```` - * - ``false`` - - ``values(@)`` - - ```` - - -Pipe Expressions -================ - -:: - - pipe-expression = expression "|" expression - -A pipe expression combines two expressions, separated by the ``|`` character. -It is similar to a ``sub-expression`` with two important distinctions: - -1. Any expression can be used on the right hand side. A ``sub-expression`` - restricts the type of expression that can be used on the right hand side. -2. A ``pipe-expression`` **stops projections on the left hand side for - propagating to the right hand side**. If the left expression creates a - projection, it does **not** apply to the right hand side. - -For example, given the following data:: - - {"foo": [{"bar": ["first1", "second1"]}, {"bar": ["first2", "second2"]}]} - -The expression ``foo[*].bar`` gives the result of:: - - [ - [ - "first1", - "second1" - ], - [ - "first2", - "second2" - ] - ] - -The first part of the expression, ``foo[*]``, creates a projection. At this -point, the remaining expression, ``bar`` is projected onto each element of the -list created from ``foo[*]``. If you project the ``[0]`` expression, you will -get the first element from each sub list. The expression ``foo[*].bar[0]`` -will return:: - - ["first1", "first2"] - -If you instead wanted *only* the first sub list, ``["first1", "second1"]``, you -can use a ``pipe-expression``:: - - foo[*].bar[0] -> ["first1", "first2"] - foo[*].bar | [0] -> ["first1", "second1"] - - -Examples --------- - -:: - - search(foo | bar, {"foo": {"bar": "baz"}}) -> "baz" - search(foo[*].bar | [0], { - "foo": [{"bar": ["first1", "second1"]}, - {"bar": ["first2", "second2"]}]}) -> ["first1", "second1"] - search(foo | [0], {"foo": [0, 1, 2]}) -> [0] diff --git a/extra/test_hypothesis.py b/extra/test_hypothesis.py deleted file mode 100644 index 9721153..0000000 --- a/extra/test_hypothesis.py +++ /dev/null @@ -1,135 +0,0 @@ -# Test suite using hypothesis to generate test cases. -# This is in a standalone module so that these tests -# can a) be run separately and b) allow for customization -# via env var for longer runs in travis. -import os -import numbers - -from hypothesis import given, settings, assume, HealthCheck -import hypothesis.strategies as st - -from jmespath import lexer -from jmespath import parser -from jmespath import exceptions -from jmespath.functions import Functions - - -JSON_NUMBERS = (st.integers() | st.floats(allow_nan=False, - allow_infinity=False)) - -RANDOM_JSON = st.recursive( - JSON_NUMBERS | st.booleans() | st.text() | st.none(), - lambda children: st.lists(children) | st.dictionaries(st.text(), children) -) - -MAX_EXAMPLES = int(os.environ.get('JP_MAX_EXAMPLES', 1000)) -BASE_SETTINGS = { - 'max_examples': MAX_EXAMPLES, - 'suppress_health_check': [HealthCheck.too_slow, - HealthCheck.filter_too_much, - HealthCheck.data_too_large], -} - - -# For all of these tests they verify these properties: -# either the operation succeeds or it raises a JMESPathError. -# If any other exception is raised then we error out. -@settings(**BASE_SETTINGS) -@given(st.text()) -def test_lexer_api(expr): - try: - tokens = list(lexer.Lexer().tokenize(expr)) - except exceptions.EmptyExpressionError: - return - except exceptions.LexerError as e: - assert e.lex_position >= 0, e.lex_position - assert e.lex_position < len(expr), e.lex_position - if expr: - assert expr[e.lex_position] == e.token_value[0], ( - "Lex position does not match first token char.\n" - "Expression: %s\n%s != %s" % (expr, expr[e.lex_position], - e.token_value[0]) - ) - return - except Exception as e: - raise AssertionError("Non JMESPathError raised: %s" % e) - assert isinstance(tokens, list) - # Token starting positions must be unique, can't have two - # tokens with the same start position. - start_locations = [t['start'] for t in tokens] - assert len(set(start_locations)) == len(start_locations), ( - "Tokens must have unique starting locations.") - # Starting positions must be increasing (i.e sorted). - assert sorted(start_locations) == start_locations, ( - "Tokens must have increasing start locations.") - # Last token is always EOF. - assert tokens[-1]['type'] == 'eof' - - -@settings(**BASE_SETTINGS) -@given(st.text()) -def test_parser_api_from_str(expr): - # Same a lexer above with the assumption that we're parsing - # a valid sequence of tokens. - try: - list(lexer.Lexer().tokenize(expr)) - except exceptions.JMESPathError as e: - # We want to try to parse things that tokenize - # properly. - assume(False) - try: - ast = parser.Parser().parse(expr) - except exceptions.JMESPathError as e: - return - except Exception as e: - raise AssertionError("Non JMESPathError raised: %s" % e) - assert isinstance(ast.parsed, dict) - assert 'type' in ast.parsed - assert 'children' in ast.parsed - assert isinstance(ast.parsed['children'], list) - - -@settings(**BASE_SETTINGS) -@given(expr=st.text(), data=RANDOM_JSON) -def test_search_api(expr, data): - try: - ast = parser.Parser().parse(expr) - except exceptions.JMESPathError as e: - # We want to try to parse things that tokenize - # properly. - assume(False) - try: - ast.search(data) - except exceptions.JMESPathError as e: - return - except Exception as e: - raise AssertionError("Non JMESPathError raised: %s" % e) - - -# Additional property tests for functions. - -@given(arg=JSON_NUMBERS) -def test_abs(arg): - assert Functions().call_function('abs', [arg]) >= 0 - - -@given(arg=st.lists(JSON_NUMBERS)) -def test_avg(arg): - result = Functions().call_function('avg', [arg]) - if result is not None: - assert isinstance(result, numbers.Number) - - -@given(arg=st.lists(st.floats() | st.booleans() | st.text() | st.none(), - min_size=1)) -def test_not_null(arg): - result = Functions().call_function('not_null', arg) - if result is not None: - assert result in arg - - -@given(arg=RANDOM_JSON) -def test_to_number(arg): - result = Functions().call_function('to_number', [arg]) - if result is not None: - assert isinstance(result, numbers.Number) diff --git a/jmespath.test b/jmespath.test deleted file mode 160000 index edd3251..0000000 --- a/jmespath.test +++ /dev/null @@ -1 +0,0 @@ -Subproject commit edd3251d123a6cbde52af7763d32cde39014da69 diff --git a/jmespath.test/.gitattributes b/jmespath.test/.gitattributes new file mode 100644 index 0000000..74be31c --- /dev/null +++ b/jmespath.test/.gitattributes @@ -0,0 +1,51 @@ + # .gitattributes snippet to force users to use same line endings for project. + # + # Handle line endings automatically for files detected as text + # and leave all files detected as binary untouched. + * text=auto + + # + # The above will handle all files NOT found below + # https://help.github.com/articles/dealing-with-line-endings/ + # https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes + + # These files are text and should be normalized (Convert crlf => lf) + *.css text + *.htm text + *.html text + *.inc text + *.ini text + *.js text + *.json text + *.php text + *.pl text + *.py text + *.rb text + *.scm text + *.sh text + *.sql text + *.txt text + *.xml text + *.yaml text + *.yml text + + .htaccess text + + # These files are binary and should be left untouched + # (binary is a macro for -text -diff) + *.7z binary + *.fla binary + *.flv binary + *.gif binary + *.gz binary + *.ico binary + *.jpeg binary + *.jpg binary + *.mov binary + *.mp3 binary + *.mp4 binary + *.png binary + *.pyc binary + *.swf binary + *.ttf binary + *.zip binary \ No newline at end of file diff --git a/jmespath.test/.travis.yml b/jmespath.test/.travis.yml new file mode 100644 index 0000000..533fc00 --- /dev/null +++ b/jmespath.test/.travis.yml @@ -0,0 +1,5 @@ +language: python +python: + - "3.5" +install: "pip install jsonschema==2.5.1" +script: ls tests/*.json | xargs -I {} jsonschema schema.json -i {} diff --git a/jmespath.test/README.md b/jmespath.test/README.md new file mode 100644 index 0000000..bc0e7c4 --- /dev/null +++ b/jmespath.test/README.md @@ -0,0 +1,140 @@ +# JMESPath Compliance Tests + +This repo contains a suite of JMESPath compliance tests. JMESPath +Community implementations can use these tests in order to verify their +implementation adheres to the JMESPath spec. + +## Compatibility + +JMESPath Community is designed to be fully backwards compatible with [JMESPath.org](https://jmespath.org). + +However, in some rare circumstances, some differences may be observed. This paragraph lists the known differences: + +|Category|Compliance|Result|JMESPath.org Result|Description +|---|---|---|---|--- +|[literal.json](https://github.com/jmespath/jmespath.test/blob/53abcc37901891cf4308fcd910eab287416c4609/tests/literal.json#L193-L197)|`` '\\' ``| `` "\" `` | `` "\\" `` | JMESPath Community `raw-string` supports escaping both `` ' `` (single quote) and `` \ `` (backslash) characters, whereas JMESPath.org can only escape single quotes +|[pipe.json](https://github.com/jmespath-community/jmespath.test/blob/304b287a9537673227c2e300a34ff8e4757579c5/tests/pipe.json#L131-L136)| `` `null`\|[@] ``| `` [null] `` | `` null `` | JMESPath Community lets a `null` left-hand side of a `pipe-expression` propagate to its right-hand side, whereas JMESPath.og shortcuts and does not evaluate the right-hand side if the left-hand side result is `null`. + +## Test Organization + +The `test/` directory contains JSON files containing the JMESPath +testcase. Each JSON file represents a JMESPath feature. Each JSON file +is a JSON list containing one or more tests suites: + + [ + , + , + ] + +Each test suite is a JSON object that has the following keys: + +- `given` - The input data from which the JMESPath expression is + evaluated. +- `cases` - A list of test cases. +- `comment` - An optional field containing a description of the test + suite. + +Each JMESPath test case can have the following keys: + +- `expression` - The JMESPath expression being tested. +- `result` - The expected result from evaluating the JMESPath + expression against the `given` input. +- `error` - The type of error that should be raised as a result of + evaluating the JMESPath expression. The valid values for an error + are: + - `syntax` - Syntax error from an invalid JMESPath expression. + - `invalid-arity` - Wrong number of arguments passed to a + function. + - `invalid-type` - Invalid argument type for a function. + - `invalid-value` - Semantically incorrect value (used in slice + tests) + - `unknown-function` - Attempting to invoke an unknown function. + - `not-a-number` - While evaluating arithmetic expressions. +- `bench` - If the case is a benchmark, `bench` contains the type of + benchmark. Available `bench` types are as follows: + - `parse` - Benchmark only the parsing of an expression. + - `interpret` - Benchmark only the interpreting of an expression. + - `full` - Benchmark both parsing and interpreting an expression. +- `comment` - An optional comment containing a description of the + specific test case. + +For each test case, either `result`, `error`, or `bench` must be +specified. Only one of these keys can be present in a single test case. + +The error type (if the `error` key is present) indicates the type of +error that an implementation should raise, but it does not indicate +**when** this error should be raised. For example, a value of +`"error": "syntax"` does not require that the syntax error be raised +when the expression is compiled. If an implementation does not have a +separate compilation step this won\'t even be possible. Similar for type +errors, implementations are free to check for type errors during +compilation or at run time (when the parsed expression is evaluated). As +long as an implementation can detect that this error occurred at any +point during the evaluation of a JMESPath expression, this is considered +sufficient. + +Below are a few examples: + + [{ + "given": + {"foo": {"bar": {"baz": "correct"}}}, + "cases": [ + { + "expression": "foo", + "result": {"bar": {"baz": "correct"}} + }, + { + "expression": "foo.1", + "error": "syntax" + }, + ] + }] + +This above JSON document specifies 1 test suite that contains 2 test +cases. The two test cases are: + +- Given the input `{"foo": {"bar": {"baz": "correct"}}}`, the + expression `foo` should have a result of + `{"bar": {"baz": "correct"}}`. +- Given the input `{"foo": {"bar": {"baz": "correct"}}}`, the + expression `foo.1` should generate a syntax error. + +# Utility Tools + +Most languages have test frameworks that are capable of reading the JSON +test descriptions and generating testcases. However, a `jp-compliance` +tool is provided to help with any implementation that does not have an +available test framework to generate test cases. The `jp-compliance` +tool takes the name of a jmespath executable and will evaluate all the +compliance tests using this provided executable. This way all that\'s +needed to verify your JMESPath implementation is for you to write a +basic executable. This executable must have the following interface: + +- Accept the input JSON data on stdin. +- Accept the jmespath expression as an argument. +- Print the jmespath result as JSON on stdout. +- If an error occurred, it must write the error name to sys.stderr. + This check is case insensitive. The error types in the compliance + tests are hyphenated, but each individual component may appear in + stderr (again case insensitive). + +Here are a few examples of error messages that would pass +`jp-compliance`: + +- Error type: `unknown-function` +- Valid error messages: + - `unknown-function: somefunction()` + - `error: unknown function 'somefunction()` + - `Unknown function: somefunction()` +- Error type: `syntax` +- Valid error messages: + - `syntax: Unknown token '$'` + - `syntax-error: Unknown token '$'` + - `Syntax error: Unknown token '$'` + - `An error occurred: Syntax error, unknown token '$'` + +> This will be substantially slower than using a test framework. Using +> `jp-compliance` each test case is evaluated by executing a new process. + +You can run the `bin/jp-compliance --help` for more information and for +examples on how to use this tool. \ No newline at end of file diff --git a/jmespath.test/schema.json b/jmespath.test/schema.json new file mode 100644 index 0000000..0a58797 --- /dev/null +++ b/jmespath.test/schema.json @@ -0,0 +1,38 @@ +{ + "type": "array", + "items": { + "type": "object", + "additionalProperties": "false", + "properties": { + "given": {}, + "comment": {"type": "string"}, + "cases": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "expression": {"type": "string"}, + "result": {}, + "error": { + "enum": ["invalid-arity", "invalid-type", + "invalid-value", "syntax", + "unknown-function"] + }, + "bench": { + "enum": ["parse", "interpret", "full"] + }, + "comment": {"type": "string"} + }, + "required": ["expression"], + "anyOf": [ + {"required": ["result"]}, + {"required": ["error"]}, + {"required": ["bench"]} + ] + } + } + }, + "required": ["given", "cases"] + } +} diff --git a/jmespath.test/tests/arithmetic.json b/jmespath.test/tests/arithmetic.json new file mode 100644 index 0000000..077992a --- /dev/null +++ b/jmespath.test/tests/arithmetic.json @@ -0,0 +1,62 @@ +[ + { + "given": { + "a": { + "b": 1 + }, + "c": { + "d": 2 + } + }, + "cases": [ + { + "expression": "`1` + `2`", + "result": 3.0 + }, + { + "expression": "`1` - `2`", + "result": -1.0 + }, + { + "expression": "`2` * `4`", + "result": 8.0 + }, + { + "expression": "`2` × `4`", + "result": 8.0 + }, + { + "expression": "`2` / `3`", + "result": 0.6666666666666666 + }, + { + "expression": "`2` ÷ `3`", + "result": 0.6666666666666666 + }, + { + "expression": "`10` % `3`", + "result": 1.0 + }, + { + "expression": "`10` // `3`", + "result": 3.0 + }, + { + "expression": "-`1` - + `2`", + "result": -3.0 + }, + { + "expression": "{ ab: a.b, cd: c.d } | ab + cd", + "result": 3.0 + }, + { + "expression": "{ ab: a.b, cd: c.d } | ab + cd × cd", + "result": 5.0 + }, + { + "expression": "{ ab: a.b, cd: c.d } | (ab + cd) × cd", + "result": 6.0 + } + ] + } +] \ No newline at end of file diff --git a/jmespath.test/tests/basic.json b/jmespath.test/tests/basic.json new file mode 100644 index 0000000..051a06a --- /dev/null +++ b/jmespath.test/tests/basic.json @@ -0,0 +1,106 @@ +[{ + "given": + {"foo": {"bar": {"baz": "correct"}}}, + "cases": [ + { + "expression": "foo", + "result": {"bar": {"baz": "correct"}} + }, + { + "expression": "foo.bar", + "result": {"baz": "correct"} + }, + { + "expression": "foo.bar.baz", + "result": "correct" + }, + { + "expression": "foo\n.\nbar\n.baz", + "result": "correct" + }, + { + "expression": "foo.bar.baz.bad", + "result": null + }, + { + "expression": "foo.bar.bad", + "result": null + }, + { + "expression": "foo.bad", + "result": null + }, + { + "expression": "bad", + "result": null + }, + { + "expression": "bad.morebad.morebad", + "result": null + } + ] +}, +{ + "given": + {"foo": {"bar": ["one", "two", "three"]}}, + "cases": [ + { + "expression": "foo", + "result": {"bar": ["one", "two", "three"]} + }, + { + "expression": "foo.bar", + "result": ["one", "two", "three"] + } + ] +}, +{ + "given": ["one", "two", "three"], + "cases": [ + { + "expression": "one", + "result": null + }, + { + "expression": "two", + "result": null + }, + { + "expression": "three", + "result": null + }, + { + "expression": "one.two", + "result": null + } + ] +}, +{ + "given": + {"foo": {"1": ["one", "two", "three"], "-1": "bar"}}, + "cases": [ + { + "expression": "foo.\"1\"", + "result": ["one", "two", "three"] + }, + { + "expression": "foo.\"1\"[0]", + "result": "one" + }, + { + "expression": "foo.\"-1\"", + "result": "bar" + } + ] +}, +{ + "given": + {"foo": {"bar": 0}}, + "cases": [ + { + "expression": "foo.bar", + "result": 0 + } + ] +} +] diff --git a/jmespath.test/tests/benchmarks.json b/jmespath.test/tests/benchmarks.json new file mode 100644 index 0000000..703055d --- /dev/null +++ b/jmespath.test/tests/benchmarks.json @@ -0,0 +1,148 @@ +[ + { + "given": { + "long_name_for_a_field": true, + "a": { + "b": { + "c": { + "d": { + "e": { + "f": { + "g": { + "h": { + "i": { + "j": { + "k": { + "l": { + "m": { + "n": { + "o": { + "p": true + } + } + } + } + } + } + } + } + } + } + } + } + } + } + }, + "b": true, + "c": { + "d": true + } + }, + "cases": [ + { + "comment": "simple field", + "expression": "b", + "result": true, + "bench": "full" + }, + { + "comment": "simple subexpression", + "expression": "c.d", + "result": true, + "bench": "full" + }, + { + "comment": "deep field selection no match", + "expression": "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s", + "result": null, + "bench": "full" + }, + { + "comment": "deep field selection", + "expression": "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p", + "result": true, + "bench": "full" + }, + { + "comment": "simple or", + "expression": "not_there || b", + "result": true, + "bench": "full" + } + ] + }, + { + "given": { + "a":0,"b":1,"c":2,"d":3,"e":4,"f":5,"g":6,"h":7,"i":8,"j":9,"k":10, + "l":11,"m":12,"n":13,"o":14,"p":15,"q":16,"r":17,"s":18,"t":19,"u":20, + "v":21,"w":22,"x":23,"y":24,"z":25 + }, + "cases": [ + { + "comment": "deep ands", + "expression": "a && b && c && d && e && f && g && h && i && j && k && l && m && n && o && p && q && r && s && t && u && v && w && x && y && z", + "result": 25, + "bench": "full" + }, + { + "comment": "deep ors", + "expression": "z || y || x || w || v || u || t || s || r || q || p || o || n || m || l || k || j || i || h || g || f || e || d || c || b || a", + "result": 25, + "bench": "full" + }, + { + "comment": "lots of summing", + "expression": "sum([z, y, x, w, v, u, t, s, r, q, p, o, n, m, l, k, j, i, h, g, f, e, d, c, b, a])", + "result": 325, + "bench": "full" + }, + { + "comment": "lots of function application", + "expression": "sum([z, sum([y, sum([x, sum([w, sum([v, sum([u, sum([t, sum([s, sum([r, sum([q, sum([p, sum([o, sum([n, sum([m, sum([l, sum([k, sum([j, sum([i, sum([h, sum([g, sum([f, sum([e, sum([d, sum([c, sum([b, a])])])])])])])])])])])])])])])])])])])])])])])])])", + "result": 325, + "bench": "full" + }, + { + "comment": "lots of multi list", + "expression": "[z, y, x, w, v, u, t, s, r, q, p, o, n, m, l, k, j, i, h, g, f, e, d, c, b, a]", + "result": [25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], + "bench": "full" + } + ] + }, + { + "given": {}, + "cases": [ + { + "comment": "field 50", + "expression": "j49.j48.j47.j46.j45.j44.j43.j42.j41.j40.j39.j38.j37.j36.j35.j34.j33.j32.j31.j30.j29.j28.j27.j26.j25.j24.j23.j22.j21.j20.j19.j18.j17.j16.j15.j14.j13.j12.j11.j10.j9.j8.j7.j6.j5.j4.j3.j2.j1.j0", + "bench": "parse" + }, + { + "comment": "pipe 50", + "expression": "j49|j48|j47|j46|j45|j44|j43|j42|j41|j40|j39|j38|j37|j36|j35|j34|j33|j32|j31|j30|j29|j28|j27|j26|j25|j24|j23|j22|j21|j20|j19|j18|j17|j16|j15|j14|j13|j12|j11|j10|j9|j8|j7|j6|j5|j4|j3|j2|j1|j0", + "bench": "parse" + }, + { + "comment": "index 50", + "expression": "[49][48][47][46][45][44][43][42][41][40][39][38][37][36][35][34][33][32][31][30][29][28][27][26][25][24][23][22][21][20][19][18][17][16][15][14][13][12][11][10][9][8][7][6][5][4][3][2][1][0]", + "bench": "parse" + }, + { + "comment": "long raw string literal", + "expression": "'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz'", + "bench": "parse" + }, + { + "comment": "deep projection 104", + "expression": "a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*]", + "bench": "parse" + }, + { + "comment": "filter projection", + "expression": "foo[?bar > baz][?qux > baz]", + "bench": "parse" + } + ] + } +] diff --git a/jmespath.test/tests/boolean.json b/jmespath.test/tests/boolean.json new file mode 100644 index 0000000..dd7ee58 --- /dev/null +++ b/jmespath.test/tests/boolean.json @@ -0,0 +1,288 @@ +[ + { + "given": { + "outer": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + } + }, + "cases": [ + { + "expression": "outer.foo || outer.bar", + "result": "foo" + }, + { + "expression": "outer.foo||outer.bar", + "result": "foo" + }, + { + "expression": "outer.bar || outer.baz", + "result": "bar" + }, + { + "expression": "outer.bar||outer.baz", + "result": "bar" + }, + { + "expression": "outer.bad || outer.foo", + "result": "foo" + }, + { + "expression": "outer.bad||outer.foo", + "result": "foo" + }, + { + "expression": "outer.foo || outer.bad", + "result": "foo" + }, + { + "expression": "outer.foo||outer.bad", + "result": "foo" + }, + { + "expression": "outer.bad || outer.alsobad", + "result": null + }, + { + "expression": "outer.bad||outer.alsobad", + "result": null + } + ] + }, + { + "given": { + "outer": { + "foo": "foo", + "bool": false, + "empty_list": [], + "empty_string": "" + } + }, + "cases": [ + { + "expression": "outer.empty_string || outer.foo", + "result": "foo" + }, + { + "expression": "outer.nokey || outer.bool || outer.empty_list || outer.empty_string || outer.foo", + "result": "foo" + } + ] + }, + { + "given": { + "True": true, + "False": false, + "Number": 5, + "EmptyList": [], + "Zero": 0, + "ZeroFloat": 0.0 + }, + "cases": [ + { + "expression": "True && False", + "result": false + }, + { + "expression": "False && True", + "result": false + }, + { + "expression": "True && True", + "result": true + }, + { + "expression": "False && False", + "result": false + }, + { + "expression": "True && Number", + "result": 5 + }, + { + "expression": "Number && True", + "result": true + }, + { + "expression": "Number && False", + "result": false + }, + { + "expression": "Number && EmptyList", + "result": [] + }, + { + "expression": "Number && True", + "result": true + }, + { + "expression": "EmptyList && True", + "result": [] + }, + { + "expression": "EmptyList && False", + "result": [] + }, + { + "expression": "True || False", + "result": true + }, + { + "expression": "True || True", + "result": true + }, + { + "expression": "False || True", + "result": true + }, + { + "expression": "False || False", + "result": false + }, + { + "expression": "Number || EmptyList", + "result": 5 + }, + { + "expression": "Number || True", + "result": 5 + }, + { + "expression": "Number || True && False", + "result": 5 + }, + { + "expression": "(Number || True) && False", + "result": false + }, + { + "expression": "Number || (True && False)", + "result": 5 + }, + { + "expression": "!True", + "result": false + }, + { + "expression": "!False", + "result": true + }, + { + "expression": "!Number", + "result": false + }, + { + "expression": "!EmptyList", + "result": true + }, + { + "expression": "True && !False", + "result": true + }, + { + "expression": "True && !EmptyList", + "result": true + }, + { + "expression": "!False && !EmptyList", + "result": true + }, + { + "expression": "!True && False", + "result": false + }, + { + "expression": "!(True && False)", + "result": true + }, + { + "expression": "!Zero", + "result": false + }, + { + "expression": "!!Zero", + "result": true + }, + { + "expression": "Zero || Number", + "result": 0 + }, + { + "expression": "ZeroFloat || Number", + "result": 0.0 + } + ] + }, + { + "given": { + "one": 1, + "two": 2, + "three": 3, + "emptylist": [], + "boolvalue": false + }, + "cases": [ + { + "expression": "one < two", + "result": true + }, + { + "expression": "one <= two", + "result": true + }, + { + "expression": "one == one", + "result": true + }, + { + "expression": "one == two", + "result": false + }, + { + "expression": "one > two", + "result": false + }, + { + "expression": "one >= two", + "result": false + }, + { + "expression": "one != two", + "result": true + }, + { + "expression": "emptylist < one", + "result": null + }, + { + "expression": "emptylist < nullvalue", + "result": null + }, + { + "expression": "emptylist < boolvalue", + "result": null + }, + { + "expression": "one < boolvalue", + "result": null + }, + { + "expression": "one < two && three > one", + "result": true + }, + { + "expression": "one < two || three > one", + "result": true + }, + { + "expression": "one < two || three < one", + "result": true + }, + { + "expression": "two < one || three < one", + "result": false + } + ] + } +] diff --git a/jmespath.test/tests/current.json b/jmespath.test/tests/current.json new file mode 100644 index 0000000..0c26248 --- /dev/null +++ b/jmespath.test/tests/current.json @@ -0,0 +1,25 @@ +[ + { + "given": { + "foo": [{"name": "a"}, {"name": "b"}], + "bar": {"baz": "qux"} + }, + "cases": [ + { + "expression": "@", + "result": { + "foo": [{"name": "a"}, {"name": "b"}], + "bar": {"baz": "qux"} + } + }, + { + "expression": "@.bar", + "result": {"baz": "qux"} + }, + { + "expression": "@.foo[0]", + "result": {"name": "a"} + } + ] + } +] diff --git a/jmespath.test/tests/escape.json b/jmespath.test/tests/escape.json new file mode 100644 index 0000000..4a62d95 --- /dev/null +++ b/jmespath.test/tests/escape.json @@ -0,0 +1,46 @@ +[{ + "given": { + "foo.bar": "dot", + "foo bar": "space", + "foo\nbar": "newline", + "foo\"bar": "doublequote", + "c:\\\\windows\\path": "windows", + "/unix/path": "unix", + "\"\"\"": "threequotes", + "bar": {"baz": "qux"} + }, + "cases": [ + { + "expression": "\"foo.bar\"", + "result": "dot" + }, + { + "expression": "\"foo bar\"", + "result": "space" + }, + { + "expression": "\"foo\\nbar\"", + "result": "newline" + }, + { + "expression": "\"foo\\\"bar\"", + "result": "doublequote" + }, + { + "expression": "\"c:\\\\\\\\windows\\\\path\"", + "result": "windows" + }, + { + "expression": "\"/unix/path\"", + "result": "unix" + }, + { + "expression": "\"\\\"\\\"\\\"\"", + "result": "threequotes" + }, + { + "expression": "\"bar\".\"baz\"", + "result": "qux" + } + ] +}] diff --git a/jmespath.test/tests/filters.json b/jmespath.test/tests/filters.json new file mode 100644 index 0000000..41c20ae --- /dev/null +++ b/jmespath.test/tests/filters.json @@ -0,0 +1,594 @@ +[ + { + "given": {"foo": [{"name": "a"}, {"name": "b"}]}, + "cases": [ + { + "comment": "Matching a literal", + "expression": "foo[?name == 'a']", + "result": [{"name": "a"}] + } + ] + }, + { + "given": {"foo": [0, 1], "bar": [2, 3]}, + "cases": [ + { + "comment": "Matching a literal", + "expression": "*[?[0] == `0`]", + "result": [[], []] + } + ] + }, + { + "given": {"foo": [{"first": "foo", "last": "bar"}, + {"first": "foo", "last": "foo"}, + {"first": "foo", "last": "baz"}]}, + "cases": [ + { + "comment": "Matching an expression", + "expression": "foo[?first == last]", + "result": [{"first": "foo", "last": "foo"}] + }, + { + "comment": "Verify projection created from filter", + "expression": "foo[?first == last].first", + "result": ["foo"] + } + ] + }, + { + "given": {"foo": [{"age": 20}, + {"age": 25}, + {"age": 30}]}, + "cases": [ + { + "comment": "Greater than with a number", + "expression": "foo[?age > `25`]", + "result": [{"age": 30}] + }, + { + "expression": "foo[?age >= `25`]", + "result": [{"age": 25}, {"age": 30}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?age > `30`]", + "result": [] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?age < `25`]", + "result": [{"age": 20}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?age <= `25`]", + "result": [{"age": 20}, {"age": 25}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?age < `20`]", + "result": [] + }, + { + "expression": "foo[?age == `20`]", + "result": [{"age": 20}] + }, + { + "expression": "foo[?age != `20`]", + "result": [{"age": 25}, {"age": 30}] + } + ] + }, + { + "given": {"foo": [{"weight": 33.3}, + {"weight": 44.4}, + {"weight": 55.5}]}, + "cases": [ + { + "comment": "Greater than with a number", + "expression": "foo[?weight > `44.4`]", + "result": [{"weight": 55.5}] + }, + { + "expression": "foo[?weight >= `44.4`]", + "result": [{"weight": 44.4}, {"weight": 55.5}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?weight > `55.5`]", + "result": [] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?weight < `44.4`]", + "result": [{"weight": 33.3}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?weight <= `44.4`]", + "result": [{"weight": 33.3}, {"weight": 44.4}] + }, + { + "comment": "Greater than with a number", + "expression": "foo[?weight < `33.3`]", + "result": [] + }, + { + "expression": "foo[?weight == `33.3`]", + "result": [{"weight": 33.3}] + }, + { + "expression": "foo[?weight != `33.3`]", + "result": [{"weight": 44.4}, {"weight": 55.5}] + } + ] + }, + { + "given": {"foo": [{"top": {"name": "a"}}, + {"top": {"name": "b"}}]}, + "cases": [ + { + "comment": "Filter with subexpression", + "expression": "foo[?top.name == 'a']", + "result": [{"top": {"name": "a"}}] + } + ] + }, + { + "given": {"foo": [{"top": {"first": "foo", "last": "bar"}}, + {"top": {"first": "foo", "last": "foo"}}, + {"top": {"first": "foo", "last": "baz"}}]}, + "cases": [ + { + "comment": "Matching an expression", + "expression": "foo[?top.first == top.last]", + "result": [{"top": {"first": "foo", "last": "foo"}}] + }, + { + "comment": "Matching a JSON array", + "expression": "foo[?top == `{\"first\": \"foo\", \"last\": \"bar\"}`]", + "result": [{"top": {"first": "foo", "last": "bar"}}] + } + ] + }, + { + "given": {"foo": [ + {"key": true}, + {"key": false}, + {"key": 0}, + {"key": 1}, + {"key": [0]}, + {"key": {"bar": [0]}}, + {"key": null}, + {"key": [1]}, + {"key": {"a":2}} + ]}, + "cases": [ + { + "expression": "foo[?key == `true`]", + "result": [{"key": true}] + }, + { + "expression": "foo[?key == `false`]", + "result": [{"key": false}] + }, + { + "expression": "foo[?key == `0`]", + "result": [{"key": 0}] + }, + { + "expression": "foo[?key == `1`]", + "result": [{"key": 1}] + }, + { + "expression": "foo[?key == `[0]`]", + "result": [{"key": [0]}] + }, + { + "expression": "foo[?key == `{\"bar\": [0]}`]", + "result": [{"key": {"bar": [0]}}] + }, + { + "expression": "foo[?key == `null`]", + "result": [{"key": null}] + }, + { + "expression": "foo[?key == `[1]`]", + "result": [{"key": [1]}] + }, + { + "expression": "foo[?key == `{\"a\":2}`]", + "result": [{"key": {"a":2}}] + }, + { + "expression": "foo[?`true` == key]", + "result": [{"key": true}] + }, + { + "expression": "foo[?`false` == key]", + "result": [{"key": false}] + }, + { + "expression": "foo[?`0` == key]", + "result": [{"key": 0}] + }, + { + "expression": "foo[?`1` == key]", + "result": [{"key": 1}] + }, + { + "expression": "foo[?`[0]` == key]", + "result": [{"key": [0]}] + }, + { + "expression": "foo[?`{\"bar\": [0]}` == key]", + "result": [{"key": {"bar": [0]}}] + }, + { + "expression": "foo[?`null` == key]", + "result": [{"key": null}] + }, + { + "expression": "foo[?`[1]` == key]", + "result": [{"key": [1]}] + }, + { + "expression": "foo[?`{\"a\":2}` == key]", + "result": [{"key": {"a":2}}] + }, + { + "expression": "foo[?key != `true`]", + "result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `false`]", + "result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `0`]", + "result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `1`]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `null`]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `[1]`]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}] + }, + { + "expression": "foo[?key != `{\"a\":2}`]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}] + }, + { + "expression": "foo[?`true` != key]", + "result": [{"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`false` != key]", + "result": [{"key": true}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`0` != key]", + "result": [{"key": true}, {"key": false}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`1` != key]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`null` != key]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": [1]}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`[1]` != key]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": {"a":2}}] + }, + { + "expression": "foo[?`{\"a\":2}` != key]", + "result": [{"key": true}, {"key": false}, {"key": 0}, {"key": 1}, {"key": [0]}, + {"key": {"bar": [0]}}, {"key": null}, {"key": [1]}] + } + ] + }, + { + "given": {"foo": [ + {"key": true}, + {"key": false}, + {"key": 0}, + {"key": 0.0}, + {"key": 1}, + {"key": 1.0}, + {"key": [0]}, + {"key": null}, + {"key": [1]}, + {"key": []}, + {"key": {}}, + {"key": {"a":2}} + ]}, + "cases": [ + { + "expression": "foo[?key == `true`]", + "result": [{"key": true}] + }, + { + "expression": "foo[?key == `false`]", + "result": [{"key": false}] + }, + { + "expression": "foo[?key]", + "result": [ + {"key": true}, + {"key": 0}, + {"key": 0.0}, + {"key": 1}, + {"key": 1.0}, + {"key": [0]}, + {"key": [1]}, + {"key": {"a": 2}} + ] + }, + { + "expression": "foo[? !key]", + "result": [ + {"key": false}, + {"key": null}, + {"key": []}, + {"key": {}} + ] + }, + { + "expression": "foo[? !!key]", + "result": [ + {"key": true}, + {"key": 0}, + {"key": 0.0}, + {"key": 1}, + {"key": 1.0}, + {"key": [0]}, + {"key": [1]}, + {"key": {"a": 2}} + ] + }, + { + "expression": "foo[? `true`]", + "result": [ + {"key": true}, + {"key": false}, + {"key": 0}, + {"key": 0.0}, + {"key": 1}, + {"key": 1.0}, + {"key": [0]}, + {"key": null}, + {"key": [1]}, + {"key": []}, + {"key": {}}, + {"key": {"a":2}} + ] + }, + { + "expression": "foo[? `false`]", + "result": [] + } + ] + }, + { + "given": {"reservations": [ + {"instances": [ + {"foo": 1, "bar": 2}, {"foo": 1, "bar": 3}, + {"foo": 1, "bar": 2}, {"foo": 2, "bar": 1}]}]}, + "cases": [ + { + "expression": "reservations[].instances[?bar==`1`]", + "result": [[{"foo": 2, "bar": 1}]] + }, + { + "expression": "reservations[*].instances[?bar==`1`]", + "result": [[{"foo": 2, "bar": 1}]] + }, + { + "expression": "reservations[].instances[?bar==`1`][]", + "result": [{"foo": 2, "bar": 1}] + } + ] + }, + { + "given": { + "baz": "other", + "foo": [ + {"bar": 1}, {"bar": 2}, {"bar": 3}, {"bar": 4}, {"bar": 1, "baz": 2} + ] + }, + "cases": [ + { + "expression": "foo[?bar==`1`].bar[0]", + "result": [] + } + ] + }, + { + "given": { + "foo": [ + {"a": 1, "b": {"c": "x"}}, + {"a": 1, "b": {"c": "y"}}, + {"a": 1, "b": {"c": "z"}}, + {"a": 2, "b": {"c": "z"}}, + {"a": 1, "baz": 2} + ] + }, + "cases": [ + { + "expression": "foo[?a==`1`].b.c", + "result": ["x", "y", "z"] + } + ] + }, + { + "given": {"foo": [{"name": "a"}, {"name": "b"}, {"name": "c"}]}, + "cases": [ + { + "comment": "Filter with or expression", + "expression": "foo[?name == 'a' || name == 'b']", + "result": [{"name": "a"}, {"name": "b"}] + }, + { + "expression": "foo[?name == 'a' || name == 'e']", + "result": [{"name": "a"}] + }, + { + "expression": "foo[?name == 'a' || name == 'b' || name == 'c']", + "result": [{"name": "a"}, {"name": "b"}, {"name": "c"}] + } + ] + }, + { + "given": {"foo": [{"a": 1, "b": 2}, {"a": 1, "b": 3}]}, + "cases": [ + { + "comment": "Filter with and expression", + "expression": "foo[?a == `1` && b == `2`]", + "result": [{"a": 1, "b": 2}] + }, + { + "expression": "foo[?a == `1` && b == `4`]", + "result": [] + } + ] + }, + { + "given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]}, + "cases": [ + { + "comment": "Filter with Or and And expressions", + "expression": "foo[?c == `3` || a == `1` && b == `4`]", + "result": [{"a": 1, "b": 2, "c": 3}] + }, + { + "expression": "foo[?b == `2` || a == `3` && b == `4`]", + "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] + }, + { + "expression": "foo[?a == `3` && b == `4` || b == `2`]", + "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] + }, + { + "expression": "foo[?(a == `3` && b == `4`) || b == `2`]", + "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] + }, + { + "expression": "foo[?((a == `3` && b == `4`)) || b == `2`]", + "result": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}] + }, + { + "expression": "foo[?a == `3` && (b == `4` || b == `2`)]", + "result": [{"a": 3, "b": 4}] + }, + { + "expression": "foo[?a == `3` && ((b == `4` || b == `2`))]", + "result": [{"a": 3, "b": 4}] + } + ] + }, + { + "given": {"foo": [{"a": 1, "b": 2, "c": 3}, {"a": 3, "b": 4}]}, + "cases": [ + { + "comment": "Verify precedence of or/and expressions", + "expression": "foo[?a == `1` || b ==`2` && c == `5`]", + "result": [{"a": 1, "b": 2, "c": 3}] + }, + { + "comment": "Parentheses can alter precedence", + "expression": "foo[?(a == `1` || b ==`2`) && c == `5`]", + "result": [] + }, + { + "comment": "Not expressions combined with and/or", + "expression": "foo[?!(a == `1` || b ==`2`)]", + "result": [{"a": 3, "b": 4}] + } + ] + }, + { + "given": { + "foo": [ + {"key": true}, + {"key": false}, + {"key": []}, + {"key": {}}, + {"key": [0]}, + {"key": {"a": "b"}}, + {"key": 0}, + {"key": 1}, + {"key": null}, + {"notkey": true} + ] + }, + "cases": [ + { + "comment": "Unary filter expression", + "expression": "foo[?key]", + "result": [ + {"key": true}, {"key": [0]}, {"key": {"a": "b"}}, + {"key": 0}, {"key": 1} + ] + }, + { + "comment": "Unary not filter expression", + "expression": "foo[?!key]", + "result": [ + {"key": false}, {"key": []}, {"key": {}}, + {"key": null}, {"notkey": true} + ] + }, + { + "comment": "Equality with null RHS", + "expression": "foo[?key == `null`]", + "result": [ + {"key": null}, {"notkey": true} + ] + } + ] + }, + { + "given": { + "foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + "cases": [ + { + "comment": "Using @ in a filter expression", + "expression": "foo[?@ < `5`]", + "result": [0, 1, 2, 3, 4] + }, + { + "comment": "Using @ in a filter expression", + "expression": "foo[?`5` > @]", + "result": [0, 1, 2, 3, 4] + }, + { + "comment": "Using @ in a filter expression", + "expression": "foo[?@ == @]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + } + ] + } +] diff --git a/jmespath.test/tests/function_group_by.json b/jmespath.test/tests/function_group_by.json new file mode 100644 index 0000000..081d71c --- /dev/null +++ b/jmespath.test/tests/function_group_by.json @@ -0,0 +1,132 @@ +[ + { + "given": { + "items": [ + { + "spec": { + "nodeNumber": 1, + "nodeName": "node_01", + "other": "values_01" + } + }, + { + "spec": { + "nodeNumber": 2, + "nodeName": "node_02", + "other": "values_02" + } + }, + { + "spec": { + "nodeNumber": 3, + "nodeName": "node_03", + "other": "values_03" + } + }, + { + "spec": { + "nodeNumber": 1, + "nodeName": "node_01", + "other": "values_04" + } + } + ] + }, + "cases": [ + { + "expression": "group_by(@, &`false`)", + "error": "invalid-type" + }, + { + "expression": "group_by(keys(items[*].spec|[0]), &`false`)", + "error": "invalid-type" + }, + { + "expression": "group_by(items, spec.nodeName)", + "error": "invalid-type" + }, + { + "expression": "group_by(items, &spec.nodeName)", + "result": { + "node_01": [ + { + "spec": { + "nodeNumber": 1, + "nodeName": "node_01", + "other": "values_01" + } + }, + { + "spec": { + "nodeNumber": 1, + "nodeName": "node_01", + "other": "values_04" + } + } + ], + "node_02": [ + { + "spec": { + "nodeNumber": 2, + "nodeName": "node_02", + "other": "values_02" + } + } + ], + "node_03": [ + { + "spec": { + "nodeNumber": 3, + "nodeName": "node_03", + "other": "values_03" + } + } + ] + } + }, + { + "expression": "group_by(items, &to_string(spec.nodeNumber))", + "result": { + "1": [ + { + "spec": { + "nodeNumber": 1, + "nodeName": "node_01", + "other": "values_01" + } + }, + { + "spec": { + "nodeNumber": 1, + "nodeName": "node_01", + "other": "values_04" + } + } + ], + "2": [ + { + "spec": { + "nodeNumber": 2, + "nodeName": "node_02", + "other": "values_02" + } + } + ], + "3": [ + { + "spec": { + "nodeNumber": 3, + "nodeName": "node_03", + "other": "values_03" + } + } + ] + } + }, + { + "expression": "group_by(items, &spec.nodeNumber)", + "error": "invalid-type" + } + ] + } +] \ No newline at end of file diff --git a/jmespath.test/tests/functions.json b/jmespath.test/tests/functions.json new file mode 100644 index 0000000..0ddb433 --- /dev/null +++ b/jmespath.test/tests/functions.json @@ -0,0 +1,870 @@ +[{ + "given": + { + "foo": -1, + "zero": 0, + "numbers": [-1, 3, 4, 5], + "array": [-1, 3, 4, 5, "a", "100"], + "strings": ["a", "b", "c"], + "decimals": [1.01, 1.2, -1.5], + "str": "Str", + "false": false, + "empty_list": [], + "empty_hash": {}, + "objects": {"foo": "bar", "bar": "baz"}, + "items": [["a", "first"], ["b", "second"], ["c", "third"]], + "null_key": null + }, + "cases": [ + { + "expression": "abs(foo)", + "result": 1 + }, + { + "expression": "abs(foo)", + "result": 1 + }, + { + "expression": "abs(str)", + "error": "invalid-type" + }, + { + "expression": "abs(array[1])", + "result": 3 + }, + { + "expression": "abs(array[1])", + "result": 3 + }, + { + "expression": "abs(`false`)", + "error": "invalid-type" + }, + { + "expression": "abs(`-24`)", + "result": 24 + }, + { + "expression": "abs(`-24`)", + "result": 24 + }, + { + "expression": "abs(`1`, `2`)", + "error": "invalid-arity" + }, + { + "expression": "abs()", + "error": "invalid-arity" + }, + { + "expression": "unknown_function(`1`, `2`)", + "error": "unknown-function" + }, + { + "expression": "avg(numbers)", + "result": 2.75 + }, + { + "expression": "avg(array)", + "error": "invalid-type" + }, + { + "expression": "avg('abc')", + "error": "invalid-type" + }, + { + "expression": "avg(foo)", + "error": "invalid-type" + }, + { + "expression": "avg(@)", + "error": "invalid-type" + }, + { + "expression": "avg(strings)", + "error": "invalid-type" + }, + { + "expression": "avg(empty_list)", + "result": null + }, + { + "expression": "ceil(`1.2`)", + "result": 2 + }, + { + "expression": "ceil(decimals[0])", + "result": 2 + }, + { + "expression": "ceil(decimals[1])", + "result": 2 + }, + { + "expression": "ceil(decimals[2])", + "result": -1 + }, + { + "expression": "ceil('string')", + "error": "invalid-type" + }, + { + "expression": "contains('abc', 'a')", + "result": true + }, + { + "expression": "contains('abc', 'd')", + "result": false + }, + { + "expression": "contains(`false`, 'd')", + "error": "invalid-type" + }, + { + "expression": "contains(strings, 'a')", + "result": true + }, + { + "expression": "contains(decimals, `1.2`)", + "result": true + }, + { + "expression": "contains(decimals, `false`)", + "result": false + }, + { + "expression": "ends_with(str, 'r')", + "result": true + }, + { + "expression": "ends_with(str, 'tr')", + "result": true + }, + { + "expression": "ends_with(str, 'Str')", + "result": true + }, + { + "expression": "ends_with(str, 'SStr')", + "result": false + }, + { + "expression": "ends_with(str, 'foo')", + "result": false + }, + { + "expression": "ends_with(str, `0`)", + "error": "invalid-type" + }, + { + "expression": "floor(`1.2`)", + "result": 1 + }, + { + "expression": "floor('string')", + "error": "invalid-type" + }, + { + "expression": "floor(decimals[0])", + "result": 1 + }, + { + "expression": "floor(foo)", + "result": -1 + }, + { + "expression": "floor(str)", + "error": "invalid-type" + }, + { + "expression": "sort_by(items(objects), &[0])", + "result": [["bar", "baz"], ["foo", "bar"]] + }, + { + "expression": "items(empty_hash)", + "result": [] + }, + { + "expression": "items(numbers)", + "error": "invalid-type" + }, + { + "expression": "from_items(items)", + "result": {"a": "first", "b": "second", "c": "third"} + }, + { + "expression": "length('abc')", + "result": 3 + }, + { + "expression": "length('✓foo')", + "result": 4 + }, + { + "expression": "length('')", + "result": 0 + }, + { + "expression": "length(@)", + "result": 13 + }, + { + "expression": "length(strings[0])", + "result": 1 + }, + { + "expression": "length(str)", + "result": 3 + }, + { + "expression": "length(array)", + "result": 6 + }, + { + "expression": "length(objects)", + "result": 2 + }, + { + "expression": "length(`false`)", + "error": "invalid-type" + }, + { + "expression": "length(foo)", + "error": "invalid-type" + }, + { + "expression": "length(strings[0])", + "result": 1 + }, + { + "expression": "max(numbers)", + "result": 5 + }, + { + "expression": "max(decimals)", + "result": 1.2 + }, + { + "expression": "max(strings)", + "result": "c" + }, + { + "expression": "max(abc)", + "error": "invalid-type" + }, + { + "expression": "max(array)", + "error": "invalid-type" + }, + { + "expression": "max(decimals)", + "result": 1.2 + }, + { + "expression": "max(empty_list)", + "result": null + }, + { + "expression": "merge(`{}`)", + "result": {} + }, + { + "expression": "merge(`{}`, `{}`)", + "result": {} + }, + { + "expression": "merge(`{\"a\": 1}`, `{\"b\": 2}`)", + "result": {"a": 1, "b": 2} + }, + { + "expression": "merge(`{\"a\": 1}`, `{\"a\": 2}`)", + "result": {"a": 2} + }, + { + "expression": "merge(`{\"a\": 1, \"b\": 2}`, `{\"a\": 2, \"c\": 3}`, `{\"d\": 4}`)", + "result": {"a": 2, "b": 2, "c": 3, "d": 4} + }, + { + "expression": "min(numbers)", + "result": -1 + }, + { + "expression": "min(decimals)", + "result": -1.5 + }, + { + "expression": "min(abc)", + "error": "invalid-type" + }, + { + "expression": "min(array)", + "error": "invalid-type" + }, + { + "expression": "min(empty_list)", + "result": null + }, + { + "expression": "min(decimals)", + "result": -1.5 + }, + { + "expression": "min(strings)", + "result": "a" + }, + { + "expression": "type('abc')", + "result": "string" + }, + { + "expression": "type(`1.0`)", + "result": "number" + }, + { + "expression": "type(`2`)", + "result": "number" + }, + { + "expression": "type(`true`)", + "result": "boolean" + }, + { + "expression": "type(`false`)", + "result": "boolean" + }, + { + "expression": "type(`null`)", + "result": "null" + }, + { + "expression": "type(`[0]`)", + "result": "array" + }, + { + "expression": "type(`{\"a\": \"b\"}`)", + "result": "object" + }, + { + "expression": "type(@)", + "result": "object" + }, + { + "expression": "sort(keys(objects))", + "result": ["bar", "foo"] + }, + { + "expression": "keys(foo)", + "error": "invalid-type" + }, + { + "expression": "keys(strings)", + "error": "invalid-type" + }, + { + "expression": "keys(`false`)", + "error": "invalid-type" + }, + { + "expression": "sort(values(objects))", + "result": ["bar", "baz"] + }, + { + "expression": "keys(empty_hash)", + "result": [] + }, + { + "expression": "values(foo)", + "error": "invalid-type" + }, + { + "expression": "join(', ', strings)", + "result": "a, b, c" + }, + { + "expression": "join(', ', strings)", + "result": "a, b, c" + }, + { + "expression": "join(',', `[\"a\", \"b\"]`)", + "result": "a,b" + }, + { + "expression": "join(',', `[\"a\", 0]`)", + "error": "invalid-type" + }, + { + "expression": "join(', ', str)", + "error": "invalid-type" + }, + { + "expression": "join('|', strings)", + "result": "a|b|c" + }, + { + "expression": "join(`2`, strings)", + "error": "invalid-type" + }, + { + "expression": "join('|', decimals)", + "error": "invalid-type" + }, + { + "expression": "join('|', decimals[].to_string(@))", + "result": "1.01|1.2|-1.5" + }, + { + "expression": "join('|', empty_list)", + "result": "" + }, + { + "expression": "reverse(numbers)", + "result": [5, 4, 3, -1] + }, + { + "expression": "reverse(array)", + "result": ["100", "a", 5, 4, 3, -1] + }, + { + "expression": "reverse(`[]`)", + "result": [] + }, + { + "expression": "reverse('')", + "result": "" + }, + { + "expression": "reverse('hello world')", + "result": "dlrow olleh" + }, + { + "expression": "starts_with(str, 'S')", + "result": true + }, + { + "expression": "starts_with(str, 'St')", + "result": true + }, + { + "expression": "starts_with(str, 'Str')", + "result": true + }, + { + "expression": "starts_with(str, 'String')", + "result": false + }, + { + "expression": "starts_with(str, `0`)", + "error": "invalid-type" + }, + { + "expression": "sum(numbers)", + "result": 11 + }, + { + "expression": "sum(decimals)", + "result": 0.71 + }, + { + "expression": "sum(array)", + "error": "invalid-type" + }, + { + "expression": "sum(array[].to_number(@))", + "result": 111 + }, + { + "expression": "sum(`[]`)", + "result": 0 + }, + { + "expression": "to_array('foo')", + "result": ["foo"] + }, + { + "expression": "to_array(`0`)", + "result": [0] + }, + { + "expression": "to_array(objects)", + "result": [{"foo": "bar", "bar": "baz"}] + }, + { + "expression": "to_array(`[1, 2, 3]`)", + "result": [1, 2, 3] + }, + { + "expression": "to_array(false)", + "result": [false] + }, + { + "expression": "to_string('foo')", + "result": "foo" + }, + { + "expression": "to_string(`1.2`)", + "result": "1.2" + }, + { + "expression": "to_string(`[0, 1]`)", + "result": "[0,1]" + }, + { + "expression": "to_number('1.0')", + "result": 1.0 + }, + { + "expression": "to_number('1e21')", + "result": 1e21 + }, + { + "expression": "to_number('1.1')", + "result": 1.1 + }, + { + "expression": "to_number('4')", + "result": 4 + }, + { + "expression": "to_number('notanumber')", + "result": null + }, + { + "expression": "to_number(`false`)", + "result": null + }, + { + "expression": "to_number(`null`)", + "result": null + }, + { + "expression": "to_number(`[0]`)", + "result": null + }, + { + "expression": "to_number(`{\"foo\": 0}`)", + "result": null + }, + { + "expression": "\"to_string\"(`1.0`)", + "error": "syntax" + }, + { + "expression": "sort(numbers)", + "result": [-1, 3, 4, 5] + }, + { + "expression": "sort(strings)", + "result": ["a", "b", "c"] + }, + { + "expression": "sort(decimals)", + "result": [-1.5, 1.01, 1.2] + }, + { + "expression": "sort(array)", + "error": "invalid-type" + }, + { + "expression": "sort(abc)", + "error": "invalid-type" + }, + { + "expression": "sort(empty_list)", + "result": [] + }, + { + "expression": "sort(@)", + "error": "invalid-type" + }, + { + "expression": "not_null(unknown_key, str)", + "result": "Str" + }, + { + "expression": "not_null(unknown_key, foo.bar, empty_list, str)", + "result": [] + }, + { + "expression": "not_null(unknown_key, null_key, empty_list, str)", + "result": [] + }, + { + "expression": "not_null(all, expressions, are_null)", + "result": null + }, + { + "expression": "not_null()", + "error": "invalid-arity" + }, + { + "comment": "function projection on single arg function", + "expression": "numbers[].to_string(@)", + "result": ["-1", "3", "4", "5"] + }, + { + "comment": "function projection on single arg function", + "expression": "array[].to_number(@)", + "result": [-1, 3, 4, 5, 100] + }, + { + "expression": "zip(strings, numbers)", + "result": [["a", -1], ["b", 3], ["c", 4]] + }, + { + "expression": "zip(strings, numbers, decimals)", + "result": [["a", -1, 1.01], ["b", 3, 1.2], ["c", 4, -1.5]] + }, + { + "expression": "zip(str)", + "error": "invalid-type" + } + ] +}, { + "given": + { + "foo": [ + {"b": "b", "a": "a"}, + {"c": "c", "b": "b"}, + {"d": "d", "c": "c"}, + {"e": "e", "d": "d"}, + {"f": "f", "e": "e"} + ] + }, + "cases": [ + { + "comment": "function projection on variadic function", + "expression": "foo[].not_null(f, e, d, c, b, a)", + "result": ["b", "c", "d", "e", "f"] + } + ] +}, { + "given": + { + "people": [ + {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"}, + {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"}, + {"age": 30, "age_str": "30", "bool": true, "name": "c"}, + {"age": 50, "age_str": "50", "bool": false, "name": "d"}, + {"age": 10, "age_str": "10", "bool": true, "name": 3} + ] + }, + "cases": [ + { + "comment": "sort by field expression", + "expression": "sort_by(people, &age)", + "result": [ + {"age": 10, "age_str": "10", "bool": true, "name": 3}, + {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"}, + {"age": 30, "age_str": "30", "bool": true, "name": "c"}, + {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"}, + {"age": 50, "age_str": "50", "bool": false, "name": "d"} + ] + }, + { + "expression": "sort_by(people, &age_str)", + "result": [ + {"age": 10, "age_str": "10", "bool": true, "name": 3}, + {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"}, + {"age": 30, "age_str": "30", "bool": true, "name": "c"}, + {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"}, + {"age": 50, "age_str": "50", "bool": false, "name": "d"} + ] + }, + { + "comment": "sort by function expression", + "expression": "sort_by(people, &to_number(age_str))", + "result": [ + {"age": 10, "age_str": "10", "bool": true, "name": 3}, + {"age": 20, "age_str": "20", "bool": true, "name": "a", "extra": "foo"}, + {"age": 30, "age_str": "30", "bool": true, "name": "c"}, + {"age": 40, "age_str": "40", "bool": false, "name": "b", "extra": "bar"}, + {"age": 50, "age_str": "50", "bool": false, "name": "d"} + ] + }, + { + "comment": "function projection on sort_by function", + "expression": "sort_by(people, &age)[].name", + "result": [3, "a", "c", "b", "d"] + }, + { + "expression": "sort_by(people, &extra)", + "error": "invalid-type" + }, + { + "expression": "sort_by(people, &bool)", + "error": "invalid-type" + }, + { + "expression": "sort_by(people, &name)", + "error": "invalid-type" + }, + { + "expression": "sort_by(people, name)", + "error": "invalid-type" + }, + { + "expression": "sort_by(people, &age)[].extra", + "result": ["foo", "bar"] + }, + { + "expression": "sort_by(`[]`, &age)", + "result": [] + }, + { + "expression": "max_by(people, &age)", + "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"} + }, + { + "expression": "max_by(people, &age_str)", + "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"} + }, + { + "expression": "max_by(people, &bool)", + "error": "invalid-type" + }, + { + "expression": "max_by(people, &extra)", + "error": "invalid-type" + }, + { + "expression": "max_by(people, &to_number(age_str))", + "result": {"age": 50, "age_str": "50", "bool": false, "name": "d"} + }, + { + "expression": "max_by(`[]`, &age)", + "result": null + }, + { + "expression": "min_by(people, &age)", + "result": {"age": 10, "age_str": "10", "bool": true, "name": 3} + }, + { + "expression": "min_by(people, &age_str)", + "result": {"age": 10, "age_str": "10", "bool": true, "name": 3} + }, + { + "expression": "min_by(people, &bool)", + "error": "invalid-type" + }, + { + "expression": "min_by(people, &extra)", + "error": "invalid-type" + }, + { + "expression": "min_by(people, &to_number(age_str))", + "result": {"age": 10, "age_str": "10", "bool": true, "name": 3} + }, + { + "expression": "min_by(`[]`, &age)", + "result": null + } + ] +}, { + "given": + { + "people": [ + {"age": 10, "order": "1"}, + {"age": 10, "order": "2"}, + {"age": 10, "order": "3"}, + {"age": 10, "order": "4"}, + {"age": 10, "order": "5"}, + {"age": 10, "order": "6"}, + {"age": 10, "order": "7"}, + {"age": 10, "order": "8"}, + {"age": 10, "order": "9"}, + {"age": 10, "order": "10"}, + {"age": 10, "order": "11"} + ] + }, + "cases": [ + { + "comment": "stable sort order", + "expression": "sort_by(people, &age)", + "result": [ + {"age": 10, "order": "1"}, + {"age": 10, "order": "2"}, + {"age": 10, "order": "3"}, + {"age": 10, "order": "4"}, + {"age": 10, "order": "5"}, + {"age": 10, "order": "6"}, + {"age": 10, "order": "7"}, + {"age": 10, "order": "8"}, + {"age": 10, "order": "9"}, + {"age": 10, "order": "10"}, + {"age": 10, "order": "11"} + ] + } + ] +}, { + "given": + { + "people": [ + {"a": 10, "b": 1, "c": "z"}, + {"a": 10, "b": 2, "c": null}, + {"a": 10, "b": 3}, + {"a": 10, "b": 4, "c": "z"}, + {"a": 10, "b": 5, "c": null}, + {"a": 10, "b": 6}, + {"a": 10, "b": 7, "c": "z"}, + {"a": 10, "b": 8, "c": null}, + {"a": 10, "b": 9} + ], + "empty": [] + }, + "cases": [ + { + "expression": "map(&a, people)", + "result": [10, 10, 10, 10, 10, 10, 10, 10, 10] + }, + { + "expression": "map(&c, people)", + "result": ["z", null, null, "z", null, null, "z", null, null] + }, + { + "expression": "map(&a, badkey)", + "error": "invalid-type" + }, + { + "expression": "map(&foo, empty)", + "result": [] + } + ] +}, { + "given": { + "array": [ + { + "foo": {"bar": "yes1"} + }, + { + "foo": {"bar": "yes2"} + }, + { + "foo1": {"bar": "no"} + } + ]}, + "cases": [ + { + "expression": "map(&foo.bar, array)", + "result": ["yes1", "yes2", null] + }, + { + "expression": "map(&foo1.bar, array)", + "result": [null, null, "no"] + }, + { + "expression": "map(&foo.bar.baz, array)", + "result": [null, null, null] + } + ] +}, { + "given": { + "array": [[1, 2, 3, [4]], [5, 6, 7, [8, 9]]] + }, + "cases": [ + { + "expression": "map(&[], array)", + "result": [[1, 2, 3, 4], [5, 6, 7, 8, 9]] + } + ] +} +] diff --git a/jmespath.test/tests/functions_strings.json b/jmespath.test/tests/functions_strings.json new file mode 100644 index 0000000..8e14797 --- /dev/null +++ b/jmespath.test/tests/functions_strings.json @@ -0,0 +1,145 @@ +[ + { + "given": { + "abab": "aabaaabaaaab", + "string": "subject string", + "split": "avg|-|min|-|max|-|mean|-|mode|-|median" + }, + "cases": [ + { + "expression": "find_first(string, 'string', `1`, `2`, `3`)", + "error": "invalid-arity" + }, + { + "expression": "find_first(@, 'string', `1`, `2`)", + "error": "invalid-type" + }, + { + "expression": "find_first(string, 'string', '1')", + "error": "invalid-type" + }, + { + "expression": "find_first(string, 'string', `1`, '2')", + "error": "invalid-type" + }, + { + "expression": "find_first(string, 'string', `1.3`, '2')", + "error": "invalid-type" + }, + { + "expression": "find_first(string, 'string', `1.3`, `2.4`)", + "error": "invalid-value" + }, + + { "expression": "find_first(string, 'string')", "result": 8 }, + { "expression": "find_first(string, 'string', `0`)", "result": 8 }, + { "expression": "find_first(string, 'string', `0`, `14`)", "result": 8 }, + { "expression": "find_first(string, 'string', `-6`)", "result": 8 }, + { "expression": "find_first(string, 'string', `-99`, `100`)", "result": 8 }, + { "expression": "find_first(string, 'string', `0`, `13`)", "result": null }, + { "expression": "find_first(string, 'string', `8`)", "result": 8 }, + { "expression": "find_first(string, 'string', `8`, `11`)", "result": null }, + { "expression": "find_first(string, 'string', `9`)", "result": null }, + { "expression": "find_first(string, 's')", "result": 0 }, + { "expression": "find_first(string, 's', `1`)", "result": 8 }, + { "expression": "find_first(string, '')", "result": null }, + { "expression": "find_first('', '')", "result": null }, + + { "expression": "find_last(string, 'string')", "result": 8 }, + { "expression": "find_last(string, 'string', `8`)", "result": 8 }, + { "expression": "find_last(string, 'string', `8`, `9`)", "result": null }, + { "expression": "find_last(string, 'string', `9`)", "result": null }, + { "expression": "find_last(string, 's', `1`)", "result": 8 }, + { "expression": "find_last(string, 's', `-6`)", "result": 8 }, + { "expression": "find_last(string, 's', `0`, `7`)", "result": 0 }, + { "expression": "find_last(string, '')", "result": null }, + { "expression": "find_last('', '')", "result": null }, + + { "expression": "lower('STRING')", "result": "string" }, + { "expression": "upper('string')", "result": "STRING" }, + + { + "expression": "replace(abab, 'aa', '-', `0.333333`)", + "error": "invalid-value" + }, + + { + "expression": "replace(abab, 'aa', '-', `0.001`)", + "error": "invalid-value" + }, + + { "expression": "replace(abab, 'aa', '-', `0`)", "result": "aabaaabaaaab" }, + { "expression": "replace(abab, 'aa', '-', `1`)", "result": "-baaabaaaab" }, + { "expression": "replace(abab, 'aa', '-', `2`)", "result": "-b-abaaaab" }, + { "expression": "replace(abab, 'aa', '-', `3`)", "result": "-b-ab-aab" }, + { "expression": "replace(abab, 'aa', '-')", "result": "-b-ab--b" }, + + { "expression": "trim(' subject string ')", "result": "subject string" }, + { "expression": "trim(' subject string ', '')", "result": "subject string" }, + { "expression": "trim(' subject string ', ' ')", "result": "subject string" }, + { "expression": "trim(' subject string ', 's')", "result": " subject string " }, + { "expression": "trim(' subject string ', 'su')", "result": " subject string " }, + { "expression": "trim(' subject string ', 'su ')", "result": "bject string" }, + { "expression": "trim(' subject string ', 'gsu ')", "result": "bject strin" }, + + { + "expression": "trim('\u0009\u000A\u000B\u000C\u000D\u0020\u0085\u00A0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000')", + "result": "" + }, + + { "expression": "trim_left(' subject string ')", "result": "subject string " }, + { "expression": "trim_left(' subject string ', 's')", "result": " subject string " }, + { "expression": "trim_left(' subject string ', 'su')", "result": " subject string " }, + { "expression": "trim_left(' subject string ', 'su ')", "result": "bject string " }, + { "expression": "trim_left(' subject string ', 'gsu ')", "result": "bject string " }, + + { "expression": "trim_right(' subject string ')", "result": " subject string" }, + { "expression": "trim_right(' subject string ', 's')", "result": " subject string " }, + { "expression": "trim_right(' subject string ', 'su')", "result": " subject string " }, + { "expression": "trim_right(' subject string ', 'su ')", "result": " subject string" }, + { "expression": "trim_right(' subject string ', 'gsu ')", "result": " subject strin" }, + + { + "expression": "pad_left('string', '1')", + "error": "invalid-type" + + }, + { + "expression": "pad_left('string', `1`, '--')", + "error": "invalid-value" + + }, + { + "expression": "pad_left('string', `1.4`)", + "error": "invalid-value" + + }, + + { "expression": "pad_left('string', `0`)", "result": "string" }, + { "expression": "pad_left('string', `5`)", "result": "string" }, + { "expression": "pad_left('string', `10`)", "result": " string" }, + { "expression": "pad_left('string', `10`, '-')", "result": "----string" }, + + { "expression": "pad_right('string', `0`)", "result": "string" }, + { "expression": "pad_right('string', `5`)", "result": "string" }, + { "expression": "pad_right('string', `10`)", "result": "string " }, + { "expression": "pad_right('string', `10`, '-')", "result": "string----" }, + + { + "expression": "split('/', '/', `3.7`)", + "error": "invalid-value" + }, + + { "expression": "split('/', '/')", "result": [ "", "" ] }, + { "expression": "split('', '')", "result": [ ] }, + { "expression": "split('all chars', '')", "result": [ "a", "l", "l", " ", "c", "h", "a", "r", "s" ] }, + { "expression": "split('all chars', '', `3`)", "result": [ "a", "l", "l", " chars" ] }, + + { "expression": "split(split, '|-|')", "result": [ "avg", "min", "max", "mean", "mode", "median" ] }, + { "expression": "split(split, '|-|', `3`)", "result": [ "avg", "min", "max", "mean|-|mode|-|median" ] }, + { "expression": "split(split, '|-|', `2`)", "result": [ "avg", "min", "max|-|mean|-|mode|-|median" ] }, + { "expression": "split(split, '|-|', `1`)", "result": [ "avg", "min|-|max|-|mean|-|mode|-|median" ] }, + { "expression": "split(split, '|-|', `0`)", "result": [ "avg|-|min|-|max|-|mean|-|mode|-|median" ] } + ] + } +] diff --git a/jmespath.test/tests/identifiers.json b/jmespath.test/tests/identifiers.json new file mode 100644 index 0000000..e168471 --- /dev/null +++ b/jmespath.test/tests/identifiers.json @@ -0,0 +1,1390 @@ +[ + { + "given": { + "__L": true + }, + "cases": [ + { + "expression": "__L", + "result": true + } + ] + }, + { + "given": { + "!\r": true + }, + "cases": [ + { + "expression": "\"!\\r\"", + "result": true + } + ] + }, + { + "given": { + "Y_1623": true + }, + "cases": [ + { + "expression": "Y_1623", + "result": true + } + ] + }, + { + "given": { + "x": true + }, + "cases": [ + { + "expression": "x", + "result": true + } + ] + }, + { + "given": { + "\tF\uCebb": true + }, + "cases": [ + { + "expression": "\"\\tF\\uCebb\"", + "result": true + } + ] + }, + { + "given": { + " \t": true + }, + "cases": [ + { + "expression": "\" \\t\"", + "result": true + } + ] + }, + { + "given": { + " ": true + }, + "cases": [ + { + "expression": "\" \"", + "result": true + } + ] + }, + { + "given": { + "v2": true + }, + "cases": [ + { + "expression": "v2", + "result": true + } + ] + }, + { + "given": { + "\t": true + }, + "cases": [ + { + "expression": "\"\\t\"", + "result": true + } + ] + }, + { + "given": { + "_X": true + }, + "cases": [ + { + "expression": "_X", + "result": true + } + ] + }, + { + "given": { + "\t4\ud9da\udd15": true + }, + "cases": [ + { + "expression": "\"\\t4\\ud9da\\udd15\"", + "result": true + } + ] + }, + { + "given": { + "v24_W": true + }, + "cases": [ + { + "expression": "v24_W", + "result": true + } + ] + }, + { + "given": { + "H": true + }, + "cases": [ + { + "expression": "\"H\"", + "result": true + } + ] + }, + { + "given": { + "\f": true + }, + "cases": [ + { + "expression": "\"\\f\"", + "result": true + } + ] + }, + { + "given": { + "E4": true + }, + "cases": [ + { + "expression": "\"E4\"", + "result": true + } + ] + }, + { + "given": { + "!": true + }, + "cases": [ + { + "expression": "\"!\"", + "result": true + } + ] + }, + { + "given": { + "tM": true + }, + "cases": [ + { + "expression": "tM", + "result": true + } + ] + }, + { + "given": { + " [": true + }, + "cases": [ + { + "expression": "\" [\"", + "result": true + } + ] + }, + { + "given": { + "R!": true + }, + "cases": [ + { + "expression": "\"R!\"", + "result": true + } + ] + }, + { + "given": { + "_6W": true + }, + "cases": [ + { + "expression": "_6W", + "result": true + } + ] + }, + { + "given": { + "\uaBA1\r": true + }, + "cases": [ + { + "expression": "\"\\uaBA1\\r\"", + "result": true + } + ] + }, + { + "given": { + "tL7": true + }, + "cases": [ + { + "expression": "tL7", + "result": true + } + ] + }, + { + "given": { + "<": true + }, + "cases": [ + { + "expression": "\">\"", + "result": true + } + ] + }, + { + "given": { + "hvu": true + }, + "cases": [ + { + "expression": "hvu", + "result": true + } + ] + }, + { + "given": { + "; !": true + }, + "cases": [ + { + "expression": "\"; !\"", + "result": true + } + ] + }, + { + "given": { + "hU": true + }, + "cases": [ + { + "expression": "hU", + "result": true + } + ] + }, + { + "given": { + "!I\n\/": true + }, + "cases": [ + { + "expression": "\"!I\\n\\/\"", + "result": true + } + ] + }, + { + "given": { + "\uEEbF": true + }, + "cases": [ + { + "expression": "\"\\uEEbF\"", + "result": true + } + ] + }, + { + "given": { + "U)\t": true + }, + "cases": [ + { + "expression": "\"U)\\t\"", + "result": true + } + ] + }, + { + "given": { + "fa0_9": true + }, + "cases": [ + { + "expression": "fa0_9", + "result": true + } + ] + }, + { + "given": { + "/": true + }, + "cases": [ + { + "expression": "\"/\"", + "result": true + } + ] + }, + { + "given": { + "Gy": true + }, + "cases": [ + { + "expression": "Gy", + "result": true + } + ] + }, + { + "given": { + "\b": true + }, + "cases": [ + { + "expression": "\"\\b\"", + "result": true + } + ] + }, + { + "given": { + "<": true + }, + "cases": [ + { + "expression": "\"<\"", + "result": true + } + ] + }, + { + "given": { + "\t": true + }, + "cases": [ + { + "expression": "\"\\t\"", + "result": true + } + ] + }, + { + "given": { + "\t&\\\r": true + }, + "cases": [ + { + "expression": "\"\\t&\\\\\\r\"", + "result": true + } + ] + }, + { + "given": { + "#": true + }, + "cases": [ + { + "expression": "\"#\"", + "result": true + } + ] + }, + { + "given": { + "B__": true + }, + "cases": [ + { + "expression": "B__", + "result": true + } + ] + }, + { + "given": { + "\nS \n": true + }, + "cases": [ + { + "expression": "\"\\nS \\n\"", + "result": true + } + ] + }, + { + "given": { + "Bp": true + }, + "cases": [ + { + "expression": "Bp", + "result": true + } + ] + }, + { + "given": { + ",\t;": true + }, + "cases": [ + { + "expression": "\",\\t;\"", + "result": true + } + ] + }, + { + "given": { + "B_q": true + }, + "cases": [ + { + "expression": "B_q", + "result": true + } + ] + }, + { + "given": { + "\/+\t\n\b!Z": true + }, + "cases": [ + { + "expression": "\"\\/+\\t\\n\\b!Z\"", + "result": true + } + ] + }, + { + "given": { + "\udadd\udfc7\\ueFAc": true + }, + "cases": [ + { + "expression": "\"\udadd\udfc7\\\\ueFAc\"", + "result": true + } + ] + }, + { + "given": { + ":\f": true + }, + "cases": [ + { + "expression": "\":\\f\"", + "result": true + } + ] + }, + { + "given": { + "\/": true + }, + "cases": [ + { + "expression": "\"\\/\"", + "result": true + } + ] + }, + { + "given": { + "_BW_6Hg_Gl": true + }, + "cases": [ + { + "expression": "_BW_6Hg_Gl", + "result": true + } + ] + }, + { + "given": { + "\udbcf\udc02": true + }, + "cases": [ + { + "expression": "\"\udbcf\udc02\"", + "result": true + } + ] + }, + { + "given": { + "zs1DC": true + }, + "cases": [ + { + "expression": "zs1DC", + "result": true + } + ] + }, + { + "given": { + "__434": true + }, + "cases": [ + { + "expression": "__434", + "result": true + } + ] + }, + { + "given": { + "\udb94\udd41": true + }, + "cases": [ + { + "expression": "\"\udb94\udd41\"", + "result": true + } + ] + }, + { + "given": { + "Z_5": true + }, + "cases": [ + { + "expression": "Z_5", + "result": true + } + ] + }, + { + "given": { + "z_M_": true + }, + "cases": [ + { + "expression": "z_M_", + "result": true + } + ] + }, + { + "given": { + "YU_2": true + }, + "cases": [ + { + "expression": "YU_2", + "result": true + } + ] + }, + { + "given": { + "_0": true + }, + "cases": [ + { + "expression": "_0", + "result": true + } + ] + }, + { + "given": { + "\b+": true + }, + "cases": [ + { + "expression": "\"\\b+\"", + "result": true + } + ] + }, + { + "given": { + "\"": true + }, + "cases": [ + { + "expression": "\"\\\"\"", + "result": true + } + ] + }, + { + "given": { + "D7": true + }, + "cases": [ + { + "expression": "D7", + "result": true + } + ] + }, + { + "given": { + "_62L": true + }, + "cases": [ + { + "expression": "_62L", + "result": true + } + ] + }, + { + "given": { + "\tK\t": true + }, + "cases": [ + { + "expression": "\"\\tK\\t\"", + "result": true + } + ] + }, + { + "given": { + "\n\\\f": true + }, + "cases": [ + { + "expression": "\"\\n\\\\\\f\"", + "result": true + } + ] + }, + { + "given": { + "I_": true + }, + "cases": [ + { + "expression": "I_", + "result": true + } + ] + }, + { + "given": { + "W_a0_": true + }, + "cases": [ + { + "expression": "W_a0_", + "result": true + } + ] + }, + { + "given": { + "BQ": true + }, + "cases": [ + { + "expression": "BQ", + "result": true + } + ] + }, + { + "given": { + "\tX$\uABBb": true + }, + "cases": [ + { + "expression": "\"\\tX$\\uABBb\"", + "result": true + } + ] + }, + { + "given": { + "Z9": true + }, + "cases": [ + { + "expression": "Z9", + "result": true + } + ] + }, + { + "given": { + "\b%\"\uda38\udd0f": true + }, + "cases": [ + { + "expression": "\"\\b%\\\"\uda38\udd0f\"", + "result": true + } + ] + }, + { + "given": { + "_F": true + }, + "cases": [ + { + "expression": "_F", + "result": true + } + ] + }, + { + "given": { + "!,": true + }, + "cases": [ + { + "expression": "\"!,\"", + "result": true + } + ] + }, + { + "given": { + "\"!": true + }, + "cases": [ + { + "expression": "\"\\\"!\"", + "result": true + } + ] + }, + { + "given": { + "Hh": true + }, + "cases": [ + { + "expression": "Hh", + "result": true + } + ] + }, + { + "given": { + "&": true + }, + "cases": [ + { + "expression": "\"&\"", + "result": true + } + ] + }, + { + "given": { + "9\r\\R": true + }, + "cases": [ + { + "expression": "\"9\\r\\\\R\"", + "result": true + } + ] + }, + { + "given": { + "M_k": true + }, + "cases": [ + { + "expression": "M_k", + "result": true + } + ] + }, + { + "given": { + "!\b\n\udb06\ude52\"\"": true + }, + "cases": [ + { + "expression": "\"!\\b\\n\udb06\ude52\\\"\\\"\"", + "result": true + } + ] + }, + { + "given": { + "6": true + }, + "cases": [ + { + "expression": "\"6\"", + "result": true + } + ] + }, + { + "given": { + "_7": true + }, + "cases": [ + { + "expression": "_7", + "result": true + } + ] + }, + { + "given": { + "0": true + }, + "cases": [ + { + "expression": "\"0\"", + "result": true + } + ] + }, + { + "given": { + "\\8\\": true + }, + "cases": [ + { + "expression": "\"\\\\8\\\\\"", + "result": true + } + ] + }, + { + "given": { + "b7eo": true + }, + "cases": [ + { + "expression": "b7eo", + "result": true + } + ] + }, + { + "given": { + "xIUo9": true + }, + "cases": [ + { + "expression": "xIUo9", + "result": true + } + ] + }, + { + "given": { + "5": true + }, + "cases": [ + { + "expression": "\"5\"", + "result": true + } + ] + }, + { + "given": { + "?": true + }, + "cases": [ + { + "expression": "\"?\"", + "result": true + } + ] + }, + { + "given": { + "sU": true + }, + "cases": [ + { + "expression": "sU", + "result": true + } + ] + }, + { + "given": { + "VH2&H\\\/": true + }, + "cases": [ + { + "expression": "\"VH2&H\\\\\\/\"", + "result": true + } + ] + }, + { + "given": { + "_C": true + }, + "cases": [ + { + "expression": "_C", + "result": true + } + ] + }, + { + "given": { + "_": true + }, + "cases": [ + { + "expression": "_", + "result": true + } + ] + }, + { + "given": { + "<\t": true + }, + "cases": [ + { + "expression": "\"<\\t\"", + "result": true + } + ] + }, + { + "given": { + "\uD834\uDD1E": true + }, + "cases": [ + { + "expression": "\"\\uD834\\uDD1E\"", + "result": true + } + ] + }, + { + "given": { "": "foo" }, + "cases": [ + { + "expression": "\"\"", + "result": "foo" + }, + { + "expression": "@ | \"\"", + "result": "foo" + } + ] + } +] diff --git a/jmespath.test/tests/indices.json b/jmespath.test/tests/indices.json new file mode 100644 index 0000000..aa03b35 --- /dev/null +++ b/jmespath.test/tests/indices.json @@ -0,0 +1,346 @@ +[{ + "given": + {"foo": {"bar": ["zero", "one", "two"]}}, + "cases": [ + { + "expression": "foo.bar[0]", + "result": "zero" + }, + { + "expression": "foo.bar[1]", + "result": "one" + }, + { + "expression": "foo.bar[2]", + "result": "two" + }, + { + "expression": "foo.bar[3]", + "result": null + }, + { + "expression": "foo.bar[-1]", + "result": "two" + }, + { + "expression": "foo.bar[-2]", + "result": "one" + }, + { + "expression": "foo.bar[-3]", + "result": "zero" + }, + { + "expression": "foo.bar[-4]", + "result": null + } + ] +}, +{ + "given": + {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]}, + "cases": [ + { + "expression": "foo.bar", + "result": null + }, + { + "expression": "foo[0].bar", + "result": "one" + }, + { + "expression": "foo[1].bar", + "result": "two" + }, + { + "expression": "foo[2].bar", + "result": "three" + }, + { + "expression": "foo[3].notbar", + "result": "four" + }, + { + "expression": "foo[3].bar", + "result": null + }, + { + "expression": "foo[0]", + "result": {"bar": "one"} + }, + { + "expression": "foo[1]", + "result": {"bar": "two"} + }, + { + "expression": "foo[2]", + "result": {"bar": "three"} + }, + { + "expression": "foo[3]", + "result": {"notbar": "four"} + }, + { + "expression": "foo[4]", + "result": null + } + ] +}, +{ + "given": [ + "one", "two", "three" + ], + "cases": [ + { + "expression": "[0]", + "result": "one" + }, + { + "expression": "[1]", + "result": "two" + }, + { + "expression": "[2]", + "result": "three" + }, + { + "expression": "[-1]", + "result": "three" + }, + { + "expression": "[-2]", + "result": "two" + }, + { + "expression": "[-3]", + "result": "one" + } + ] +}, +{ + "given": {"reservations": [ + {"instances": [{"foo": 1}, {"foo": 2}]} + ]}, + "cases": [ + { + "expression": "reservations[].instances[].foo", + "result": [1, 2] + }, + { + "expression": "reservations[].instances[].bar", + "result": [] + }, + { + "expression": "reservations[].notinstances[].foo", + "result": [] + }, + { + "expression": "reservations[].notinstances[].foo", + "result": [] + } + ] +}, +{ + "given": {"reservations": [{ + "instances": [ + {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}, + {"foo": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]}, + {"foo": "bar"}, + {"notfoo": [{"bar": 20}, {"bar": 21}, {"notbar": [7]}, {"bar": 22}]}, + {"bar": [{"baz": [1]}, {"baz": [2]}, {"baz": [3]}, {"baz": [4]}]}, + {"baz": [{"baz": [1, 2]}, {"baz": []}, {"baz": []}, {"baz": [3, 4]}]}, + {"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]} + ], + "otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]} + }, { + "instances": [ + {"a": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]}, + {"b": [{"bar": 5}, {"bar": 6}, {"notbar": [7]}, {"bar": 8}]}, + {"c": "bar"}, + {"notfoo": [{"bar": 23}, {"bar": 24}, {"notbar": [7]}, {"bar": 25}]}, + {"qux": [{"baz": []}, {"baz": [1, 2, 3]}, {"baz": [4]}, {"baz": []}]} + ], + "otherkey": {"foo": [{"bar": 1}, {"bar": 2}, {"notbar": 3}, {"bar": 4}]} + } + ]}, + "cases": [ + { + "expression": "reservations[].instances[].foo[].bar", + "result": [1, 2, 4, 5, 6, 8] + }, + { + "expression": "reservations[].instances[].foo[].baz", + "result": [] + }, + { + "expression": "reservations[].instances[].notfoo[].bar", + "result": [20, 21, 22, 23, 24, 25] + }, + { + "expression": "reservations[].instances[].notfoo[].notbar", + "result": [[7], [7]] + }, + { + "expression": "reservations[].notinstances[].foo", + "result": [] + }, + { + "expression": "reservations[].instances[].foo[].notbar", + "result": [3, [7]] + }, + { + "expression": "reservations[].instances[].bar[].baz", + "result": [[1], [2], [3], [4]] + }, + { + "expression": "reservations[].instances[].baz[].baz", + "result": [[1, 2], [], [], [3, 4]] + }, + { + "expression": "reservations[].instances[].qux[].baz", + "result": [[], [1, 2, 3], [4], [], [], [1, 2, 3], [4], []] + }, + { + "expression": "reservations[].instances[].qux[].baz[]", + "result": [1, 2, 3, 4, 1, 2, 3, 4] + } + ] +}, +{ + "given": { + "foo": [ + [["one", "two"], ["three", "four"]], + [["five", "six"], ["seven", "eight"]], + [["nine"], ["ten"]] + ] + }, + "cases": [ + { + "expression": "foo[]", + "result": [["one", "two"], ["three", "four"], ["five", "six"], + ["seven", "eight"], ["nine"], ["ten"]] + }, + { + "expression": "foo[][0]", + "result": ["one", "three", "five", "seven", "nine", "ten"] + }, + { + "expression": "foo[][1]", + "result": ["two", "four", "six", "eight"] + }, + { + "expression": "foo[][0][0]", + "result": [] + }, + { + "expression": "foo[][2][2]", + "result": [] + }, + { + "expression": "foo[][0][0][100]", + "result": [] + } + ] +}, +{ + "given": { + "foo": [{ + "bar": [ + { + "qux": 2, + "baz": 1 + }, + { + "qux": 4, + "baz": 3 + } + ] + }, + { + "bar": [ + { + "qux": 6, + "baz": 5 + }, + { + "qux": 8, + "baz": 7 + } + ] + } + ] + }, + "cases": [ + { + "expression": "foo", + "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, + {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] + }, + { + "expression": "foo[]", + "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, + {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] + }, + { + "expression": "foo[].bar", + "result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}], + [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]] + }, + { + "expression": "foo[].bar[]", + "result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}, + {"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}] + }, + { + "expression": "foo[].bar[].baz", + "result": [1, 3, 5, 7] + } + ] +}, +{ + "given": { + "string": "string", + "hash": {"foo": "bar", "bar": "baz"}, + "number": 23, + "nullvalue": null + }, + "cases": [ + { + "expression": "string[]", + "result": null + }, + { + "expression": "hash[]", + "result": null + }, + { + "expression": "number[]", + "result": null + }, + { + "expression": "nullvalue[]", + "result": null + }, + { + "expression": "string[].foo", + "result": null + }, + { + "expression": "hash[].foo", + "result": null + }, + { + "expression": "number[].foo", + "result": null + }, + { + "expression": "nullvalue[].foo", + "result": null + }, + { + "expression": "nullvalue[].foo[].bar", + "result": null + } + ] +} +] diff --git a/jmespath.test/tests/jep-12/jep-12-literal.json b/jmespath.test/tests/jep-12/jep-12-literal.json new file mode 100644 index 0000000..40cdd53 --- /dev/null +++ b/jmespath.test/tests/jep-12/jep-12-literal.json @@ -0,0 +1,39 @@ +[ + { + "comment": "Literals", + "given": { + "type": "object" + }, + "cases": [ + { + "expression": "`foo`", + "error": "syntax" + }, + { + "comment": "Literal with non-JSON whitespace U+0085 NEXT LINE", + "expression": "`0\u0085`", + "error": "syntax" + }, + { + "comment": "Literal with non-JSON whitespace U+00A0 NO-BREAK SPACE", + "expression": "`0\u00A0`", + "error": "syntax" + }, + { + "comment": "Literal with non-JSON whitespace U+1680 OGHAM SPACE MARK", + "expression": "`0\u1680`", + "error": "syntax" + }, + { + "comment": "Literal with non-JSON whitespace U+2028 LINE SEPARATOR", + "expression": "`0\u2028`", + "error": "syntax" + }, + { + "comment": "Literal with non-JSON whitespace U+3000 IDEOGRAPHIC SPACE", + "expression": "`0\u3000`", + "error": "syntax" + } + ] + } +] \ No newline at end of file diff --git a/jmespath.test/tests/legacy/legacy-literal.json b/jmespath.test/tests/legacy/legacy-literal.json new file mode 100644 index 0000000..8fd73b9 --- /dev/null +++ b/jmespath.test/tests/legacy/legacy-literal.json @@ -0,0 +1,89 @@ +[ + { + "given": { + "foo": [ + { + "name": "a" + }, + { + "name": "b" + } + ], + "bar": { + "baz": "qux" + } + }, + "cases": [ + { + "expression": "`foo`", + "result": "foo" + }, + { + "comment": "Double quotes must be escaped.", + "expression": "`foo\\\"quote`", + "result": "foo\"quote" + }, + { + "expression": "`✓`", + "result": "✓" + }, + { + "expression": "`1\\``", + "result": "1`" + }, + { + "comment": "Multiple literal expressions with escapes", + "expression": "`\\\\`.{a:`b`}", + "result": { + "a": "b" + } + } + ] + }, + { + "comment": "Literals", + "given": { + "type": "object" + }, + "cases": [ + { + "expression": "`foo`", + "result": "foo" + }, + { + "expression": "` foo`", + "result": "foo" + }, + { + "comment": "Literal on RHS of subexpr not allowed", + "expression": "foo.`bar`", + "error": "syntax" + }, + { + "comment": "Literal with non-JSON whitespace U+0085 NEXT LINE", + "expression": "`0\u0085`", + "result": "0\u0085" + }, + { + "comment": "Literal with non-JSON whitespace U+00A0 NO-BREAK SPACE", + "expression": "`0\u00A0`", + "result": "0\u00A0" + }, + { + "comment": "Literal with non-JSON whitespace U+1680 OGHAM SPACE MARK", + "expression": "`0\u1680`", + "result": "0\u1680" + }, + { + "comment": "Literal with non-JSON whitespace U+2028 LINE SEPARATOR", + "expression": "`0\u2028`", + "result": "0\u2028" + }, + { + "comment": "Literal with non-JSON whitespace U+3000 IDEOGRAPHIC SPACE", + "expression": "`0\u3000`", + "result": "0\u3000" + } + ] + } +] \ No newline at end of file diff --git a/jmespath.test/tests/letexpr.json b/jmespath.test/tests/letexpr.json new file mode 100644 index 0000000..9132a1b --- /dev/null +++ b/jmespath.test/tests/letexpr.json @@ -0,0 +1,259 @@ +[ + { + "given": { + "foo": { + "bar": "baz" + } + }, + "cases": [ + { + "expression": "let $foo = foo in $foo", + "result": { + "bar": "baz" + } + }, + { + "expression": "let $foo = foo.bar in $foo", + "result": "baz" + }, + { + "expression": "let $foo = foo.bar in [$foo, $foo]", + "result": [ + "baz", + "baz" + ] + }, + { + "command": "Multiple assignments", + "expression": "let $foo = 'foo', $bar = 'bar' in [$foo, $bar]", + "result": [ + "foo", + "bar" + ] + } + ] + }, + { + "given": { + "a": "topval", + "b": [ + { + "a": "inner1" + }, + { + "a": "inner2" + } + ] + }, + "cases": [ + { + "expression": "let $a = a in b[*].[a, $a, let $a = 'shadow' in $a]", + "result": [ + [ + "inner1", + "topval", + "shadow" + ], + [ + "inner2", + "topval", + "shadow" + ] + ] + }, + { + "comment": "Bindings only visible within expression clause", + "expression": "let $a = 'top-a' in let $a = 'in-a', $b = $a in $b", + "result": "top-a" + } + ] + }, + { + "given": { + "foo": [ + [ + 0, + 1 + ], + [ + 2, + 3 + ], + [ + 4, + 5 + ] + ] + }, + "cases": [ + { + "comment": "Projection is stopped when bound to variable", + "expression": "let $foo = foo[*] in $foo[0]", + "result": [ + 0, + 1 + ] + } + ] + }, + { + "given": [ + { + "home_state": "WA", + "states": [ + { + "name": "WA", + "cities": [ + "Seattle", + "Bellevue", + "Olympia" + ] + }, + { + "name": "CA", + "cities": [ + "Los Angeles", + "San Francisco" + ] + }, + { + "name": "NY", + "cities": [ + "New York City", + "Albany" + ] + } + ] + }, + { + "home_state": "NY", + "states": [ + { + "name": "WA", + "cities": [ + "Seattle", + "Bellevue", + "Olympia" + ] + }, + { + "name": "CA", + "cities": [ + "Los Angeles", + "San Francisco" + ] + }, + { + "name": "NY", + "cities": [ + "New York City", + "Albany" + ] + } + ] + } + ], + "cases": [ + { + "expression": "[*].[let $home_state = home_state in states[? name == $home_state].cities[]][]", + "result": [ + [ + "Seattle", + "Bellevue", + "Olympia" + ], + [ + "New York City", + "Albany" + ] + ] + } + ] + }, + { + "given": { + "imageDetails": [ + { + "repositoryName": "org/first-repo", + "imageTags": [ + "latest", + "v1.0", + "v1.2" + ], + "imageDigest": "sha256:abcd" + }, + { + "repositoryName": "org/second-repo", + "imageTags": [ + "v2.0", + "v2.2" + ], + "imageDigest": "sha256:efgh" + } + ] + }, + "cases": [ + { + "expression": "imageDetails[].[\n let $repo = repositoryName,\n $digest = imageDigest\n in\n imageTags[].[@, $digest, $repo]\n][][]\n", + "result": [ + [ + "latest", + "sha256:abcd", + "org/first-repo" + ], + [ + "v1.0", + "sha256:abcd", + "org/first-repo" + ], + [ + "v1.2", + "sha256:abcd", + "org/first-repo" + ], + [ + "v2.0", + "sha256:efgh", + "org/second-repo" + ], + [ + "v2.2", + "sha256:efgh", + "org/second-repo" + ] + ] + } + ] + }, + { + "given": {}, + "cases": [ + { + "expression": "$noexist", + "error": "undefined-variable" + }, + { + "comment": "Reference out of scope variable", + "expression": "[let $scope = 'foo' in [$scope], $scope]", + "error": "undefined-variable" + }, + { + "comment": "Can't use var ref in RHS of subexpression", + "expression": "foo.$bar", + "error": "syntax" + } + ] + }, + { + "given": { + "foo": "outer" + }, + "cases": [ + { + "comment": "Inner scope explicitly sets 'foo' to null, should not fall back to outer scope", + "expression": "let $foo = foo in let $foo = null in $foo", + "result": null + } + ] + } +] diff --git a/jmespath.test/tests/literal.json b/jmespath.test/tests/literal.json new file mode 100644 index 0000000..13a6754 --- /dev/null +++ b/jmespath.test/tests/literal.json @@ -0,0 +1,235 @@ +[ + { + "given": { + "foo": [ + { + "name": "a" + }, + { + "name": "b" + } + ], + "bar": { + "baz": "qux" + } + }, + "cases": [ + { + "expression": "`\"foo\"`", + "result": "foo" + }, + { + "comment": "Interpret escaped unicode.", + "expression": "`\"\\u03a6\"`", + "result": "Φ" + }, + { + "expression": "`\"✓\"`", + "result": "✓" + }, + { + "expression": "`[1, 2, 3]`", + "result": [ + 1, + 2, + 3 + ] + }, + { + "expression": "`{\"a\": \"b\"}`", + "result": { + "a": "b" + } + }, + { + "expression": "`true`", + "result": true + }, + { + "expression": "`false`", + "result": false + }, + { + "expression": "`null`", + "result": null + }, + { + "expression": "`0`", + "result": 0 + }, + { + "expression": "`1`", + "result": 1 + }, + { + "expression": "`2`", + "result": 2 + }, + { + "expression": "`3`", + "result": 3 + }, + { + "expression": "`4`", + "result": 4 + }, + { + "expression": "`5`", + "result": 5 + }, + { + "expression": "`6`", + "result": 6 + }, + { + "expression": "`7`", + "result": 7 + }, + { + "expression": "`8`", + "result": 8 + }, + { + "expression": "`9`", + "result": 9 + }, + { + "comment": "Escaping a backtick in quotes", + "expression": "`\"foo\\`bar\"`", + "result": "foo`bar" + }, + { + "comment": "Double quote in literal", + "expression": "`\"foo\\\"bar\"`", + "result": "foo\"bar" + }, + { + "expression": "`\"1\\`\"`", + "result": "1`" + }, + { + "comment": "Multiple literal expressions with escapes", + "expression": "`\"\\\\\"`.{a:`\"b\"`}", + "result": { + "a": "b" + } + }, + { + "comment": "literal . identifier", + "expression": "`{\"a\": \"b\"}`.a", + "result": "b" + }, + { + "comment": "literal . identifier . identifier", + "expression": "`{\"a\": {\"b\": \"c\"}}`.a.b", + "result": "c" + }, + { + "comment": "literal . identifier bracket-expr", + "expression": "`[0, 1, 2]`[1]", + "result": 1 + } + ] + }, + { + "comment": "Literals", + "given": { + "type": "object" + }, + "cases": [ + { + "comment": "Literal with leading whitespace", + "expression": "` {\"foo\": true}`", + "result": { + "foo": true + } + }, + { + "comment": "Literal with trailing whitespace", + "expression": "`{\"foo\": true} `", + "result": { + "foo": true + } + }, + { + "comment": "Literal on RHS of subexpr not allowed", + "expression": "foo.`\"bar\"`", + "error": "syntax" + }, + { + "comment": "Literal with all valid leading and trailing JSON whitespace", + "expression": "` \t\n\r{\"foo\": true} \t\n\r`", + "result": { + "foo": true + } + }, + { + "comment": "Literal with non-JSON C0 control character U+000C FORM FEED", + "expression": "`0\f`", + "error": "syntax" + } + ] + }, + { + "comment": "Raw String Literals", + "given": {}, + "cases": [ + { + "expression": "'foo'", + "result": "foo" + }, + { + "expression": "' foo '", + "result": " foo " + }, + { + "expression": "'0'", + "result": "0" + }, + { + "expression": "'newline\n'", + "result": "newline\n" + }, + { + "expression": "'\n'", + "result": "\n" + }, + { + "expression": "'✓'", + "result": "✓" + }, + { + "expression": "'𝄞'", + "result": "𝄞" + }, + { + "expression": "' [foo] '", + "result": " [foo] " + }, + { + "expression": "'[foo]'", + "result": "[foo]" + }, + { + "comment": "Do not interpret escaped unicode.", + "expression": "'\\u03a6'", + "result": "\\u03a6" + }, + { + "comment": "Can escape single quote", + "expression": "'foo\\'bar'", + "result": "foo'bar" + }, + { + "comment": "Can escape backslash", + "expression": "'\\\\'", + "result": "\\" + }, + { + "comment": "Backslash not followed by single quote or backslash is treated as any other character", + "expression": "'\\z'", + "result": "\\z" + } + ] + } +] \ No newline at end of file diff --git a/jmespath.test/tests/multiselect.json b/jmespath.test/tests/multiselect.json new file mode 100644 index 0000000..4f46482 --- /dev/null +++ b/jmespath.test/tests/multiselect.json @@ -0,0 +1,398 @@ +[{ + "given": { + "foo": { + "bar": "bar", + "baz": "baz", + "qux": "qux", + "nested": { + "one": { + "a": "first", + "b": "second", + "c": "third" + }, + "two": { + "a": "first", + "b": "second", + "c": "third" + }, + "three": { + "a": "first", + "b": "second", + "c": {"inner": "third"} + } + } + }, + "bar": 1, + "baz": 2, + "qux\"": 3 + }, + "cases": [ + { + "expression": "foo.{bar: bar}", + "result": {"bar": "bar"} + }, + { + "expression": "foo.{\"bar\": bar}", + "result": {"bar": "bar"} + }, + { + "expression": "foo.{\"foo.bar\": bar}", + "result": {"foo.bar": "bar"} + }, + { + "expression": "foo.{bar: bar, baz: baz}", + "result": {"bar": "bar", "baz": "baz"} + }, + { + "expression": "foo.{\"bar\": bar, \"baz\": baz}", + "result": {"bar": "bar", "baz": "baz"} + }, + { + "expression": "{\"baz\": baz, \"qux\\\"\": \"qux\\\"\"}", + "result": {"baz": 2, "qux\"": 3} + }, + { + "expression": "foo.{bar:bar,baz:baz}", + "result": {"bar": "bar", "baz": "baz"} + }, + { + "expression": "foo.{bar: bar,qux: qux}", + "result": {"bar": "bar", "qux": "qux"} + }, + { + "expression": "foo.{bar: bar, noexist: noexist}", + "result": {"bar": "bar", "noexist": null} + }, + { + "expression": "foo.{noexist: noexist, alsonoexist: alsonoexist}", + "result": {"noexist": null, "alsonoexist": null} + }, + { + "expression": "foo.badkey.{nokey: nokey, alsonokey: alsonokey}", + "result": null + }, + { + "expression": "foo.nested.*.{a: a,b: b}", + "result": [{"a": "first", "b": "second"}, + {"a": "first", "b": "second"}, + {"a": "first", "b": "second"}] + }, + { + "expression": "foo.nested.three.{a: a, cinner: c.inner}", + "result": {"a": "first", "cinner": "third"} + }, + { + "expression": "foo.nested.three.{a: a, c: c.inner.bad.key}", + "result": {"a": "first", "c": null} + }, + { + "expression": "foo.{a: nested.one.a, b: nested.two.b}", + "result": {"a": "first", "b": "second"} + }, + { + "expression": "{bar: bar, baz: baz}", + "result": {"bar": 1, "baz": 2} + }, + { + "expression": "{bar: bar}", + "result": {"bar": 1} + }, + { + "expression": "{otherkey: bar}", + "result": {"otherkey": 1} + }, + { + "expression": "{no: no, exist: exist}", + "result": {"no": null, "exist": null} + }, + { + "expression": "foo.[bar]", + "result": ["bar"] + }, + { + "expression": "foo.[bar,baz]", + "result": ["bar", "baz"] + }, + { + "expression": "foo.[bar,qux]", + "result": ["bar", "qux"] + }, + { + "expression": "foo.[bar,noexist]", + "result": ["bar", null] + }, + { + "expression": "foo.[noexist,alsonoexist]", + "result": [null, null] + } + ] +}, { + "given": { + "foo": {"bar": 1, "baz": [2, 3, 4]} + }, + "cases": [ + { + "expression": "foo.{bar:bar,baz:baz}", + "result": {"bar": 1, "baz": [2, 3, 4]} + }, + { + "expression": "foo.[bar,baz[0]]", + "result": [1, 2] + }, + { + "expression": "foo.[bar,baz[1]]", + "result": [1, 3] + }, + { + "expression": "foo.[bar,baz[2]]", + "result": [1, 4] + }, + { + "expression": "foo.[bar,baz[3]]", + "result": [1, null] + }, + { + "expression": "foo.[bar[0],baz[3]]", + "result": [null, null] + } + ] +}, { + "given": { + "foo": {"bar": 1, "baz": 2} + }, + "cases": [ + { + "expression": "foo.{bar: bar, baz: baz}", + "result": {"bar": 1, "baz": 2} + }, + { + "expression": "foo.[bar,baz]", + "result": [1, 2] + } + ] +}, { + "given": { + "foo": { + "bar": {"baz": [{"common": "first", "one": 1}, + {"common": "second", "two": 2}]}, + "ignoreme": 1, + "includeme": true + } + }, + "cases": [ + { + "expression": "foo.{bar: bar.baz[1],includeme: includeme}", + "result": {"bar": {"common": "second", "two": 2}, "includeme": true} + }, + { + "expression": "foo.{\"bar.baz.two\": bar.baz[1].two, includeme: includeme}", + "result": {"bar.baz.two": 2, "includeme": true} + }, + { + "expression": "foo.[includeme, bar.baz[*].common]", + "result": [true, ["first", "second"]] + }, + { + "expression": "foo.[includeme, bar.baz[*].none]", + "result": [true, []] + }, + { + "expression": "foo.[includeme, bar.baz[].common]", + "result": [true, ["first", "second"]] + } + ] +}, { + "given": { + "reservations": [{ + "instances": [ + {"id": "id1", + "name": "first"}, + {"id": "id2", + "name": "second"} + ]}, { + "instances": [ + {"id": "id3", + "name": "third"}, + {"id": "id4", + "name": "fourth"} + ]} + ]}, + "cases": [ + { + "expression": "reservations[*].instances[*].{id: id, name: name}", + "result": [[{"id": "id1", "name": "first"}, {"id": "id2", "name": "second"}], + [{"id": "id3", "name": "third"}, {"id": "id4", "name": "fourth"}]] + }, + { + "expression": "reservations[].instances[].{id: id, name: name}", + "result": [{"id": "id1", "name": "first"}, + {"id": "id2", "name": "second"}, + {"id": "id3", "name": "third"}, + {"id": "id4", "name": "fourth"}] + }, + { + "expression": "reservations[].instances[].[id, name]", + "result": [["id1", "first"], + ["id2", "second"], + ["id3", "third"], + ["id4", "fourth"]] + } + ] +}, +{ + "given": { + "foo": [{ + "bar": [ + { + "qux": 2, + "baz": 1 + }, + { + "qux": 4, + "baz": 3 + } + ] + }, + { + "bar": [ + { + "qux": 6, + "baz": 5 + }, + { + "qux": 8, + "baz": 7 + } + ] + } + ] + }, + "cases": [ + { + "expression": "foo", + "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, + {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] + }, + { + "expression": "foo[]", + "result": [{"bar": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}]}, + {"bar": [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]}] + }, + { + "expression": "foo[].bar", + "result": [[{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}], + [{"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}]] + }, + { + "expression": "foo[].bar[]", + "result": [{"qux": 2, "baz": 1}, {"qux": 4, "baz": 3}, + {"qux": 6, "baz": 5}, {"qux": 8, "baz": 7}] + }, + { + "expression": "foo[].bar[].[baz, qux]", + "result": [[1, 2], [3, 4], [5, 6], [7, 8]] + }, + { + "expression": "foo[].bar[].[baz]", + "result": [[1], [3], [5], [7]] + }, + { + "expression": "foo[].bar[].[baz, qux][]", + "result": [1, 2, 3, 4, 5, 6, 7, 8] + } + ] +}, +{ + "given": { + "foo": { + "baz": [ + { + "bar": "abc" + }, { + "bar": "def" + } + ], + "qux": ["zero"] + } + }, + "cases": [ + { + "expression": "foo.[baz[*].bar, qux[0]]", + "result": [["abc", "def"], "zero"] + } + ] +}, +{ + "given": { + "foo": { + "baz": [ + { + "bar": "a", + "bam": "b", + "boo": "c" + }, { + "bar": "d", + "bam": "e", + "boo": "f" + } + ], + "qux": ["zero"] + } + }, + "cases": [ + { + "expression": "foo.[baz[*].[bar, boo], qux[0]]", + "result": [[["a", "c" ], ["d", "f" ]], "zero"] + } + ] +}, +{ + "given": { + "foo": { + "baz": [ + { + "bar": "a", + "bam": "b", + "boo": "c" + }, { + "bar": "d", + "bam": "e", + "boo": "f" + } + ], + "qux": ["zero"] + } + }, + "cases": [ + { + "expression": "foo.[baz[*].not_there || baz[*].bar, qux[0]]", + "result": [["a", "d"], "zero"] + } + ] +}, +{ + "given": {"type": "object"}, + "cases": [ + { + "comment": "Nested multiselect", + "expression": "[[*],*]", + "result": [null, ["object"]] + } + ] +}, +{ + "given": [], + "cases": [ + { + "comment": "Nested multiselect", + "expression": "[[*]]", + "result": [[]] + }, + { + "comment": "Select on null", + "expression": "missing.{foo: bar}", + "result": null + } + ] +} +] diff --git a/jmespath.test/tests/pipe.json b/jmespath.test/tests/pipe.json new file mode 100644 index 0000000..274be96 --- /dev/null +++ b/jmespath.test/tests/pipe.json @@ -0,0 +1,139 @@ +[{ + "given": { + "foo": { + "bar": { + "baz": "subkey" + }, + "other": { + "baz": "subkey" + }, + "other2": { + "baz": "subkey" + }, + "other3": { + "notbaz": ["a", "b", "c"] + }, + "other4": { + "notbaz": ["a", "b", "c"] + } + } + }, + "cases": [ + { + "expression": "foo.*.baz | [0]", + "result": "subkey" + }, + { + "expression": "foo.*.baz | [1]", + "result": "subkey" + }, + { + "expression": "foo.*.baz | [2]", + "result": "subkey" + }, + { + "expression": "foo.bar.* | [0]", + "result": "subkey" + }, + { + "expression": "foo.*.notbaz | [*]", + "result": [["a", "b", "c"], ["a", "b", "c"]] + }, + { + "expression": "{\"a\": foo.bar, \"b\": foo.other} | *.baz", + "result": ["subkey", "subkey"] + } + ] +}, { + "given": { + "foo": { + "bar": { + "baz": "one" + }, + "other": { + "baz": "two" + }, + "other2": { + "baz": "three" + }, + "other3": { + "notbaz": ["a", "b", "c"] + }, + "other4": { + "notbaz": ["d", "e", "f"] + } + } + }, + "cases": [ + { + "expression": "foo | bar", + "result": {"baz": "one"} + }, + { + "expression": "foo | bar | baz", + "result": "one" + }, + { + "expression": "foo|bar| baz", + "result": "one" + }, + { + "expression": "not_there | [0]", + "result": null + }, + { + "expression": "not_there | [0]", + "result": null + }, + { + "expression": "[foo.bar, foo.other] | [0]", + "result": {"baz": "one"} + }, + { + "expression": "{\"a\": foo.bar, \"b\": foo.other} | a", + "result": {"baz": "one"} + }, + { + "expression": "{\"a\": foo.bar, \"b\": foo.other} | b", + "result": {"baz": "two"} + }, + { + "expression": "foo.bam || foo.bar | baz", + "result": "one" + }, + { + "expression": "foo | not_there || bar", + "result": {"baz": "one"} + } + ] +}, { + "given": { + "foo": [{ + "bar": [{ + "baz": "one" + }, { + "baz": "two" + }] + }, { + "bar": [{ + "baz": "three" + }, { + "baz": "four" + }] + }] + }, + "cases": [ + { + "expression": "foo[*].bar[*] | [0][0]", + "result": {"baz": "one"} + }, + { + "expression": "`null`|[@]", + "result": [ null ] + }, + { + "expression": "`null`|{foo: @}", + "result": {"foo": null } + } + ] +}] diff --git a/jmespath.test/tests/root_node.json b/jmespath.test/tests/root_node.json new file mode 100644 index 0000000..c2c72e9 --- /dev/null +++ b/jmespath.test/tests/root_node.json @@ -0,0 +1,41 @@ +[ + { + "given": { + "first_choice": "WA", + "states": [ + { + "name": "WA", + "cities": [ + "Seattle", + "Bellevue", + "Olympia" + ] + }, + { + "name": "CA", + "cities": [ + "Los Angeles", + "San Francisco" + ] + }, + { + "name": "NY", + "cities": [ + "New York City", + "Albany" + ] + } + ] + }, + "cases": [ + { + "expression": "states[?name==$.first_choice].cities[]", + "result": ["Seattle", "Bellevue", "Olympia"] + }, + { + "expression": "first_choice.\"$\"|states", + "result": null + } + ] + } +] \ No newline at end of file diff --git a/jmespath.test/tests/slice.json b/jmespath.test/tests/slice.json new file mode 100644 index 0000000..8c3a42a --- /dev/null +++ b/jmespath.test/tests/slice.json @@ -0,0 +1,207 @@ +[{ + "given": { + "foo": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "bar": { + "baz": 1 + } + }, + "cases": [ + { + "expression": "bar[0:10]", + "result": null + }, + { + "expression": "foo[0:10:1]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[0:10]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[0:10:]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[0::1]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[0::]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[0:]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[:10:1]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[::1]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[:10:]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[::]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[:]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[1:9]", + "result": [1, 2, 3, 4, 5, 6, 7, 8] + }, + { + "expression": "foo[0:10:2]", + "result": [0, 2, 4, 6, 8] + }, + { + "expression": "foo[5:]", + "result": [5, 6, 7, 8, 9] + }, + { + "expression": "foo[5::2]", + "result": [5, 7, 9] + }, + { + "expression": "foo[::2]", + "result": [0, 2, 4, 6, 8] + }, + { + "expression": "foo[::-1]", + "result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + }, + { + "expression": "foo[1::2]", + "result": [1, 3, 5, 7, 9] + }, + { + "expression": "foo[10:0:-1]", + "result": [9, 8, 7, 6, 5, 4, 3, 2, 1] + }, + { + "expression": "foo[10:5:-1]", + "result": [9, 8, 7, 6] + }, + { + "expression": "foo[8:2:-2]", + "result": [8, 6, 4] + }, + { + "expression": "foo[0:20]", + "result": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + }, + { + "expression": "foo[10:-20:-1]", + "result": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + }, + { + "expression": "foo[10:-20]", + "result": [] + }, + { + "expression": "foo[-4:-1]", + "result": [6, 7, 8] + }, + { + "expression": "foo[:-5:-1]", + "result": [9, 8, 7, 6] + }, + { + "expression": "foo[8:2:0]", + "error": "invalid-value" + }, + { + "expression": "foo[8:2:0:1]", + "error": "syntax" + }, + { + "expression": "foo[8:2&]", + "error": "syntax" + }, + { + "expression": "foo[2:a:3]", + "error": "syntax" + } + ] +}, { + "given": { + "foo": [{"a": 1}, {"a": 2}, {"a": 3}], + "bar": [{"a": {"b": 1}}, {"a": {"b": 2}}, + {"a": {"b": 3}}], + "baz": 50 + }, + "cases": [ + { + "expression": "foo[:2].a", + "result": [1, 2] + }, + { + "expression": "foo[:2].b", + "result": [] + }, + { + "expression": "foo[:2].a.b", + "result": [] + }, + { + "expression": "bar[::-1].a.b", + "result": [3, 2, 1] + }, + { + "expression": "bar[:2].a.b", + "result": [1, 2] + }, + { + "expression": "baz[:2].a", + "result": null + } + ] +}, { + "given": [{"a": 1}, {"a": 2}, {"a": 3}], + "cases": [ + { + "expression": "[:]", + "result": [{"a": 1}, {"a": 2}, {"a": 3}] + }, + { + "expression": "[:2].a", + "result": [1, 2] + }, + { + "expression": "[::-1].a", + "result": [3, 2, 1] + }, + { + "expression": "[:2].b", + "result": [] + } + ] +}, { + "given": null, + "cases": [ + { + "expression": "'e\u0301le\u0301ment'[::-1]", + "result": "tnem\u0301el\u0301e" + }, + { + "expression": "'foo'[2::-1]", + "result": "oof" + }, + { + "expression": "'foo'[2:-1:-1]", + "result": "" + }, + { + "expression": "'foo'[:].length(@)", + "result": 3 + } + ] +}] diff --git a/jmespath.test/tests/syntax.json b/jmespath.test/tests/syntax.json new file mode 100644 index 0000000..538337b --- /dev/null +++ b/jmespath.test/tests/syntax.json @@ -0,0 +1,692 @@ +[{ + "comment": "Dot syntax", + "given": {"type": "object"}, + "cases": [ + { + "expression": "foo.bar", + "result": null + }, + { + "expression": "foo", + "result": null + }, + { + "expression": "foo.1", + "error": "syntax" + }, + { + "expression": "foo.-11", + "error": "syntax" + }, + { + "expression": "foo.", + "error": "syntax" + }, + { + "expression": ".foo", + "error": "syntax" + }, + { + "expression": "foo..bar", + "error": "syntax" + }, + { + "expression": "foo.bar.", + "error": "syntax" + }, + { + "expression": "foo[.]", + "error": "syntax" + } + ] +}, + { + "comment": "Simple token errors", + "given": {"type": "object"}, + "cases": [ + { + "expression": ".", + "error": "syntax" + }, + { + "expression": ":", + "error": "syntax" + }, + { + "expression": ",", + "error": "syntax" + }, + { + "expression": "]", + "error": "syntax" + }, + { + "expression": "[", + "error": "syntax" + }, + { + "expression": "}", + "error": "syntax" + }, + { + "expression": "{", + "error": "syntax" + }, + { + "expression": ")", + "error": "syntax" + }, + { + "expression": "(", + "error": "syntax" + }, + { + "expression": "((&", + "error": "syntax" + }, + { + "expression": "a[", + "error": "syntax" + }, + { + "expression": "a]", + "error": "syntax" + }, + { + "expression": "a][", + "error": "syntax" + }, + { + "expression": "!", + "error": "syntax" + }, + { + "expression": "@=", + "error": "syntax" + }, + { + "expression": "@``", + "error": "syntax" + } + ] + }, + { + "comment": "Boolean syntax errors", + "given": {"type": "object"}, + "cases": [ + { + "expression": "![!(!", + "error": "syntax" + } + ] + }, + { + "comment": "Paren syntax errors", + "given": {}, + "cases": [ + { + "comment": "missing closing paren", + "expression": "(@", + "error": "syntax" + } + ] + }, + { + "comment": "Function syntax errors", + "given": {}, + "cases": [ + { + "comment": "invalid start of function", + "expression": "@(foo)", + "error": "syntax" + }, + { + "comment": "function names cannot be quoted", + "expression": "\"foo\"(bar)", + "error": "syntax" + } + ] + }, + { + "comment": "Wildcard syntax", + "given": {"type": "object"}, + "cases": [ + { + "expression": "*", + "result": ["object"] + }, + { + "expression": "*.*", + "result": [] + }, + { + "expression": "*.foo", + "result": [] + }, + { + "expression": "*[0]", + "result": [] + }, + { + "expression": ".*", + "error": "syntax" + }, + { + "expression": "*foo", + "error": "syntax" + }, + { + "expression": "*0", + "error": "syntax" + }, + { + "expression": "foo[*]bar", + "error": "syntax" + }, + { + "expression": "foo[*]*", + "error": "syntax" + } + ] + }, + { + "comment": "Flatten syntax", + "given": {"type": "object"}, + "cases": [ + { + "expression": "[]", + "result": null + } + ] + }, + { + "comment": "Simple bracket syntax", + "given": {"type": "object"}, + "cases": [ + { + "expression": "[0]", + "result": null + }, + { + "expression": "[*]", + "result": null + }, + { + "expression": "*.[0]", + "error": "syntax" + }, + { + "expression": "*.[\"0\"]", + "result": [[null]] + }, + { + "expression": "[*].bar", + "result": null + }, + { + "expression": "[*][0]", + "result": null + }, + { + "expression": "foo[#]", + "error": "syntax" + }, + { + "comment": "missing rbracket for led wildcard index", + "expression": "led[*", + "error": "syntax" + } + ] + }, + { + "comment": "slice syntax", + "given": {}, + "cases": [ + { + "comment": "slice expected colon or rbracket", + "expression": "[:@]", + "error": "syntax" + }, + { + "comment": "slice has too many colons", + "expression": "[:::]", + "error": "syntax" + }, + { + "comment": "slice expected number", + "expression": "[:@:]", + "error": "syntax" + }, + { + "comment": "slice expected number of colon", + "expression": "[:1@]", + "error": "syntax" + } + ] + }, + { + "comment": "Multi-select list syntax", + "given": {"type": "object"}, + "cases": [ + { + "expression": "foo[0]", + "result": null + }, + { + "comment": "Valid multi-select of a list", + "expression": "foo[0, 1]", + "error": "syntax" + }, + { + "expression": "foo.[0]", + "error": "syntax" + }, + { + "expression": "foo.[*]", + "result": null + }, + { + "comment": "Multi-select of a list with trailing comma", + "expression": "foo[0, ]", + "error": "syntax" + }, + { + "comment": "Multi-select of a list with trailing comma and no close", + "expression": "foo[0,", + "error": "syntax" + }, + { + "comment": "Multi-select of a list with trailing comma and no close", + "expression": "foo.[a", + "error": "syntax" + }, + { + "comment": "Multi-select of a list with extra comma", + "expression": "foo[0,, 1]", + "error": "syntax" + }, + { + "comment": "Multi-select of a list using an identifier index", + "expression": "foo[abc]", + "error": "syntax" + }, + { + "comment": "Multi-select of a list using identifier indices", + "expression": "foo[abc, def]", + "error": "syntax" + }, + { + "comment": "Multi-select of a list using an identifier index", + "expression": "foo[abc, 1]", + "error": "syntax" + }, + { + "comment": "Multi-select of a list using an identifier index with trailing comma", + "expression": "foo[abc, ]", + "error": "syntax" + }, + { + "comment": "Valid multi-select of a hash using an identifier index", + "expression": "foo.[abc]", + "result": null + }, + { + "comment": "Valid multi-select of a hash", + "expression": "foo.[abc, def]", + "result": null + }, + { + "comment": "Multi-select of a hash using a numeric index", + "expression": "foo.[abc, 1]", + "error": "syntax" + }, + { + "comment": "Multi-select of a hash with a trailing comma", + "expression": "foo.[abc, ]", + "error": "syntax" + }, + { + "comment": "Multi-select of a hash with extra commas", + "expression": "foo.[abc,, def]", + "error": "syntax" + }, + { + "comment": "Multi-select of a hash using number indices", + "expression": "foo.[0, 1]", + "error": "syntax" + } + ] + }, + { + "comment": "Multi-select hash syntax", + "given": {"type": "object"}, + "cases": [ + { + "comment": "No key or value", + "expression": "a{}", + "error": "syntax" + }, + { + "comment": "No closing token", + "expression": "a{", + "error": "syntax" + }, + { + "comment": "Not a key value pair", + "expression": "a{foo}", + "error": "syntax" + }, + { + "comment": "Missing value and closing character", + "expression": "a{foo:", + "error": "syntax" + }, + { + "comment": "Missing closing character", + "expression": "a{foo: 0", + "error": "syntax" + }, + { + "comment": "Missing value", + "expression": "a{foo:}", + "error": "syntax" + }, + { + "comment": "Trailing comma and no closing character", + "expression": "a{foo: 0, ", + "error": "syntax" + }, + { + "comment": "Missing value with trailing comma", + "expression": "a{foo: ,}", + "error": "syntax" + }, + { + "comment": "Accessing Array using an identifier", + "expression": "a{foo: bar}", + "error": "syntax" + }, + { + "expression": "a{foo: 0}", + "error": "syntax" + }, + { + "comment": "Missing key-value pair", + "expression": "a.{}", + "error": "syntax" + }, + { + "comment": "Not a key-value pair", + "expression": "a.{foo}", + "error": "syntax" + }, + { + "comment": "Missing value", + "expression": "a.{foo:}", + "error": "syntax" + }, + { + "comment": "Missing value with trailing comma", + "expression": "a.{foo: ,}", + "error": "syntax" + }, + { + "comment": "Valid multi-select hash extraction", + "expression": "a.{foo: bar}", + "result": null + }, + { + "comment": "Valid multi-select hash extraction", + "expression": "a.{foo: bar, baz: bam}", + "result": null + }, + { + "comment": "Trailing comma", + "expression": "a.{foo: bar, }", + "error": "syntax" + }, + { + "comment": "Missing key in second key-value pair", + "expression": "a.{foo: bar, baz}", + "error": "syntax" + }, + { + "comment": "Missing value in second key-value pair", + "expression": "a.{foo: bar, baz:}", + "error": "syntax" + }, + { + "comment": "Trailing comma", + "expression": "a.{foo: bar, baz: bam, }", + "error": "syntax" + }, + { + "comment": "Nested multi select", + "expression": "{\"\\\\\":{\" \":*}}", + "result": {"\\": {" ": ["object"]}} + }, + { + "comment": "Missing closing } after a valid nud", + "expression": "{a: @", + "error": "syntax" + } + ] + }, + { + "comment": "Or expressions", + "given": {"type": "object"}, + "cases": [ + { + "expression": "foo || bar", + "result": null + }, + { + "expression": "foo ||", + "error": "syntax" + }, + { + "expression": "foo.|| bar", + "error": "syntax" + }, + { + "expression": " || foo", + "error": "syntax" + }, + { + "expression": "foo || || foo", + "error": "syntax" + }, + { + "expression": "foo.[a || b]", + "result": null + }, + { + "expression": "foo.[a ||]", + "error": "syntax" + }, + { + "expression": "\"foo", + "error": "syntax" + } + ] + }, + { + "comment": "Filter expressions", + "given": {"type": "object"}, + "cases": [ + { + "expression": "foo[?bar==`\"baz\"`]", + "result": null + }, + { + "expression": "foo[? bar == `\"baz\"` ]", + "result": null + }, + { + "expression": "foo[ ?bar==`\"baz\"`]", + "error": "syntax" + }, + { + "expression": "foo[?bar==]", + "error": "syntax" + }, + { + "expression": "foo[?==]", + "error": "syntax" + }, + { + "expression": "foo[?==bar]", + "error": "syntax" + }, + { + "expression": "foo[?bar==baz?]", + "error": "syntax" + }, + { + "expression": "foo[?a.b.c==d.e.f]", + "result": null + }, + { + "expression": "foo[?bar==`[0, 1, 2]`]", + "result": null + }, + { + "expression": "foo[?bar==`[\"a\", \"b\", \"c\"]`]", + "result": null + }, + { + "comment": "Literal char not escaped", + "expression": "foo[?bar==`[\"foo`bar\"]`]", + "error": "syntax" + }, + { + "comment": "Literal char escaped", + "expression": "foo[?bar==`[\"foo\\`bar\"]`]", + "result": null + }, + { + "comment": "Unknown comparator", + "expression": "foo[?bar<>baz]", + "error": "syntax" + }, + { + "comment": "Unknown comparator", + "expression": "foo[?bar^baz]", + "error": "syntax" + }, + { + "expression": "foo[bar==baz]", + "error": "syntax" + }, + { + "comment": "Quoted identifier in filter expression no spaces", + "expression": "[?\"\\\\\">`\"foo\"`]", + "result": null + }, + { + "comment": "Quoted identifier in filter expression with spaces", + "expression": "[?\"\\\\\" > `\"foo\"`]", + "result": null + } + ] + }, + { + "comment": "Filter expression errors", + "given": {"type": "object"}, + "cases": [ + { + "expression": "bar.`\"anything\"`", + "error": "syntax" + }, + { + "expression": "bar.baz.noexists.`\"literal\"`", + "error": "syntax" + }, + { + "comment": "Literal wildcard projection", + "expression": "foo[*].`\"literal\"`", + "error": "syntax" + }, + { + "expression": "foo[*].name.`\"literal\"`", + "error": "syntax" + }, + { + "expression": "foo[].name.`\"literal\"`", + "error": "syntax" + }, + { + "expression": "foo[].name.`\"literal\"`.`\"subliteral\"`", + "error": "syntax" + }, + { + "comment": "Projecting a literal onto an empty list", + "expression": "foo[*].name.noexist.`\"literal\"`", + "error": "syntax" + }, + { + "expression": "foo[].name.noexist.`\"literal\"`", + "error": "syntax" + }, + { + "expression": "twolen[*].`\"foo\"`", + "error": "syntax" + }, + { + "comment": "Two level projection of a literal", + "expression": "twolen[*].threelen[*].`\"bar\"`", + "error": "syntax" + }, + { + "comment": "Two level flattened projection of a literal", + "expression": "twolen[].threelen[].`\"bar\"`", + "error": "syntax" + }, + { + "comment": "expects closing ]", + "expression": "foo[? @ | @", + "error": "syntax" + } + ] + }, + { + "comment": "Identifiers", + "given": {"type": "object"}, + "cases": [ + { + "expression": "foo", + "result": null + }, + { + "expression": "\"foo\"", + "result": null + }, + { + "expression": "\"\\\\\"", + "result": null + }, + { + "expression": "\"\\u\"", + "error": "syntax" + } + ] + }, + { + "comment": "Combined syntax", + "given": [], + "cases": [ + { + "expression": "*||*|*|*", + "result": null + }, + { + "expression": "*[]||[*]", + "result": [] + }, + { + "expression": "[*.*]", + "result": [null] + } + ] + } +] diff --git a/jmespath.test/tests/unicode.json b/jmespath.test/tests/unicode.json new file mode 100644 index 0000000..9a50207 --- /dev/null +++ b/jmespath.test/tests/unicode.json @@ -0,0 +1,117 @@ +[ + { + "given": {"foo": [{"✓": "✓"}, {"✓": "✗"}]}, + "cases": [ + { + "expression": "foo[].\"✓\"", + "result": ["✓", "✗"] + } + ] + }, + { + "given": {"☯": true}, + "cases": [ + { + "expression": "\"☯\"", + "result": true + } + ] + }, + { + "given": {"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪": true}, + "cases": [ + { + "expression": "\"♪♫•*¨*•.¸¸❤¸¸.•*¨*•♫♪\"", + "result": true + } + ] + }, + { + "given": {"☃": true}, + "cases": [ + { + "expression": "\"☃\"", + "result": true + } + ] + }, + { + "given": {}, + "cases": [ + { + "expression": "'é' == 'e\u0301'", + "result": false + } + ] + }, + { + "given": {}, + "cases": [ + { + "expression": "length('𝌆')", + "result": 1 + }, + { + "expression": "length('é')", + "result": 1 + }, + { + "expression": "length('e\u0301')", + "result": 2 + } + ] + }, + { + "given": {}, + "cases": [ + { + "expression": "reverse('a𝌆b')", + "result": "b𝌆a" + }, + { + "expression": "reverse('aéb')", + "result": "béa" + }, + { + "expression": "reverse('xe\u0303y')", + "result": "y\u0303ex" + } + ] + }, + { + "given": { + "strings": ["é", "e\u0301", "𝌆", "st", "style", "ffi"] + }, + "cases": [ + { + "expression": "sort(strings)", + "result": ["e\u0301", "é", "ffi", "st", "style", "𝌆"] + } + ] + }, + { + "given": { + "graphemeClusters": [ + {"string": "é", "codePoints": 233, "name": "LATIN SMALL LETTER E WITH ACUTE"}, + {"string": "é", "codePoints": [101, 769], "name": "LATIN SMALL LETTER E + COMBINING ACUTE ACCENT"}, + {"string": "𝌆", "codePoints": 119558, "name": "TETRAGRAM FOR CENTRE"}, + {"string": "st", "codePoints": 64262, "name": "LATIN SMALL LIGATURE ST"}, + {"string": "ff", "codePoints": 64256, "name": "LATIN SMALL LIGATURE FF"}, + {"string": "ffi", "codePoints": 64259, "name": "LATIN SMALL LIGATURE FFI"} + ] + }, + "cases": [ + { + "expression": "sort_by(graphemeClusters, &string)", + "result": [ + {"string": "é", "codePoints": [101, 769], "name": "LATIN SMALL LETTER E + COMBINING ACUTE ACCENT"}, + {"string": "é", "codePoints": 233, "name": "LATIN SMALL LETTER E WITH ACUTE"}, + {"string": "ff", "codePoints": 64256, "name": "LATIN SMALL LIGATURE FF"}, + {"string": "ffi", "codePoints": 64259, "name": "LATIN SMALL LIGATURE FFI"}, + {"string": "st", "codePoints": 64262, "name": "LATIN SMALL LIGATURE ST"}, + {"string": "𝌆", "codePoints": 119558, "name": "TETRAGRAM FOR CENTRE"} + ] + } + ] + } +] diff --git a/jmespath.test/tests/wildcard.json b/jmespath.test/tests/wildcard.json new file mode 100644 index 0000000..3bcec30 --- /dev/null +++ b/jmespath.test/tests/wildcard.json @@ -0,0 +1,460 @@ +[{ + "given": { + "foo": { + "bar": { + "baz": "val" + }, + "other": { + "baz": "val" + }, + "other2": { + "baz": "val" + }, + "other3": { + "notbaz": ["a", "b", "c"] + }, + "other4": { + "notbaz": ["a", "b", "c"] + }, + "other5": { + "other": { + "a": 1, + "b": 1, + "c": 1 + } + } + } + }, + "cases": [ + { + "expression": "foo.*.baz", + "result": ["val", "val", "val"] + }, + { + "expression": "foo.bar.*", + "result": ["val"] + }, + { + "expression": "foo.*.notbaz", + "result": [["a", "b", "c"], ["a", "b", "c"]] + }, + { + "expression": "foo.*.notbaz[0]", + "result": ["a", "a"] + }, + { + "expression": "foo.*.notbaz[-1]", + "result": ["c", "c"] + } + ] +}, { + "given": { + "foo": { + "first-1": { + "second-1": "val" + }, + "first-2": { + "second-1": "val" + }, + "first-3": { + "second-1": "val" + } + } + }, + "cases": [ + { + "expression": "foo.*", + "result": [{"second-1": "val"}, {"second-1": "val"}, + {"second-1": "val"}] + }, + { + "expression": "foo.*.*", + "result": [["val"], ["val"], ["val"]] + }, + { + "expression": "foo.*.*.*", + "result": [[], [], []] + }, + { + "expression": "foo.*.*.*.*", + "result": [[], [], []] + } + ] +}, { + "given": { + "foo": { + "bar": "one" + }, + "other": { + "bar": "one" + }, + "nomatch": { + "notbar": "three" + } + }, + "cases": [ + { + "expression": "*.bar", + "result": ["one", "one"] + } + ] +}, { + "given": { + "top1": { + "sub1": {"foo": "one"} + }, + "top2": { + "sub1": {"foo": "one"} + } + }, + "cases": [ + { + "expression": "*", + "result": [{"sub1": {"foo": "one"}}, + {"sub1": {"foo": "one"}}] + }, + { + "expression": "*.sub1", + "result": [{"foo": "one"}, + {"foo": "one"}] + }, + { + "expression": "*.*", + "result": [[{"foo": "one"}], + [{"foo": "one"}]] + }, + { + "expression": "*.*.foo[]", + "result": ["one", "one"] + }, + { + "expression": "*.sub1.foo", + "result": ["one", "one"] + } + ] +}, +{ + "given": + {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]}, + "cases": [ + { + "expression": "foo[*].bar", + "result": ["one", "two", "three"] + }, + { + "expression": "foo[*].notbar", + "result": ["four"] + } + ] +}, +{ + "given": + [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}], + "cases": [ + { + "expression": "[*]", + "result": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}] + }, + { + "expression": "[*].bar", + "result": ["one", "two", "three"] + }, + { + "expression": "[*].notbar", + "result": ["four"] + } + ] +}, +{ + "given": { + "foo": { + "bar": [ + {"baz": ["one", "two", "three"]}, + {"baz": ["four", "five", "six"]}, + {"baz": ["seven", "eight", "nine"]} + ] + } + }, + "cases": [ + { + "expression": "foo.bar[*].baz", + "result": [["one", "two", "three"], ["four", "five", "six"], ["seven", "eight", "nine"]] + }, + { + "expression": "foo.bar[*].baz[0]", + "result": ["one", "four", "seven"] + }, + { + "expression": "foo.bar[*].baz[1]", + "result": ["two", "five", "eight"] + }, + { + "expression": "foo.bar[*].baz[2]", + "result": ["three", "six", "nine"] + }, + { + "expression": "foo.bar[*].baz[3]", + "result": [] + } + ] +}, +{ + "given": { + "foo": { + "bar": [["one", "two"], ["three", "four"]] + } + }, + "cases": [ + { + "expression": "foo.bar[*]", + "result": [["one", "two"], ["three", "four"]] + }, + { + "expression": "foo.bar[0]", + "result": ["one", "two"] + }, + { + "expression": "foo.bar[0][0]", + "result": "one" + }, + { + "expression": "foo.bar[0][0][0]", + "result": null + }, + { + "expression": "foo.bar[0][0][0][0]", + "result": null + }, + { + "expression": "foo[0][0]", + "result": null + } + ] +}, +{ + "given": { + "foo": [ + {"bar": [{"kind": "basic"}, {"kind": "intermediate"}]}, + {"bar": [{"kind": "advanced"}, {"kind": "expert"}]}, + {"bar": "string"} + ] + + }, + "cases": [ + { + "expression": "foo[*].bar[*].kind", + "result": [["basic", "intermediate"], ["advanced", "expert"]] + }, + { + "expression": "foo[*].bar[0].kind", + "result": ["basic", "advanced"] + } + ] +}, +{ + "given": { + "foo": [ + {"bar": {"kind": "basic"}}, + {"bar": {"kind": "intermediate"}}, + {"bar": {"kind": "advanced"}}, + {"bar": {"kind": "expert"}}, + {"bar": "string"} + ] + }, + "cases": [ + { + "expression": "foo[*].bar.kind", + "result": ["basic", "intermediate", "advanced", "expert"] + } + ] +}, +{ + "given": { + "foo": [{"bar": ["one", "two"]}, {"bar": ["three", "four"]}, {"bar": ["five"]}] + }, + "cases": [ + { + "expression": "foo[*].bar[0]", + "result": ["one", "three", "five"] + }, + { + "expression": "foo[*].bar[1]", + "result": ["two", "four"] + }, + { + "expression": "foo[*].bar[2]", + "result": [] + } + ] +}, +{ + "given": { + "foo": [{"bar": []}, {"bar": []}, {"bar": []}] + }, + "cases": [ + { + "expression": "foo[*].bar[0]", + "result": [] + } + ] +}, +{ + "given": { + "foo": [["one", "two"], ["three", "four"], ["five"]] + }, + "cases": [ + { + "expression": "foo[*][0]", + "result": ["one", "three", "five"] + }, + { + "expression": "foo[*][1]", + "result": ["two", "four"] + } + ] +}, +{ + "given": { + "foo": [ + [ + ["one", "two"], ["three", "four"] + ], [ + ["five", "six"], ["seven", "eight"] + ], [ + ["nine"], ["ten"] + ] + ] + }, + "cases": [ + { + "expression": "foo[*][0]", + "result": [["one", "two"], ["five", "six"], ["nine"]] + }, + { + "expression": "foo[*][1]", + "result": [["three", "four"], ["seven", "eight"], ["ten"]] + }, + { + "expression": "foo[*][0][0]", + "result": ["one", "five", "nine"] + }, + { + "expression": "foo[*][1][0]", + "result": ["three", "seven", "ten"] + }, + { + "expression": "foo[*][0][1]", + "result": ["two", "six"] + }, + { + "expression": "foo[*][1][1]", + "result": ["four", "eight"] + }, + { + "expression": "foo[*][2]", + "result": [] + }, + { + "expression": "foo[*][2][2]", + "result": [] + }, + { + "expression": "bar[*]", + "result": null + }, + { + "expression": "bar[*].baz[*]", + "result": null + } + ] +}, +{ + "given": { + "string": "string", + "hash": {"foo": "bar", "bar": "baz"}, + "number": 23, + "nullvalue": null + }, + "cases": [ + { + "expression": "string[*]", + "result": null + }, + { + "expression": "hash[*]", + "result": null + }, + { + "expression": "number[*]", + "result": null + }, + { + "expression": "nullvalue[*]", + "result": null + }, + { + "expression": "string[*].foo", + "result": null + }, + { + "expression": "hash[*].foo", + "result": null + }, + { + "expression": "number[*].foo", + "result": null + }, + { + "expression": "nullvalue[*].foo", + "result": null + }, + { + "expression": "nullvalue[*].foo[*].bar", + "result": null + } + ] +}, +{ + "given": { + "string": "string", + "hash": {"foo": "val", "bar": "val"}, + "number": 23, + "array": [1, 2, 3], + "nullvalue": null + }, + "cases": [ + { + "expression": "string.*", + "result": null + }, + { + "expression": "hash.*", + "result": ["val", "val"] + }, + { + "expression": "number.*", + "result": null + }, + { + "expression": "array.*", + "result": null + }, + { + "expression": "nullvalue.*", + "result": null + } + ] +}, +{ + "given": { + "a": [0, 1, 2], + "b": [0, 1, 2] + }, + "cases": [ + { + "expression": "*[0]", + "result": [0, 0] + } + ] +} +] diff --git a/perf/cases/basic.json b/perf/cases/basic.json deleted file mode 100644 index 7e41f6b..0000000 --- a/perf/cases/basic.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "description": "Basic minimal case", - "given": - {"foo": {"bar": {"baz": "correct"}}}, - "cases": [ - { - "name": "single_expression", - "expression": "foo", - "result": {"bar": {"baz": "correct"}} - }, - { - "name": "single_dot_expression", - "expression": "foo.bar", - "result": {"baz": "correct"} - }, - { - "name": "double_dot_expression", - "expression": "foo.bar.baz", - "result": "correct" - }, - { - "name": "dot_no_match", - "expression": "foo.bar.baz.bad", - "result": null - } - ] -} diff --git a/perf/cases/deep_hierarchy.json b/perf/cases/deep_hierarchy.json deleted file mode 100644 index 309ef94..0000000 --- a/perf/cases/deep_hierarchy.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "Deeply nested dict", - "given": - {"j49": {"j48": {"j47": {"j46": {"j45": {"j44": {"j43": {"j42": {"j41": {"j40": {"j39": {"j38": {"j37": {"j36": {"j35": {"j34": {"j33": {"j32": {"j31": {"j30": {"j29": {"j28": {"j27": {"j26": {"j25": {"j24": {"j23": {"j22": {"j21": {"j20": {"j19": {"j18": {"j17": {"j16": {"j15": {"j14": {"j13": {"j12": {"j11": {"j10": {"j9": {"j8": {"j7": {"j6": {"j5": {"j4": {"j3": {"j2": {"j1": {"j0": {}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}, - "cases": [ - { - "name": "deep_nesting_10", - "expression": "j49.j48.j47.j46.j45.j44.j43.j42.j41.j40", - "result": {"j39": {"j38": {"j37": {"j36": {"j35": {"j34": {"j33": {"j32": {"j31": {"j30": {"j29": {"j28": {"j27": {"j26": {"j25": {"j24": {"j23": {"j22": {"j21": {"j20": {"j19": {"j18": {"j17": {"j16": {"j15": {"j14": {"j13": {"j12": {"j11": {"j10": {"j9": {"j8": {"j7": {"j6": {"j5": {"j4": {"j3": {"j2": {"j1": {"j0": {}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} - }, - { - "name": "deep_nesting_50", - "expression": "j49.j48.j47.j46.j45.j44.j43.j42.j41.j40.j39.j38.j37.j36.j35.j34.j33.j32.j31.j30.j29.j28.j27.j26.j25.j24.j23.j22.j21.j20.j19.j18.j17.j16.j15.j14.j13.j12.j11.j10.j9.j8.j7.j6.j5.j4.j3.j2.j1.j0", - "result": {} - }, - { - "name": "deep_nesting_50_pipe", - "expression": "j49|j48|j47|j46|j45|j44|j43|j42|j41|j40|j39|j38|j37|j36|j35|j34|j33|j32|j31|j30|j29|j28|j27|j26|j25|j24|j23|j22|j21|j20|j19|j18|j17|j16|j15|j14|j13|j12|j11|j10|j9|j8|j7|j6|j5|j4|j3|j2|j1|j0", - "result": {} - }, - { - "name": "deep_nesting_50_index", - "expression": "[49][48][47][46][45][44][43][42][41][40][39][38][37][36][35][34][33][32][31][30][29][28][27][26][25][24][23][22][21][20][19][18][17][16][15][14][13][12][11][10][9][8][7][6][5][4][3][2][1][0]", - "result": null - } - ] -} - diff --git a/perf/cases/deep_projection.json b/perf/cases/deep_projection.json deleted file mode 100644 index e3d85b4..0000000 --- a/perf/cases/deep_projection.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "description": "Deep projections", - "given": - {"a": []}, - "cases": [ - { - "name": "deep_projection_104", - "expression": "a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*].a[*].b[*].c[*].d[*].e[*].f[*].g[*].h[*].i[*].j[*].k[*].l[*].m[*].n[*].o[*].p[*].q[*].r[*].s[*].t[*].u[*].v[*].w[*].x[*].y[*].z[*]", - "result": [] - } - ] -} - diff --git a/perf/cases/functions.json b/perf/cases/functions.json deleted file mode 100644 index 6edc34e..0000000 --- a/perf/cases/functions.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "description": "Deep projections", - "given": - [749, 222, 102, 148, 869, 848, 326, 644, 402, 150, 361, 827, 741, 60, 842, 943, 214, 519, 134, 866, 621, 851, 59, 580, 760, 576, 951, 989, 266, 259, 809, 643, 292, 731, 129, 970, 589, 430, 690, 715, 901, 491, 276, 88, 738, 282, 547, 349, 236, 879, 403, 557, 554, 23, 649, 720, 531, 2, 601, 152, 530, 477, 568, 122, 811, 75, 181, 203, 683, 152, 794, 155, 54, 314, 957, 468, 740, 532, 504, 806, 927, 827, 840, 100, 519, 357, 536, 398, 417, 543, 599, 383, 144, 772, 988, 184, 118, 921, 497, 193, 320, 919, 583, 346, 575, 143, 866, 907, 570, 255, 539, 164, 764, 256, 315, 305, 960, 587, 804, 577, 667, 869, 563, 956, 677, 469, 934, 52, 323, 933, 398, 305, 138, 133, 443, 419, 717, 838, 287, 177, 192, 210, 892, 319, 470, 76, 643, 737, 135, 425, 586, 882, 844, 113, 268, 323, 938, 569, 374, 295, 648, 27, 703, 530, 667, 118, 176, 972, 611, 60, 47, 19, 500, 344, 332, 452, 647, 388, 188, 235, 151, 353, 219, 766, 626, 885, 456, 182, 363, 617, 236, 285, 152, 87, 666, 429, 599, 762, 13, 778, 634, 43, 199, 361, 300, 370, 957, 488, 359, 354, 972, 368, 482, 88, 766, 709, 804, 637, 368, 950, 752, 932, 638, 291, 177, 739, 740, 357, 928, 964, 621, 472, 813, 36, 271, 642, 3, 771, 397, 670, 324, 244, 827, 194, 693, 846, 351, 668, 911, 600, 682, 735, 26, 876, 581, 915, 184, 263, 857, 960, 5, 523, 932, 694, 457, 739, 897, 28, 794, 885, 77, 768, 39, 763, 748, 792, 60, 582, 667, 909, 820, 898, 569, 252, 583, 237, 677, 613, 914, 956, 541, 297, 853, 581, 118, 888, 368, 156, 582, 183], - "cases": [ - { - "name": "min sort with slice", - "expression": "sort(@)[:3]", - "result": [2, 3, 5] - }, - { - "name": "max sort with slice", - "expression": "sort(@)[-3:]", - "result": [972, 988, 989] - } - ] -} - diff --git a/perf/cases/multiwildcard.json b/perf/cases/multiwildcard.json deleted file mode 100644 index d0dbe9d..0000000 --- a/perf/cases/multiwildcard.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "description": "Multiple wildcards in an expression", - "given": { - "foo": [ - {"bar": [{"kind": "basic"}, {"kind": "intermediate"}]}, - {"bar": [{"kind": "advanced"}, {"kind": "expert"}]} - ] - - }, - "cases": [ - { - "name": "multi_wildcard_field", - "expression": "foo[*].bar[*].kind", - "result": [["basic", "intermediate"], ["advanced", "expert"]] - }, - { - "name": "wildcard_with_index", - "expression": "foo[*].bar[0].kind", - "result": ["basic", "advanced"] - } - ] -} diff --git a/perf/cases/wildcardindex.json b/perf/cases/wildcardindex.json deleted file mode 100644 index 8915c4d..0000000 --- a/perf/cases/wildcardindex.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "description": "Multiple wildcards", - "given": - {"foo": [{"bar": "one"}, {"bar": "two"}, {"bar": "three"}, {"notbar": "four"}]}, - "cases": [ - { - "name": "wildcard_with_field_match", - "expression": "foo[*].bar", - "result": ["one", "two", "three"] - }, - { - "name": "wildcard_with_field_match2", - "expression": "foo[*].notbar", - "result": ["four"] - } - ] -} diff --git a/perf/perftest.py b/perf/perftest.py deleted file mode 100644 index 69508e1..0000000 --- a/perf/perftest.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/env python -"""Generate performance diagnostics. - -The purpose of this script is to generate performance diagnostics for -various jmespath expressions to be able to track the performance -over time. The test files are data driven similar to the -compliance tests. -""" -import argparse -import time -import os -import json -import sys -import timeit - -_clock = timeit.default_timer - - -from jmespath.parser import Parser -from jmespath.lexer import Lexer - - -BENCHMARK_FILE = os.path.join( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))), - 'tests', - 'compliance', - 'benchmarks.json') -APPROX_RUN_TIME = 0.5 - - -def run_tests(tests): - times = [] - for test in tests: - given = test['given'] - expression = test['expression'] - result = test['result'] - should_search = test['bench_type'] == 'full' - lex_time = _lex_time(expression) - parse_time = _parse_time(expression) - if should_search: - search_time = _search_time(expression, given) - combined_time = _combined_time(expression, given, result) - else: - search_time = 0 - combined_time = 0 - sys.stdout.write( - "lex_time: %10.5fus, parse_time: %10.5fus, search_time: %10.5fus " - "combined_time: %10.5fus " % (1000000 * lex_time, - 1000000 * parse_time, - 1000000 * search_time, - 1000000 * combined_time)) - sys.stdout.write("name: %s\n" % test['name']) - - -def _lex_time(expression, clock=_clock): - lex = Lexer() - duration = 0 - i = 0 - while True: - i += 1 - start = clock() - list(lex.tokenize(expression)) - end = clock() - total = end - start - duration += total - if duration >= APPROX_RUN_TIME: - break - return duration / i - - -def _search_time(expression, given, clock=_clock): - p = Parser() - parsed = p.parse(expression) - duration = 0 - i = 0 - while True: - i += 1 - start = clock() - parsed.search(given) - end = clock() - total = end - start - duration += total - if duration >= APPROX_RUN_TIME: - break - return duration / i - - -def _parse_time(expression, clock=_clock): - best = float('inf') - p = Parser() - duration = 0 - i = 0 - while True: - i += 1 - p.purge() - start = clock() - p.parse(expression) - end = clock() - total = end - start - duration += total - if duration >= APPROX_RUN_TIME: - break - return duration / i - - -def _combined_time(expression, given, result, clock=_clock): - best = float('inf') - p = Parser() - duration = 0 - i = 0 - while True: - i += 1 - p.purge() - start = clock() - r = p.parse(expression).search(given) - end = clock() - total = end - start - if r != result: - raise RuntimeError("Unexpected result, received: %s, " - "expected: %s" % (r, result)) - duration += total - if duration >= APPROX_RUN_TIME: - break - return duration / i - - -def load_tests(filename): - loaded = [] - with open(filename) as f: - data = json.load(f) - if isinstance(data, list): - for i, d in enumerate(data): - _add_cases(d, loaded, '%s-%s' % (filename, i)) - else: - _add_cases(data, loaded, filename) - return loaded - - -def _add_cases(data, loaded, filename): - for case in data['cases']: - current = { - 'given': data['given'], - 'name': case.get('comment', case['expression']), - 'expression': case['expression'], - 'result': case.get('result'), - 'bench_type': case['bench'], - } - loaded.append(current) - return loaded - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('-f', '--filename', default=BENCHMARK_FILE) - args = parser.parse_args() - collected_tests = [] - collected_tests.extend(load_tests(args.filename)) - run_tests(collected_tests) - - -if __name__ == '__main__': - main() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..cf8ba43 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,236 @@ +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.6.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.3.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "d7052bffba3b2d11e6ad128707b59ae6caa6a42978e566229e15504b27ddc714" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2b9808e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "jmespath" +version = "0.1.0" +description = "" +authors = ["Springcomp "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.8" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.3.4" +pytest-cov = "5" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements-docs.txt b/requirements-docs.txt deleted file mode 100644 index b66d036..0000000 --- a/requirements-docs.txt +++ /dev/null @@ -1,3 +0,0 @@ --r requirements.txt -Sphinx==1.2b3 -guzzle-sphinx-theme==0.3.1 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index bd50c2a..0000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -wheel==0.38.1 -pytest==6.2.5 -pytest-cov==3.0.0 -hypothesis==3.1.0 ; python_version < '3.8' -hypothesis==5.5.4 ; python_version == '3.8' -hypothesis==5.35.4 ; python_version == '3.9' diff --git a/scripts/abnfstress b/scripts/abnfstress deleted file mode 100755 index adc8d75..0000000 --- a/scripts/abnfstress +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python -"""Uses abnfgen to stress test the grammar. - -Make sure abnfgen is installed:: - - brew install abnfgen # On Mac - -""" -import argparse -import tempfile -import subprocess -import jmespath -from jmespath import exceptions - -# Add any additional args you want to control -# abnfgen. -#ARGS = ['-y', '10'] -ARGS = [] - - -GRAMMAR = r""" -expression = sub-expression / index-expression / or-expression / identifier / "*" -expression =/ multi-select-list / multi-select-hash / literal / function-expression / pipe-expression -sub-expression = expression "." ( identifier / - multi-select-list / - multi-select-hash / - function-expression / - "*" ) -or-expression = expression "||" expression -index-expression = expression bracket-specifier / bracket-specifier -multi-select-list = "[" ( expression *( "," expression ) ) "]" -multi-select-hash = "{" ( keyval-expr *( "," keyval-expr ) ) "}" -pipe-expression = expression "|" expression -keyval-expr = identifier ":" expression -bracket-specifier = "[" (number / "*") "]" / "[]" -bracket-specifier =/ "[?" list-filter-expr "]" -list-filter-expr = expression comparator expression -comparator = "<" / "<=" / "==" / ">=" / ">" / "!=" -function-expression = unquoted-string ( - no-args / - one-or-more-args ) -no-args = "(" ")" -one-or-more-args = "(" ( function-arg *( "," function-arg ) ) ")" -function-arg = expression / current-node / expression-type -current-node = "@" -expression-type = "&" expression - -literal = "`" json-value "`" -literal =/ "`" 1*(unescaped-literal / escaped-literal) "`" -unescaped-literal = %x20-21 / ; space ! - %x23-5A / ; # - [ - %x5D-5F / ; ] ^ _ - %x61-7A ; a-z - %x7C-10FFFF ; |}~ ... -escaped-literal = escaped-char / (escape %x60) -number = ["-"]1*digit -digit = %x30-39 -identifier = unquoted-string / quoted-string -unquoted-string = (%x41-5A / %x61-7A / %x5F) *( ; a-zA-Z_ - %x30-39 / ; 0-9 - %x41-5A / ; A-Z - %x5F / ; _ - %x61-7A) ; a-z -quoted-string = quote 1*(unescaped-char / escaped-char) quote -unescaped-char = %x20-21 / %x23-5B / %x5D-10FFFF -escape = %x5C ; Back slash: \ -quote = %x22 ; Double quote: '"' -escaped-char = escape ( - %x22 / ; " quotation mark U+0022 - %x5C / ; \ reverse solidus U+005C - %x2F / ; / solidus U+002F - %x62 / ; b backspace U+0008 - %x66 / ; f form feed U+000C - %x6E / ; n line feed U+000A - %x72 / ; r carriage return U+000D - %x74 / ; t tab U+0009 - %x75 4HEXDIG ) ; uXXXX U+XXXX - -; The ``json-value`` is any valid JSON value with the one exception that the -; ``%x60`` character must be escaped. While it's encouraged that implementations -; use any existing JSON parser for this grammar rule (after handling the escaped -; literal characters), the grammar rule is shown below for completeness:: - -json-value = false / null / true / json-object / json-array / - json-number / json-quoted-string -false = %x66.61.6c.73.65 ; false -null = %x6e.75.6c.6c ; null -true = %x74.72.75.65 ; true -json-quoted-string = %x22 1*(unescaped-literal / escaped-literal) %x22 -begin-array = ws %x5B ws ; [ left square bracket -begin-object = ws %x7B ws ; { left curly bracket -end-array = ws %x5D ws ; ] right square bracket -end-object = ws %x7D ws ; } right curly bracket -name-separator = ws %x3A ws ; : colon -value-separator = ws %x2C ws ; , comma -ws = *(%x20 / ; Space - %x09 / ; Horizontal tab - %x0A / ; Line feed or New line - %x0D ; Carriage return - ) -json-object = begin-object [ member *( value-separator member ) ] end-object -member = quoted-string name-separator json-value -json-array = begin-array [ json-value *( value-separator json-value ) ] end-array -json-number = [ minus ] int [ frac ] [ exp ] -decimal-point = %x2E ; . -digit1-9 = %x31-39 ; 1-9 -e = %x65 / %x45 ; e E -exp = e [ minus / plus ] 1*DIGIT -frac = decimal-point 1*DIGIT -int = zero / ( digit1-9 *DIGIT ) -minus = %x2D ; - -plus = %x2B ; + -zero = %x30 ; 0 -""" - - -def stress(args): - with tempfile.NamedTemporaryFile('w') as f: - f.write(GRAMMAR) - f.flush() - i = 0 - while True: - output = subprocess.check_output(['abnfgen'] + ARGS + [f.name]) - output = output.decode('utf-8') - try: - parsed = jmespath.compile(output) - # abnfgen can generate expressions that contain - # unknown functions. This is ok, because the - # grammar doesn't enforce the known functions. - except exceptions.UnknownFunctionError: - pass - except Exception as e: - print(e) - print(output) - import pdb; pdb.set_trace() - i += 1 - if i % 1000 == 0: - print("num_expressions: %s" % i) - - -def main(): - parser = argparse.ArgumentParser() - args = parser.parse_args() - stress(args) - - -if __name__ == '__main__': - main() diff --git a/scripts/astrender b/scripts/astrender deleted file mode 100755 index 8d839ea..0000000 --- a/scripts/astrender +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python -import os -import sys -import time -import tempfile -import argparse -import shutil -import webbrowser -from subprocess import check_call, call, PIPE - -import jmespath -import jmespath.exceptions - - -def verify_preconditions(): - # Must have a 'dot' executable on the path. - rc = call(['type', 'dot'], stdout=PIPE) - if rc != 0: - sys.stderr.write("Could not find the 'dot' executable. Ensure " - "that graphviz is installed.") - raise RuntimeError("Could not find 'dot'.") - - -def main(): - verify_preconditions() - parser = argparse.ArgumentParser() - parser.add_argument('expression', help='JMESPath expression.') - parser.add_argument('-s', '--save-file', - help='The filename to save the rendered AST. If no ' - 'value is specified, a temporary file will be used ' - 'and immediately deleted after displaying the AST.') - args = parser.parse_args() - try: - parsed = jmespath.compile(args.expression) - except jmespath.exceptions.JMESPathError as e: - sys.stderr.write("Invalid expressions: %s\n" % e) - return 1 - with tempfile.NamedTemporaryFile('w') as f: - contents = parsed._render_dot_file() - f.write(contents) - f.flush() - svg_name = os.path.splitext(f.name)[0] + '.png' - check_call('dot -Tpng %s -o %s' % (f.name, svg_name), shell=True) - webbrowser.open('file://%s' % svg_name) - # Rather than prompt the user to hit enter - # just sleep for as long as we think is reasonable for - # an application to open and display the png. - time.sleep(2) - if args.save_file: - shutil.copy(svg_name, args.save_file) - os.remove(svg_name) - return 0 - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/scripts/bumpversion b/scripts/bumpversion deleted file mode 100755 index 66d8f09..0000000 --- a/scripts/bumpversion +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python -import os -import argparse -from subprocess import check_call - -try: - input = raw_input -except NameError: - pass - - -def open_file(filename, mode='r'): - # relative open a file from the root project dir. - d = os.path.dirname - full_path = os.path.join( - d(d(os.path.abspath(__file__))), - filename) - return open(full_path, mode) - - -def bump_version(version): - update_sphinx_conf(version) - update_setup_py(version) - update_jmespath__init__(version) - show_git_diff() - response = input("Accept changes? ").strip() - if response.lower().startswith('y'): - commit_changes(version) - - -def update_setup_py(version): - with open_file('setup.py') as f: - lines = f.readlines() - for i, line in enumerate(lines): - if line.startswith(' version='): - lines[i] = " version='%s',\n" % version - with open_file('setup.py', 'w') as f: - f.write(''.join(lines)) - - -def update_sphinx_conf(version): - with open_file('docs/conf.py') as f: - lines = f.readlines() - for i, line in enumerate(lines): - if line.startswith('release = '): - lines[i] = "release = '%s'\n" % version - with open_file('docs/conf.py', 'w') as f: - f.write(''.join(lines)) - - -def update_jmespath__init__(version): - with open_file('jmespath/__init__.py') as f: - lines = f.readlines() - for i, line in enumerate(lines): - if line.startswith('__version__ ='): - lines[i] = "__version__ = '%s'\n" % version - with open_file('jmespath/__init__.py', 'w') as f: - f.write(''.join(lines)) - - -def show_git_diff(): - check_call('git diff', shell=True) - - -def commit_changes(version): - check_call('git commit -a -m "Bump version to %s"' % version, shell=True) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('release_version') - args = parser.parse_args() - bump_version(args.release_version) - - -if __name__ == '__main__': - main() diff --git a/scripts/release b/scripts/release deleted file mode 100755 index 4eacad2..0000000 --- a/scripts/release +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python -"""Script to manage releases. - -* Create release branches -* Version bump setup.py, docs/conf.py, etc. -* Tag the releases -* Merge to master -* Push everything up to github. - -The only thing this script doesn't do are the pypi uploads. -You'll need to do those yourself. - - -Usage:: - - scripts/release 1.0.0 - -""" -import argparse -from subprocess import check_call, check_output - -try: - input = raw_input -except NameError: - pass - - -def run(command): - print("Running:", command) - check_call(command, shell=True) - -def verify_starting_state(): - must_be_on_develop_branch() - no_outstanding_changes() - -def must_be_on_develop_branch(): - output = check_output('git rev-parse --abbrev-ref HEAD', - shell=True).strip().decode('utf8') - assert output == 'develop', 'Must be on the develop branch, not %r' % output - -def no_outstanding_changes(): - output = check_output( - 'git status --short', shell=True).strip().decode('utf8') - if output: - raise AssertionError("Can't have any pending changes:\n%s" % output) - - -def create_release_branch(version): - check_call('git checkout -b release-%s' % version, shell=True) - - -def bump_version(version): - check_call('scripts/bumpversion %s' % version, shell=True) - - -# It might be nice to auto populate the merge/tag messages, -# right now it will pop up an editor. - -def merge_to_master(version): - run('git checkout master') - run('git merge --no-ff release-%s' % version) - - -def tag_release(version): - run('git tag -s -m "Tagging %s release" %s' % (version, version)) - - -def merge_to_develop(version): - run('git checkout develop') - run('git merge --no-ff release-%s' % version) - - -def push_changes(): - run('git push origin develop') - run('git push origin master') - run('git push --tags') - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument('release_version') - args = parser.parse_args() - - verify_starting_state() - create_release_branch(args.release_version) - bump_version(args.release_version) - merge_to_master(args.release_version) - tag_release(args.release_version) - merge_to_develop(args.release_version) - push_changes() - print('Completed, run "python setup.py sdist upload" to ' - "upload changes to pypi.") - - -if __name__ == '__main__': - main() diff --git a/scripts/run-tests b/scripts/run-tests index dbcd9a5..8b6237a 100755 --- a/scripts/run-tests +++ b/scripts/run-tests @@ -3,7 +3,7 @@ from subprocess import check_call def main(): - check_call('cd tests && py.test --cov jmespath --cov-report term-missing', shell=True) + check_call('poetry run pytest --cov jmespath --cov-report term-missing', shell=True) if __name__ == '__main__': diff --git a/scripts/sync-tests b/scripts/sync-tests index 2a1302e..656c2e5 100755 --- a/scripts/sync-tests +++ b/scripts/sync-tests @@ -6,7 +6,6 @@ from subprocess import check_call def main(): check_call('git submodule update', shell=True) check_call('cp -r jmespath.test/tests/* tests/compliance/', shell=True) - check_call('git clone https://github.com/jmespath/jmespath.test tests/jmespath.org', shell=True) if __name__ == '__main__': diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 60162cd..0000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[bdist_wheel] -universal = 0 - -[metadata] -license_file = LICENSE diff --git a/setup.py b/setup.py deleted file mode 100644 index b972539..0000000 --- a/setup.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python - -import io - -from setuptools import setup, find_packages - - -setup( - name='jmespath-community', - version='1.1.3', - description='JSON Matching Expressions', - long_description=io.open('README.rst', encoding='utf-8').read(), - author='James Saryerwinnie, Springcomp', - author_email='js@jamesls.com, springcomp@users.noreply.github.com', - url='https://github.com/jmespath-community/jmespath.py', - scripts=['bin/jp.py'], - packages=find_packages(exclude=['tests']), - license='MIT', - python_requires='>=3.7', - classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Natural Language :: English', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - ], -) diff --git a/tests/.gitignore b/tests/.gitignore deleted file mode 100644 index 621066f..0000000 --- a/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -jmespath.org/ \ No newline at end of file diff --git a/tests/jmespath.org b/tests/jmespath.org new file mode 160000 index 0000000..53abcc3 --- /dev/null +++ b/tests/jmespath.org @@ -0,0 +1 @@ +Subproject commit 53abcc37901891cf4308fcd910eab287416c4609 diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 3184836..0000000 --- a/tox.ini +++ /dev/null @@ -1,17 +0,0 @@ -[tox] -envlist = py26,py27,py33,py34,py35,py36,py37,pypy - -[testenv] -commands = nosetests -deps = - nose - mock - -[testenv:py26] -commands = nosetests -deps = - nose - mock - unittest2 - ordereddict - simplejson