diff options
author | cylan <cylan@google.com> | 2018-06-13 11:46:30 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2018-06-13 11:46:30 -0700 |
commit | ab37609af41f79288be1573bb1de4ae68f36879a (patch) | |
tree | 5b580b38af5f26a80363afdf694cb666d95a3d7e | |
parent | 4edb22282e4cfaa9250d3fdb2d2f7be9c0a34fb4 (diff) | |
parent | 87f7b2447ed02e26bfe1fc3db379d8e1ff166572 (diff) | |
download | uritemplates-ab37609af41f79288be1573bb1de4ae68f36879a.tar.gz |
Merge commit '623fce3' into uritemplate 3.0.0. Initial commitcd of uritemplate 3.0.0. am: 43c5a5f39f am: eaee30993c
am: 87f7b2447e
Change-Id: I7ba2da86e9ad760f6239e671b32e662111cea545
34 files changed, 3760 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..47b44f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.pyc +*.swp +docs/_build +bin/ +include/ +lib/ +lib64/ +dist/ +*.egg-info/ +.coverage +htmlcov/ +.tox/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b7c9b48 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,31 @@ +language: python +sudo: false + +matrix: + include: + - python: 2.6 + env: TOXENV=py26 + - python: 2.7 + env: TOXENV=py27 + - python: 3.3 + env: TOXENV=py33 + - python: 3.4 + env: TOXENV=py34 + - python: 3.5 + env: TOXENV=py35 + - env: TOXENV=pep8 + +install: + - pip install tox + +script: + - tox + +notifications: + on_success: change + on_failure: change + irc: + channels: + - "irc.freenode.org#github3.py" + user_notice: true + skip_join: true diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..55848f0 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,4 @@ +Development Lead +---------------- + +- Ian Cordasco <graffatcolmingov@gmail.com> diff --git a/HISTORY.rst b/HISTORY.rst new file mode 100644 index 0000000..f04a309 --- /dev/null +++ b/HISTORY.rst @@ -0,0 +1,68 @@ +Changelog - uritemplate +======================= + +2.0.0 - 2016-08-29 +------------------ + +- Merge uritemplate.py into uritemplate + + +Changelog - uritemplate.py +========================== + +3.0.2 - 2015-08-30 +------------------ + +- Fix meta-package requirements. + +3.0.1 - 2015-08-29 +------------------ + +- Deprecate in favor of uritemplate. This package is now a metapackage that + depends on uritemplate. + +2.0.0 - 2016-08-20 +------------------ + +- Relicense uritemplate.py as Apache 2 and BSD (See + https://github.com/sigmavirus24/uritemplate/pull/23) + +1.0.1 - 2016-08-18 +------------------ + +- Fix some minor packaging problems. + +1.0.0 - 2016-08-17 +------------------ + +- Fix handling of Unicode values on Python 2.6 and 2.7 for urllib.quote. + +- Confirm public stable API via version number. + +0.3.0 - 2013-10-22 +------------------ + +- Add ``#partial`` to partially expand templates and return new instances of + ``URITemplate``. + +0.2.0 - 2013-07-26 +------------------ + +- Refactor the library a bit and add more tests. + +- Backwards incompatible with 0.1.x if using ``URIVariable`` directly from + ``uritemplate.template`` + +0.1.1 - 2013-05-19 +------------------ + +- Add ability to get set of variable names in the current URI + +- If there is no value or default given, simply return an empty string + +- Fix sdist + +0.1.0 - 2013-05-14 +------------------ + +- Initial Release @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to uritemplate.py are +made under the terms of *both* these licenses. diff --git a/LICENSE.APACHE b/LICENSE.APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE.BSD b/LICENSE.BSD new file mode 100644 index 0000000..ee1eb05 --- /dev/null +++ b/LICENSE.BSD @@ -0,0 +1,23 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f545e0f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,12 @@ +include README.rst +include HISTORY.rst +include LICENSE +include LICENSE.APACHE +include LICENSE.BSD +include AUTHORS.rst + +recursive-include docs * +recursive-include tests * + +prune docs/_build +global-exclude *.py[cdo] __pycache__ *.so *.pyd diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..b2e2d6c --- /dev/null +++ b/METADATA @@ -0,0 +1,16 @@ +name: "uritemplate" +description: + "Simple python library to deal with URI Templates." + +third_party { + url { + type: HOMEPAGE + value: "https://pypi.org/project/uritemplate" + } + url { + type: GIT + value: "https://github.com/python-hyper/uritemplate" + } + version: "3.0.0" + last_upgrade_date { year: 2018 month: 6 day: 4 } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE2 diff --git a/MODULE_LICENSE_BSD b/MODULE_LICENSE_BSD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_BSD @@ -0,0 +1,230 @@ +====== Start LICENSE.BSD ====== +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +====== End LICENSE.BSD ====== + +====== Start LICENSE.APACHE ====== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +====== End LICENSE.APACHE ====== diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..2780d7b --- /dev/null +++ b/README.rst @@ -0,0 +1,64 @@ +uritemplate +=========== + +Documentation_ -- GitHub_ -- BitBucket_ -- Travis-CI_ + +Simple python library to deal with `URI Templates`_. The API looks like + +.. code-block:: python + + from uritemplate import URITemplate, expand + + # NOTE: URI params must be strings not integers + + gist_uri = 'https://api.github.com/users/sigmavirus24/gists{/gist_id}' + t = URITemplate(gist_uri) + print(t.expand(gist_id='123456')) + # => https://api.github.com/users/sigmavirus24/gists/123456 + + # or + print(expand(gist_uri, gist_id='123456')) + + # also + t.expand({'gist_id': '123456'}) + print(expand(gist_uri, {'gist_id': '123456'})) + +Where it might be useful to have a class + +.. code-block:: python + + import requests + + class GitHubUser(object): + url = URITemplate('https://api.github.com/user{/login}') + def __init__(self, name): + self.api_url = url.expand(login=name) + response = requests.get(self.api_url) + if response.status_code == 200: + self.__dict__.update(response.json()) + +When the module containing this class is loaded, ``GitHubUser.url`` is +evaluated and so the template is created once. It's often hard to notice in +Python, but object creation can consume a great deal of time and so can the +``re`` module which uritemplate relies on. Constructing the object once should +reduce the amount of time your code takes to run. + +Installing +---------- + +:: + + pip install uritemplate.py + +License +------- + +Modified BSD license_ + + +.. _Documentation: http://uritemplate.rtfd.org/ +.. _GitHub: https://github.com/sigmavirus24/uritemplate +.. _BitBucket: https://bitbucket.org/icordasc/uritemplate +.. _Travis-CI: https://travis-ci.org/sigmavirus24/uritemplate +.. _URI Templates: http://tools.ietf.org/html/rfc6570 +.. _license: https://github.com/sigmavirus24/uritemplate/blob/master/LICENSE diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..bf49b54 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,130 @@ +# 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) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make <target>' where <target> 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 " 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/Raclette.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Raclette.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/Raclette" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Raclette" + @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." + +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 new file mode 100644 index 0000000..86292ea --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +# +# Requests documentation build configuration file, created by +# sphinx-quickstart on Sun Feb 13 23:54:25 2011. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# This environment variable makes decorators not decorate functions, so their +# signatures in the generated documentation are still correct +os.environ['GENERATING_DOCUMENTATION'] = "uritemplate" + +# 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('..')) +import uritemplate + +# -- 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'] + +# 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'uritemplate' +copyright = u'2013 - Ian Cordasco' + +# 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 = uritemplate.__version__ +# The full version, including alpha/beta/rc tags. +release = version + +# 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 + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +# pygments_style = 'flask_theme_support.FlaskyStyle' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'nature' + +# 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 = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# 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'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = False + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'uritemplate_doc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'uritemplate.tex', u'uritemplate Documentation', + u'Ian Cordasco', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'uritemplate', u'uritemplate Documentation', + [u'Ian Cordasco'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'uritemplate', u'uritemplate Documentation', u'Ian Cordasco', + 'uritemplate', 'Library to expand RFC6570 templated URIs', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +texinfo_appendices = [] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..35776f0 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,81 @@ +uritemplate +=========== + +Release v\ |version|. + +Examples +-------- + +This first example shows how simple the API can be when using for a one-off +item in a script or elsewhere. + +.. code-block:: python + + from requests import get + from uritemplate import expand + + uri = 'https://api.github.com{/user}' + + user = get(expand(uri, user='sigmavirus24')).json() + +This second example shows how using the class will save you time for template +parsing and object creation. Making the template once means the URI is parsed +once which decreases the number of :class:`URITemplate +<uritemplate.URITemplate>` objects created and usage of the ``re`` module. +This means that as soon as the file is parsed, the ``User.github_url`` and +``Repository.github_url`` variables are made once and only once. They're then +usable in every instance of those classes. + +.. code-block:: python + + from uritemplate import URITemplate + + class User(object): + github_url = URITemplate('https://api.github.com{/user}') + def __init__(self, name): + self.uri = self.github_url.expand({'user': name}) + self.name = name + + class Repository(object): + github_url = URITemplate('https://api.github.com{/user}{/repo}') + def __init__(self, name): + self.uri = self.github_url.expand( + dict(zip(['user', 'repo'], name.split('/'))) + ) + self.name = name + +API +--- + +.. module:: uritemplate + +.. autofunction:: uritemplate.expand + +.. autofunction:: uritemplate.partial + +.. autofunction:: uritemplate.variables + +.. autoclass:: uritemplate.URITemplate + :members: + +Implementation Details +---------------------- + +Classes, their methods, and functions in this section are not part of the API +and as such are not meant to be used by users of ``uritemplate.py``. These are +documented here purely for reference as they are inadvertently exposed via the +public API. + +For example:: + + t = URITemplate('https://api.github.com/users{/user}') + t.variables + # => [URIVariable(/user)] + +Users can interact with :class:`URIVariable` objects as they see fit, but +their API may change and are not guaranteed to be consistent across versions. +Code relying on methods defined on :class:`URIVariable` and other classes, +methods, and functions in this section may break in future releases. + +.. autoclass:: uritemplate.template.URIVariable + :members: expand diff --git a/old/uritemplate.py/uritemplatepy-setup.py b/old/uritemplate.py/uritemplatepy-setup.py new file mode 100644 index 0000000..430d519 --- /dev/null +++ b/old/uritemplate.py/uritemplatepy-setup.py @@ -0,0 +1,31 @@ +from setuptools import setup + +setup( + name="uritemplate.py", + version="3.0.2", + description="URI templates", + long_description="\n\n".join([open("README.rst").read(), + open("HISTORY.rst").read()]), + license="BSD 3-Clause License or Apache License, Version 2.0", + author="Ian Cordasco", + author_email="graffatcolmingov@gmail.com", + url="https://uritemplate.readthedocs.org", + install_requires=["uritemplate>=2.0"], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'License :: OSI Approved', + 'License :: OSI Approved :: BSD License', + 'License :: OSI Approved :: Apache Software License', + 'Intended Audience :: Developers', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: Implementation :: CPython', + ], +) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2a9acf1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal = 1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..59f03ba --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +from setuptools import setup + +from uritemplate import __version__ + +packages = [ + 'uritemplate' +] + +setup( + name="uritemplate", + version=__version__, + description='URI templates', + long_description="\n\n".join([open("README.rst").read(), + open("HISTORY.rst").read()]), + license="BSD 3-Clause License or Apache License, Version 2.0", + author="Ian Cordasco", + author_email="graffatcolmingov@gmail.com", + url="https://uritemplate.readthedocs.org", + packages=packages, + package_data={'': ['LICENSE', 'AUTHORS.rst']}, + include_package_data=True, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'License :: OSI Approved', + 'License :: OSI Approved :: BSD License', + 'License :: OSI Approved :: Apache Software License', + 'Intended Audience :: Developers', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: Implementation :: CPython', + ], +) diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md new file mode 100644 index 0000000..11dcddb --- /dev/null +++ b/tests/fixtures/README.md @@ -0,0 +1,91 @@ +Theses test are borrowed from https://github.com/uri-templates/uritemplate-test +at commit: fdd5d611a849b922c2ff40fc3997fd265dd14c02 +URI Template Tests +================== + +This is a set of tests for implementations of +[RFC6570](http://tools.ietf.org/html/rfc6570) - URI Template. It is designed +to be reused by any implementation, to improve interoperability and +implementation quality. + +If your project uses Git for version control, you can make uritemplate-tests into a [submodule](http://help.github.com/submodules/). + +Test Format +----------- + +Each test file is a [JSON](http://tools.ietf.org/html/RFC6627) document +containing an object whose properties are groups of related tests. +Alternatively, all tests are available in XML as well, with the XML files +being generated by transform-json-tests.xslt which uses json2xml.xslt as a +general-purpose JSON-to-XML parsing library. + +Each group, in turn, is an object with three children: + +* level - the level of the tests covered, as per the RFC (optional; if absent, + assume level 4). +* variables - an object representing the variables that are available to the + tests in the suite +* testcases - a list of testcases, where each case is a two-member list, the + first being the template, the second being the result of expanding the + template with the provided variables. + +Note that the result string can be a few different things: + +* string - if the second member is a string, the result of expansion is + expected to match it, character-for-character. +* list - if the second member is a list of strings, the result of expansion + is expected to match one of them; this allows for templates that can + expand into different, equally-acceptable URIs. +* false - if the second member is boolean false, expansion is expected to + fail (i.e., the template was invalid). + +For example: + + { + "Level 1 Examples" : + { + "level": 1, + "variables": { + "var" : "value", + "hello" : "Hello World!" + }, + "testcases" : [ + ["{var}", "value"], + ["{hello}", "Hello%20World%21"] + ] + } + } + + +Tests Included +-------------- + +The following test files are included: + +* spec-examples.json - The complete set of example templates from the RFC +* spec-examples-by-section.json - The examples, section by section +* extended-tests.json - more complex test cases +* negative-tests.json - invalid templates + +For all these test files, XML versions with the names *.xml can be +generated with the transform-json-tests.xslt XSLT stylesheet. The XSLT +contains the names of the above test files as a parameter, and can be +started with any XML as input (i.e., the XML input is ignored). + +License +------- + + Copyright 2011-2012 The Authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/tests/fixtures/extended-tests.json b/tests/fixtures/extended-tests.json new file mode 100644 index 0000000..fd69744 --- /dev/null +++ b/tests/fixtures/extended-tests.json @@ -0,0 +1,118 @@ +{ + "Additional Examples 1":{ + "level":4, + "variables":{ + "id" : "person", + "token" : "12345", + "fields" : ["id", "name", "picture"], + "format" : "json", + "q" : "URI Templates", + "page" : "5", + "lang" : "en", + "geocode" : ["37.76","-122.427"], + "first_name" : "John", + "last.name" : "Doe", + "Some%20Thing" : "foo", + "number" : 6, + "long" : 37.76, + "lat" : -122.427, + "group_id" : "12345", + "query" : "PREFIX dc: <http://purl.org/dc/elements/1.1/> SELECT ?book ?who WHERE { ?book dc:creator ?who }", + "uri" : "http://example.org/?uri=http%3A%2F%2Fexample.org%2F", + "word" : "drücken", + "Stra%C3%9Fe" : "Grüner Weg", + "random" : "šö䟜ñꀣ¥‡ÑÒÓÔÕÖ×ØÙÚà áâãäåæçÿ", + "assoc_special_chars" : + { "šö䟜ñꀣ¥‡ÑÒÓÔÕ" : "Ö×ØÙÚà áâãäåæçÿ" } + }, + "testcases":[ + + [ "{/id*}" , "/person" ], + [ "{/id*}{?fields,first_name,last.name,token}" , [ + "/person?fields=id,name,picture&first_name=John&last.name=Doe&token=12345", + "/person?fields=id,picture,name&first_name=John&last.name=Doe&token=12345", + "/person?fields=picture,name,id&first_name=John&last.name=Doe&token=12345", + "/person?fields=picture,id,name&first_name=John&last.name=Doe&token=12345", + "/person?fields=name,picture,id&first_name=John&last.name=Doe&token=12345", + "/person?fields=name,id,picture&first_name=John&last.name=Doe&token=12345"] + ], + ["/search.{format}{?q,geocode,lang,locale,page,result_type}", + [ "/search.json?q=URI%20Templates&geocode=37.76,-122.427&lang=en&page=5", + "/search.json?q=URI%20Templates&geocode=-122.427,37.76&lang=en&page=5"] + ], + ["/test{/Some%20Thing}", "/test/foo" ], + ["/set{?number}", "/set?number=6"], + ["/loc{?long,lat}" , "/loc?long=37.76&lat=-122.427"], + ["/base{/group_id,first_name}/pages{/page,lang}{?format,q}","/base/12345/John/pages/5/en?format=json&q=URI%20Templates"], + ["/sparql{?query}", "/sparql?query=PREFIX%20dc%3A%20%3Chttp%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%3E%20SELECT%20%3Fbook%20%3Fwho%20WHERE%20%7B%20%3Fbook%20dc%3Acreator%20%3Fwho%20%7D"], + ["/go{?uri}", "/go?uri=http%3A%2F%2Fexample.org%2F%3Furi%3Dhttp%253A%252F%252Fexample.org%252F"], + ["/service{?word}", "/service?word=dr%C3%BCcken"], + ["/lookup{?Stra%C3%9Fe}", "/lookup?Stra%C3%9Fe=Gr%C3%BCner%20Weg"], + ["{random}" , "%C5%A1%C3%B6%C3%A4%C5%B8%C5%93%C3%B1%C3%AA%E2%82%AC%C2%A3%C2%A5%E2%80%A1%C3%91%C3%92%C3%93%C3%94%C3%95%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%BF"], + ["{?assoc_special_chars*}", "?%C5%A1%C3%B6%C3%A4%C5%B8%C5%93%C3%B1%C3%AA%E2%82%AC%C2%A3%C2%A5%E2%80%A1%C3%91%C3%92%C3%93%C3%94%C3%95=%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%BF"] + ] + }, + "Additional Examples 2":{ + "level":4, + "variables":{ + "id" : ["person","albums"], + "token" : "12345", + "fields" : ["id", "name", "picture"], + "format" : "atom", + "q" : "URI Templates", + "page" : "10", + "start" : "5", + "lang" : "en", + "geocode" : ["37.76","-122.427"] + }, + "testcases":[ + + [ "{/id*}" , ["/person/albums","/albums/person"] ], + [ "{/id*}{?fields,token}" , [ + "/person/albums?fields=id,name,picture&token=12345", + "/person/albums?fields=id,picture,name&token=12345", + "/person/albums?fields=picture,name,id&token=12345", + "/person/albums?fields=picture,id,name&token=12345", + "/person/albums?fields=name,picture,id&token=12345", + "/person/albums?fields=name,id,picture&token=12345", + "/albums/person?fields=id,name,picture&token=12345", + "/albums/person?fields=id,picture,name&token=12345", + "/albums/person?fields=picture,name,id&token=12345", + "/albums/person?fields=picture,id,name&token=12345", + "/albums/person?fields=name,picture,id&token=12345", + "/albums/person?fields=name,id,picture&token=12345"] + ] + ] + }, + "Additional Examples 3: Empty Variables":{ + "variables" : { + "empty_list" : [], + "empty_assoc" : {} + }, + "testcases":[ + [ "{/empty_list}", [ "" ] ], + [ "{/empty_list*}", [ "" ] ], + [ "{?empty_list}", [ ""] ], + [ "{?empty_list*}", [ "" ] ], + [ "{?empty_assoc}", [ "" ] ], + [ "{?empty_assoc*}", [ "" ] ] + ] + }, + "Additional Examples 4: Numeric Keys":{ + "variables" : { + "42" : "The Answer to the Ultimate Question of Life, the Universe, and Everything", + "1337" : ["leet", "as","it", "can","be"], + "german" : { + "11": "elf", + "12": "zwölf" + } + }, + "testcases":[ + [ "{42}", "The%20Answer%20to%20the%20Ultimate%20Question%20of%20Life%2C%20the%20Universe%2C%20and%20Everything"], + [ "{?42}", "?42=The%20Answer%20to%20the%20Ultimate%20Question%20of%20Life%2C%20the%20Universe%2C%20and%20Everything"], + [ "{1337}", "leet,as,it,can,be"], + [ "{?1337*}", "?1337=leet&1337=as&1337=it&1337=can&1337=be"], + [ "{?german*}", [ "?11=elf&12=zw%C3%B6lf", "?12=zw%C3%B6lf&11=elf"] ] + ] + } +} diff --git a/tests/fixtures/json2xml.xslt b/tests/fixtures/json2xml.xslt new file mode 100644 index 0000000..59b3548 --- /dev/null +++ b/tests/fixtures/json2xml.xslt @@ -0,0 +1,201 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Downloaded on 12/6/2012 from http://www.gerixsoft.com/blog/xslt/json2xml --> + +<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + + <xsl:template match="json"> + <xsl:copy> + <xsl:copy-of select="@*"/> + <xsl:call-template name="json2xml"> + <xsl:with-param name="text" select="."/> + </xsl:call-template> + </xsl:copy> + </xsl:template> + + <xsl:template name="json2xml"> + <xsl:param name="text"/> + <xsl:variable name="mode0"> + <xsl:variable name="regexps" select="'//(.*?)\n', '/\*(.*?)\*/', '(''|")(([^\\]|\\[\\"''/btnvfr])*?)\3', '(-?\d+(\.\d+([eE][+-]?\d+)?|[eE][+-]?\d+))', '(-?[1-9]\d*)', '(-?0[0-7]+)', '(-?0x[0-9a-fA-F]+)', '([:,\{\}\[\]])', '(true|false)', '(null)'"/> + <xsl:analyze-string select="$text" regex="{string-join($regexps,'|')}" flags="s"> + <xsl:matching-substring> + <xsl:choose> + <!-- single line comment --> + <xsl:when test="regex-group(1)"> + <xsl:comment> + <xsl:value-of select="regex-group(1)"/> + </xsl:comment> + <xsl:text> </xsl:text> + </xsl:when> + <!-- multi line comment --> + <xsl:when test="regex-group(2)"> + <xsl:comment> + <xsl:value-of select="regex-group(2)"/> + </xsl:comment> + </xsl:when> + <!-- string --> + <xsl:when test="regex-group(3)"> + <string> + <xsl:analyze-string select="regex-group(4)" regex="\\([\\"'/btnvfr])" flags="s"> + <xsl:matching-substring> + <xsl:variable name="s" select="regex-group(1)"/> + <xsl:choose> + <xsl:when test="$s=('\', '"', '''', '/')"> + <xsl:value-of select="regex-group(1)"/> + </xsl:when> + <xsl:when test="$s='b'"> + <!--xsl:text></xsl:text--> + <xsl:message select="'escape sequense \b is not supported by XML'"/> + <xsl:text>\b</xsl:text> + </xsl:when> + <xsl:when test="$s='t'"> + <xsl:text>	</xsl:text> + </xsl:when> + <xsl:when test="$s='n'"> + <xsl:text> </xsl:text> + </xsl:when> + <xsl:when test="$s='v'"> + <!--xsl:text></xsl:text--> + <xsl:message select="'escape sequence \v is not supported by XML'"/> + <xsl:text>\v</xsl:text> + </xsl:when> + <xsl:when test="$s='f'"> + <!--xsl:text></xsl:text--> + <xsl:message select="'escape sequence \f is not supported by XML'"/> + <xsl:text>\f</xsl:text> + </xsl:when> + <xsl:when test="$s='r'"> + <xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes" select="'internal error'"/> + </xsl:otherwise> + </xsl:choose> + </xsl:matching-substring> + <xsl:non-matching-substring> + <xsl:value-of select="."/> + </xsl:non-matching-substring> + </xsl:analyze-string> + </string> + </xsl:when> + <!-- double --> + <xsl:when test="regex-group(6)"> + <double> + <xsl:value-of select="regex-group(6)"/> + </double> + </xsl:when> + <!-- integer --> + <xsl:when test="regex-group(9)"> + <integer> + <xsl:value-of select="regex-group(9)"/> + </integer> + </xsl:when> + <!-- octal --> + <xsl:when test="regex-group(10)"> + <integer> + <xsl:value-of xmlns:Integer="java:java.lang.Integer" select="Integer:parseInt(regex-group(10), 8)"/> + </integer> + </xsl:when> + <!-- hex --> + <xsl:when test="regex-group(11)"> + <integer> + <xsl:value-of xmlns:Integer="java:java.lang.Integer" select="Integer:parseInt(replace(regex-group(11), '0x', ''), 16)"/> + </integer> + </xsl:when> + <!-- symbol --> + <xsl:when test="regex-group(12)"> + <symbol> + <xsl:value-of select="regex-group(12)"/> + </symbol> + </xsl:when> + <!-- boolean --> + <xsl:when test="regex-group(13)"> + <boolean> + <xsl:value-of select="regex-group(13)"/> + </boolean> + </xsl:when> + <!-- null --> + <xsl:when test="regex-group(14)"> + <null /> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes" select="'internal error'"/> + </xsl:otherwise> + </xsl:choose> + </xsl:matching-substring> + <xsl:non-matching-substring> + <xsl:if test="normalize-space()!=''"> + <xsl:message select="concat('unknown token: ', .)"/> + <xsl:value-of select="."/> + </xsl:if> + </xsl:non-matching-substring> + </xsl:analyze-string> + </xsl:variable> + <xsl:variable name="mode1"> + <xsl:apply-templates mode="json2xml1" select="$mode0/node()[1]"/> + </xsl:variable> + <xsl:variable name="mode2"> + <xsl:apply-templates mode="json2xml2" select="$mode1"/> + </xsl:variable> + <xsl:variable name="mode3"> + <xsl:apply-templates mode="json2xml3" select="$mode2"/> + </xsl:variable> + <xsl:copy-of select="$mode3"/> <!-- change $mode3 to $mode[0-2] for easy debug --> + </xsl:template> + + <!-- json2xml1 mode: group content between {} and [] into object and array elements --> + + <xsl:template mode="json2xml1" match="node()" priority="-9"> + <xsl:copy-of select="."/> + <xsl:apply-templates mode="json2xml1" select="following-sibling::node()[1]"/> + </xsl:template> + + <xsl:template mode="json2xml1" match="symbol[.=('}',']')]"/> + + <xsl:template mode="json2xml1" match="symbol[.=('{','[')]"> + <xsl:element name="{if (.='{') then 'object' else 'array'}"> + <xsl:apply-templates mode="json2xml1" select="following-sibling::node()[1]"/> + </xsl:element> + <xsl:variable name="level" select="count(preceding-sibling::symbol[.=('{','[')])-count(preceding-sibling::symbol[.=('}',']')])+1"/> + <xsl:variable name="ender" + select="following-sibling::symbol[.=('}',']') and count(preceding-sibling::symbol[.=('{','[')])-count(preceding-sibling::symbol[.=('}',']')])=$level][1]"/> + <xsl:apply-templates mode="json2xml1" select="$ender/following-sibling::node()[1]"/> + </xsl:template> + + <!-- json2xml2 mode: group <string>:<string|integer|double|object|array> into field element --> + + <xsl:template priority="-9" mode="json2xml2" match="@*|node()"> + <xsl:copy> + <xsl:apply-templates mode="json2xml2" select="@*|node()"/> + </xsl:copy> + </xsl:template> + + <xsl:template mode="json2xml2" + match="string[following-sibling::*[1]/self::symbol[.=':'] and following-sibling::*[2]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null)]"/> + + <xsl:template mode="json2xml2" + match="symbol[.=':'][preceding-sibling::*[1]/self::string and following-sibling::*[1]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null)]"> + <field name="{preceding-sibling::*[1]}"> + <xsl:for-each select="following-sibling::*[1]"> + <xsl:copy> + <xsl:apply-templates mode="json2xml2" select="@*|node()"/> + </xsl:copy> + </xsl:for-each> + </field> + </xsl:template> + + <xsl:template mode="json2xml2" + match="*[self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null][preceding-sibling::*[2]/self::string and preceding-sibling::*[1]/self::symbol[.=':']]"/> + + <!-- json2xml3 mode: drop comma between consecutive field and object elements --> + + <xsl:template priority="-9" mode="json2xml3" match="@*|node()"> + <xsl:copy> + <xsl:apply-templates mode="json2xml3" select="@*|node()"/> + </xsl:copy> + </xsl:template> + + <xsl:template mode="json2xml3" match="object/symbol[.=','][preceding-sibling::*[1]/self::field and following-sibling::*[1]/self::field]"/> + + <xsl:template mode="json2xml3" match="array/symbol[.=','][preceding-sibling::*[1]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null) and following-sibling::*[1]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null)]"/> + +</xsl:stylesheet>
\ No newline at end of file diff --git a/tests/fixtures/negative-tests.json b/tests/fixtures/negative-tests.json new file mode 100644 index 0000000..552a6bf --- /dev/null +++ b/tests/fixtures/negative-tests.json @@ -0,0 +1,57 @@ +{ + "Failure Tests":{ + "level":4, + "variables":{ + "id" : "thing", + "var" : "value", + "hello" : "Hello World!", + "with space" : "fail", + " leading_space" : "Hi!", + "trailing_space " : "Bye!", + "empty" : "", + "path" : "/foo/bar", + "x" : "1024", + "y" : "768", + "list" : ["red", "green", "blue"], + "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, + "example" : "red", + "searchTerms" : "uri templates", + "~thing" : "some-user", + "default-graph-uri" : ["http://www.example/book/","http://www.example/papers/"], + "query" : "PREFIX dc: <http://purl.org/dc/elements/1.1/> SELECT ?book ?who WHERE { ?book dc:creator ?who }" + + }, + "testcases":[ + [ "{/id*", false ], + [ "/id*}", false ], + [ "{/?id}", false ], + [ "{var:prefix}", false ], + [ "{hello:2*}", false ] , + [ "{??hello}", false ] , + [ "{!hello}", false ] , + [ "{with space}", false], + [ "{ leading_space}", false], + [ "{trailing_space }", false], + [ "{=path}", false ] , + [ "{$var}", false ], + [ "{|var*}", false ], + [ "{*keys?}", false ], + [ "{?empty=default,var}", false ], + [ "{var}{-prefix|/-/|var}" , false ], + [ "?q={searchTerms}&c={example:color?}" , false ], + [ "x{?empty|foo=none}" , false ], + [ "/h{#hello+}" , false ], + [ "/h#{hello+}" , false ], + [ "{keys:1}", false ], + [ "{+keys:1}", false ], + [ "{;keys:1*}", false ], + [ "?{-join|&|var,list}" , false ], + [ "/people/{~thing}", false], + [ "/{default-graph-uri}", false ], + [ "/sparql{?query,default-graph-uri}", false ], + [ "/sparql{?query){&default-graph-uri*}", false ], + [ "/resolution{?x, y}" , false ] + + ] + } +}
\ No newline at end of file diff --git a/tests/fixtures/spec-examples-by-section.json b/tests/fixtures/spec-examples-by-section.json new file mode 100644 index 0000000..5aef182 --- /dev/null +++ b/tests/fixtures/spec-examples-by-section.json @@ -0,0 +1,439 @@ +{ + "3.2.1 Variable Expansion" : + { + "variables": { + "count" : ["one", "two", "three"], + "dom" : ["example", "com"], + "dub" : "me/too", + "hello" : "Hello World!", + "half" : "50%", + "var" : "value", + "who" : "fred", + "base" : "http://example.com/home/", + "path" : "/foo/bar", + "list" : ["red", "green", "blue"], + "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, + "v" : "6", + "x" : "1024", + "y" : "768", + "empty" : "", + "empty_keys" : [], + "undef" : null + }, + "testcases" : [ + ["{count}", "one,two,three"], + ["{count*}", "one,two,three"], + ["{/count}", "/one,two,three"], + ["{/count*}", "/one/two/three"], + ["{;count}", ";count=one,two,three"], + ["{;count*}", ";count=one;count=two;count=three"], + ["{?count}", "?count=one,two,three"], + ["{?count*}", "?count=one&count=two&count=three"], + ["{&count*}", "&count=one&count=two&count=three"] + ] + }, + "3.2.2 Simple String Expansion" : + { + "variables": { + "count" : ["one", "two", "three"], + "dom" : ["example", "com"], + "dub" : "me/too", + "hello" : "Hello World!", + "half" : "50%", + "var" : "value", + "who" : "fred", + "base" : "http://example.com/home/", + "path" : "/foo/bar", + "list" : ["red", "green", "blue"], + "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, + "v" : "6", + "x" : "1024", + "y" : "768", + "empty" : "", + "empty_keys" : [], + "undef" : null + }, + "testcases" : [ + ["{var}", "value"], + ["{hello}", "Hello%20World%21"], + ["{half}", "50%25"], + ["O{empty}X", "OX"], + ["O{undef}X", "OX"], + ["{x,y}", "1024,768"], + ["{x,hello,y}", "1024,Hello%20World%21,768"], + ["?{x,empty}", "?1024,"], + ["?{x,undef}", "?1024"], + ["?{undef,y}", "?768"], + ["{var:3}", "val"], + ["{var:30}", "value"], + ["{list}", "red,green,blue"], + ["{list*}", "red,green,blue"], + ["{keys}", [ + "comma,%2C,dot,.,semi,%3B", + "comma,%2C,semi,%3B,dot,.", + "dot,.,comma,%2C,semi,%3B", + "dot,.,semi,%3B,comma,%2C", + "semi,%3B,comma,%2C,dot,.", + "semi,%3B,dot,.,comma,%2C" + ]], + ["{keys*}", [ + "comma=%2C,dot=.,semi=%3B", + "comma=%2C,semi=%3B,dot=.", + "dot=.,comma=%2C,semi=%3B", + "dot=.,semi=%3B,comma=%2C", + "semi=%3B,comma=%2C,dot=.", + "semi=%3B,dot=.,comma=%2C" + ]] + ] + }, + "3.2.3 Reserved Expansion" : + { + "variables": { + "count" : ["one", "two", "three"], + "dom" : ["example", "com"], + "dub" : "me/too", + "hello" : "Hello World!", + "half" : "50%", + "var" : "value", + "who" : "fred", + "base" : "http://example.com/home/", + "path" : "/foo/bar", + "list" : ["red", "green", "blue"], + "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, + "v" : "6", + "x" : "1024", + "y" : "768", + "empty" : "", + "empty_keys" : [], + "undef" : null + }, + "testcases" : [ + ["{+var}", "value"], + ["{/var,empty}", "/value/"], + ["{/var,undef}", "/value"], + ["{+hello}", "Hello%20World!"], + ["{+half}", "50%25"], + ["{base}index", "http%3A%2F%2Fexample.com%2Fhome%2Findex"], + ["{+base}index", "http://example.com/home/index"], + ["O{+empty}X", "OX"], + ["O{+undef}X", "OX"], + ["{+path}/here", "/foo/bar/here"], + ["{+path:6}/here", "/foo/b/here"], + ["here?ref={+path}", "here?ref=/foo/bar"], + ["up{+path}{var}/here", "up/foo/barvalue/here"], + ["{+x,hello,y}", "1024,Hello%20World!,768"], + ["{+path,x}/here", "/foo/bar,1024/here"], + ["{+list}", "red,green,blue"], + ["{+list*}", "red,green,blue"], + ["{+keys}", [ + "comma,,,dot,.,semi,;", + "comma,,,semi,;,dot,.", + "dot,.,comma,,,semi,;", + "dot,.,semi,;,comma,,", + "semi,;,comma,,,dot,.", + "semi,;,dot,.,comma,," + ]], + ["{+keys*}", [ + "comma=,,dot=.,semi=;", + "comma=,,semi=;,dot=.", + "dot=.,comma=,,semi=;", + "dot=.,semi=;,comma=,", + "semi=;,comma=,,dot=.", + "semi=;,dot=.,comma=," + ]] + ] + }, + "3.2.4 Fragment Expansion" : + { + "variables": { + "count" : ["one", "two", "three"], + "dom" : ["example", "com"], + "dub" : "me/too", + "hello" : "Hello World!", + "half" : "50%", + "var" : "value", + "who" : "fred", + "base" : "http://example.com/home/", + "path" : "/foo/bar", + "list" : ["red", "green", "blue"], + "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, + "v" : "6", + "x" : "1024", + "y" : "768", + "empty" : "", + "empty_keys" : [], + "undef" : null + }, + "testcases" : [ + ["{#var}", "#value"], + ["{#hello}", "#Hello%20World!"], + ["{#half}", "#50%25"], + ["foo{#empty}", "foo#"], + ["foo{#undef}", "foo"], + ["{#x,hello,y}", "#1024,Hello%20World!,768"], + ["{#path,x}/here", "#/foo/bar,1024/here"], + ["{#path:6}/here", "#/foo/b/here"], + ["{#list}", "#red,green,blue"], + ["{#list*}", "#red,green,blue"], + ["{#keys}", [ + "#comma,,,dot,.,semi,;", + "#comma,,,semi,;,dot,.", + "#dot,.,comma,,,semi,;", + "#dot,.,semi,;,comma,,", + "#semi,;,comma,,,dot,.", + "#semi,;,dot,.,comma,," + ]] + ] + }, + "3.2.5 Label Expansion with Dot-Prefix" : + { + "variables": { + "count" : ["one", "two", "three"], + "dom" : ["example", "com"], + "dub" : "me/too", + "hello" : "Hello World!", + "half" : "50%", + "var" : "value", + "who" : "fred", + "base" : "http://example.com/home/", + "path" : "/foo/bar", + "list" : ["red", "green", "blue"], + "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, + "v" : "6", + "x" : "1024", + "y" : "768", + "empty" : "", + "empty_keys" : [], + "undef" : null + }, + "testcases" : [ + ["{.who}", ".fred"], + ["{.who,who}", ".fred.fred"], + ["{.half,who}", ".50%25.fred"], + ["www{.dom*}", "www.example.com"], + ["X{.var}", "X.value"], + ["X{.var:3}", "X.val"], + ["X{.empty}", "X."], + ["X{.undef}", "X"], + ["X{.list}", "X.red,green,blue"], + ["X{.list*}", "X.red.green.blue"], + ["{#keys}", [ + "#comma,,,dot,.,semi,;", + "#comma,,,semi,;,dot,.", + "#dot,.,comma,,,semi,;", + "#dot,.,semi,;,comma,,", + "#semi,;,comma,,,dot,.", + "#semi,;,dot,.,comma,," + ]], + ["{#keys*}", [ + "#comma=,,dot=.,semi=;", + "#comma=,,semi=;,dot=.", + "#dot=.,comma=,,semi=;", + "#dot=.,semi=;,comma=,", + "#semi=;,comma=,,dot=.", + "#semi=;,dot=.,comma=," + ]], + ["X{.empty_keys}", "X"], + ["X{.empty_keys*}", "X"] + ] + }, + "3.2.6 Path Segment Expansion" : + { + "variables": { + "count" : ["one", "two", "three"], + "dom" : ["example", "com"], + "dub" : "me/too", + "hello" : "Hello World!", + "half" : "50%", + "var" : "value", + "who" : "fred", + "base" : "http://example.com/home/", + "path" : "/foo/bar", + "list" : ["red", "green", "blue"], + "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, + "v" : "6", + "x" : "1024", + "y" : "768", + "empty" : "", + "empty_keys" : [], + "undef" : null + }, + "testcases" : [ + ["{/who}", "/fred"], + ["{/who,who}", "/fred/fred"], + ["{/half,who}", "/50%25/fred"], + ["{/who,dub}", "/fred/me%2Ftoo"], + ["{/var}", "/value"], + ["{/var,empty}", "/value/"], + ["{/var,undef}", "/value"], + ["{/var,x}/here", "/value/1024/here"], + ["{/var:1,var}", "/v/value"], + ["{/list}", "/red,green,blue"], + ["{/list*}", "/red/green/blue"], + ["{/list*,path:4}", "/red/green/blue/%2Ffoo"], + ["{/keys}", [ + "/comma,%2C,dot,.,semi,%3B", + "/comma,%2C,semi,%3B,dot,.", + "/dot,.,comma,%2C,semi,%3B", + "/dot,.,semi,%3B,comma,%2C", + "/semi,%3B,comma,%2C,dot,.", + "/semi,%3B,dot,.,comma,%2C" + ]], + ["{/keys*}", [ + "/comma=%2C/dot=./semi=%3B", + "/comma=%2C/semi=%3B/dot=.", + "/dot=./comma=%2C/semi=%3B", + "/dot=./semi=%3B/comma=%2C", + "/semi=%3B/comma=%2C/dot=.", + "/semi=%3B/dot=./comma=%2C" + ]] + ] + }, + "3.2.7 Path-Style Parameter Expansion" : + { + "variables": { + "count" : ["one", "two", "three"], + "dom" : ["example", "com"], + "dub" : "me/too", + "hello" : "Hello World!", + "half" : "50%", + "var" : "value", + "who" : "fred", + "base" : "http://example.com/home/", + "path" : "/foo/bar", + "list" : ["red", "green", "blue"], + "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, + "v" : "6", + "x" : "1024", + "y" : "768", + "empty" : "", + "empty_keys" : [], + "undef" : null + }, + "testcases" : [ + ["{;who}", ";who=fred"], + ["{;half}", ";half=50%25"], + ["{;empty}", ";empty"], + ["{;hello:5}", ";hello=Hello"], + ["{;v,empty,who}", ";v=6;empty;who=fred"], + ["{;v,bar,who}", ";v=6;who=fred"], + ["{;x,y}", ";x=1024;y=768"], + ["{;x,y,empty}", ";x=1024;y=768;empty"], + ["{;x,y,undef}", ";x=1024;y=768"], + ["{;list}", ";list=red,green,blue"], + ["{;list*}", ";list=red;list=green;list=blue"], + ["{;keys}", [ + ";keys=comma,%2C,dot,.,semi,%3B", + ";keys=comma,%2C,semi,%3B,dot,.", + ";keys=dot,.,comma,%2C,semi,%3B", + ";keys=dot,.,semi,%3B,comma,%2C", + ";keys=semi,%3B,comma,%2C,dot,.", + ";keys=semi,%3B,dot,.,comma,%2C" + ]], + ["{;keys*}", [ + ";comma=%2C;dot=.;semi=%3B", + ";comma=%2C;semi=%3B;dot=.", + ";dot=.;comma=%2C;semi=%3B", + ";dot=.;semi=%3B;comma=%2C", + ";semi=%3B;comma=%2C;dot=.", + ";semi=%3B;dot=.;comma=%2C" + ]] + ] + }, + "3.2.8 Form-Style Query Expansion" : + { + "variables": { + "count" : ["one", "two", "three"], + "dom" : ["example", "com"], + "dub" : "me/too", + "hello" : "Hello World!", + "half" : "50%", + "var" : "value", + "who" : "fred", + "base" : "http://example.com/home/", + "path" : "/foo/bar", + "list" : ["red", "green", "blue"], + "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, + "v" : "6", + "x" : "1024", + "y" : "768", + "empty" : "", + "empty_keys" : [], + "undef" : null + }, + "testcases" : [ + ["{?who}", "?who=fred"], + ["{?half}", "?half=50%25"], + ["{?x,y}", "?x=1024&y=768"], + ["{?x,y,empty}", "?x=1024&y=768&empty="], + ["{?x,y,undef}", "?x=1024&y=768"], + ["{?var:3}", "?var=val"], + ["{?list}", "?list=red,green,blue"], + ["{?list*}", "?list=red&list=green&list=blue"], + ["{?keys}", [ + "?keys=comma,%2C,dot,.,semi,%3B", + "?keys=comma,%2C,semi,%3B,dot,.", + "?keys=dot,.,comma,%2C,semi,%3B", + "?keys=dot,.,semi,%3B,comma,%2C", + "?keys=semi,%3B,comma,%2C,dot,.", + "?keys=semi,%3B,dot,.,comma,%2C" + ]], + ["{?keys*}", [ + "?comma=%2C&dot=.&semi=%3B", + "?comma=%2C&semi=%3B&dot=.", + "?dot=.&comma=%2C&semi=%3B", + "?dot=.&semi=%3B&comma=%2C", + "?semi=%3B&comma=%2C&dot=.", + "?semi=%3B&dot=.&comma=%2C" + ]] + ] + }, + "3.2.9 Form-Style Query Continuation" : + { + "variables": { + "count" : ["one", "two", "three"], + "dom" : ["example", "com"], + "dub" : "me/too", + "hello" : "Hello World!", + "half" : "50%", + "var" : "value", + "who" : "fred", + "base" : "http://example.com/home/", + "path" : "/foo/bar", + "list" : ["red", "green", "blue"], + "keys" : { "semi" : ";", "dot" : ".", "comma" : ","}, + "v" : "6", + "x" : "1024", + "y" : "768", + "empty" : "", + "empty_keys" : [], + "undef" : null + }, + "testcases" : [ + ["{&who}", "&who=fred"], + ["{&half}", "&half=50%25"], + ["?fixed=yes{&x}", "?fixed=yes&x=1024"], + ["{&var:3}", "&var=val"], + ["{&x,y,empty}", "&x=1024&y=768&empty="], + ["{&x,y,undef}", "&x=1024&y=768"], + ["{&list}", "&list=red,green,blue"], + ["{&list*}", "&list=red&list=green&list=blue"], + ["{&keys}", [ + "&keys=comma,%2C,dot,.,semi,%3B", + "&keys=comma,%2C,semi,%3B,dot,.", + "&keys=dot,.,comma,%2C,semi,%3B", + "&keys=dot,.,semi,%3B,comma,%2C", + "&keys=semi,%3B,comma,%2C,dot,.", + "&keys=semi,%3B,dot,.,comma,%2C" + ]], + ["{&keys*}", [ + "&comma=%2C&dot=.&semi=%3B", + "&comma=%2C&semi=%3B&dot=.", + "&dot=.&comma=%2C&semi=%3B", + "&dot=.&semi=%3B&comma=%2C", + "&semi=%3B&comma=%2C&dot=.", + "&semi=%3B&dot=.&comma=%2C" + ]] + ] + } +} diff --git a/tests/fixtures/spec-examples.json b/tests/fixtures/spec-examples.json new file mode 100644 index 0000000..2e8e942 --- /dev/null +++ b/tests/fixtures/spec-examples.json @@ -0,0 +1,218 @@ +{ + "Level 1 Examples" : + { + "level": 1, + "variables": { + "var" : "value", + "hello" : "Hello World!" + }, + "testcases" : [ + ["{var}", "value"], + ["{hello}", "Hello%20World%21"] + ] + }, + "Level 2 Examples" : + { + "level": 2, + "variables": { + "var" : "value", + "hello" : "Hello World!", + "path" : "/foo/bar" + }, + "testcases" : [ + ["{+var}", "value"], + ["{+hello}", "Hello%20World!"], + ["{+path}/here", "/foo/bar/here"], + ["here?ref={+path}", "here?ref=/foo/bar"] + ] + }, + "Level 3 Examples" : + { + "level": 3, + "variables": { + "var" : "value", + "hello" : "Hello World!", + "empty" : "", + "path" : "/foo/bar", + "x" : "1024", + "y" : "768" + }, + "testcases" : [ + ["map?{x,y}", "map?1024,768"], + ["{x,hello,y}", "1024,Hello%20World%21,768"], + ["{+x,hello,y}", "1024,Hello%20World!,768"], + ["{+path,x}/here", "/foo/bar,1024/here"], + ["{#x,hello,y}", "#1024,Hello%20World!,768"], + ["{#path,x}/here", "#/foo/bar,1024/here"], + ["X{.var}", "X.value"], + ["X{.x,y}", "X.1024.768"], + ["{/var}", "/value"], + ["{/var,x}/here", "/value/1024/here"], + ["{;x,y}", ";x=1024;y=768"], + ["{;x,y,empty}", ";x=1024;y=768;empty"], + ["{?x,y}", "?x=1024&y=768"], + ["{?x,y,empty}", "?x=1024&y=768&empty="], + ["?fixed=yes{&x}", "?fixed=yes&x=1024"], + ["{&x,y,empty}", "&x=1024&y=768&empty="] + ] + }, + "Level 4 Examples" : + { + "level": 4, + "variables": { + "var": "value", + "hello": "Hello World!", + "path": "/foo/bar", + "list": ["red", "green", "blue"], + "keys": {"semi": ";", "dot": ".", "comma":","} + }, + "testcases": [ + ["{var:3}", "val"], + ["{var:30}", "value"], + ["{list}", "red,green,blue"], + ["{list*}", "red,green,blue"], + ["{keys}", [ + "comma,%2C,dot,.,semi,%3B", + "comma,%2C,semi,%3B,dot,.", + "dot,.,comma,%2C,semi,%3B", + "dot,.,semi,%3B,comma,%2C", + "semi,%3B,comma,%2C,dot,.", + "semi,%3B,dot,.,comma,%2C" + ]], + ["{keys*}", [ + "comma=%2C,dot=.,semi=%3B", + "comma=%2C,semi=%3B,dot=.", + "dot=.,comma=%2C,semi=%3B", + "dot=.,semi=%3B,comma=%2C", + "semi=%3B,comma=%2C,dot=.", + "semi=%3B,dot=.,comma=%2C" + ]], + ["{+path:6}/here", "/foo/b/here"], + ["{+list}", "red,green,blue"], + ["{+list*}", "red,green,blue"], + ["{+keys}", [ + "comma,,,dot,.,semi,;", + "comma,,,semi,;,dot,.", + "dot,.,comma,,,semi,;", + "dot,.,semi,;,comma,,", + "semi,;,comma,,,dot,.", + "semi,;,dot,.,comma,," + ]], + ["{+keys*}", [ + "comma=,,dot=.,semi=;", + "comma=,,semi=;,dot=.", + "dot=.,comma=,,semi=;", + "dot=.,semi=;,comma=,", + "semi=;,comma=,,dot=.", + "semi=;,dot=.,comma=," + ]], + ["{#path:6}/here", "#/foo/b/here"], + ["{#list}", "#red,green,blue"], + ["{#list*}", "#red,green,blue"], + ["{#keys}", [ + "#comma,,,dot,.,semi,;", + "#comma,,,semi,;,dot,.", + "#dot,.,comma,,,semi,;", + "#dot,.,semi,;,comma,,", + "#semi,;,comma,,,dot,.", + "#semi,;,dot,.,comma,," + ]], + ["{#keys*}", [ + "#comma=,,dot=.,semi=;", + "#comma=,,semi=;,dot=.", + "#dot=.,comma=,,semi=;", + "#dot=.,semi=;,comma=,", + "#semi=;,comma=,,dot=.", + "#semi=;,dot=.,comma=," + ]], + ["X{.var:3}", "X.val"], + ["X{.list}", "X.red,green,blue"], + ["X{.list*}", "X.red.green.blue"], + ["X{.keys}", [ + "X.comma,%2C,dot,.,semi,%3B", + "X.comma,%2C,semi,%3B,dot,.", + "X.dot,.,comma,%2C,semi,%3B", + "X.dot,.,semi,%3B,comma,%2C", + "X.semi,%3B,comma,%2C,dot,.", + "X.semi,%3B,dot,.,comma,%2C" + ]], + ["{/var:1,var}", "/v/value"], + ["{/list}", "/red,green,blue"], + ["{/list*}", "/red/green/blue"], + ["{/list*,path:4}", "/red/green/blue/%2Ffoo"], + ["{/keys}", [ + "/comma,%2C,dot,.,semi,%3B", + "/comma,%2C,semi,%3B,dot,.", + "/dot,.,comma,%2C,semi,%3B", + "/dot,.,semi,%3B,comma,%2C", + "/semi,%3B,comma,%2C,dot,.", + "/semi,%3B,dot,.,comma,%2C" + ]], + ["{/keys*}", [ + "/comma=%2C/dot=./semi=%3B", + "/comma=%2C/semi=%3B/dot=.", + "/dot=./comma=%2C/semi=%3B", + "/dot=./semi=%3B/comma=%2C", + "/semi=%3B/comma=%2C/dot=.", + "/semi=%3B/dot=./comma=%2C" + ]], + ["{;hello:5}", ";hello=Hello"], + ["{;list}", ";list=red,green,blue"], + ["{;list*}", ";list=red;list=green;list=blue"], + ["{;keys}", [ + ";keys=comma,%2C,dot,.,semi,%3B", + ";keys=comma,%2C,semi,%3B,dot,.", + ";keys=dot,.,comma,%2C,semi,%3B", + ";keys=dot,.,semi,%3B,comma,%2C", + ";keys=semi,%3B,comma,%2C,dot,.", + ";keys=semi,%3B,dot,.,comma,%2C" + ]], + ["{;keys*}", [ + ";comma=%2C;dot=.;semi=%3B", + ";comma=%2C;semi=%3B;dot=.", + ";dot=.;comma=%2C;semi=%3B", + ";dot=.;semi=%3B;comma=%2C", + ";semi=%3B;comma=%2C;dot=.", + ";semi=%3B;dot=.;comma=%2C" + ]], + ["{?var:3}", "?var=val"], + ["{?list}", "?list=red,green,blue"], + ["{?list*}", "?list=red&list=green&list=blue"], + ["{?keys}", [ + "?keys=comma,%2C,dot,.,semi,%3B", + "?keys=comma,%2C,semi,%3B,dot,.", + "?keys=dot,.,comma,%2C,semi,%3B", + "?keys=dot,.,semi,%3B,comma,%2C", + "?keys=semi,%3B,comma,%2C,dot,.", + "?keys=semi,%3B,dot,.,comma,%2C" + ]], + ["{?keys*}", [ + "?comma=%2C&dot=.&semi=%3B", + "?comma=%2C&semi=%3B&dot=.", + "?dot=.&comma=%2C&semi=%3B", + "?dot=.&semi=%3B&comma=%2C", + "?semi=%3B&comma=%2C&dot=.", + "?semi=%3B&dot=.&comma=%2C" + ]], + ["{&var:3}", "&var=val"], + ["{&list}", "&list=red,green,blue"], + ["{&list*}", "&list=red&list=green&list=blue"], + ["{&keys}", [ + "&keys=comma,%2C,dot,.,semi,%3B", + "&keys=comma,%2C,semi,%3B,dot,.", + "&keys=dot,.,comma,%2C,semi,%3B", + "&keys=dot,.,semi,%3B,comma,%2C", + "&keys=semi,%3B,comma,%2C,dot,.", + "&keys=semi,%3B,dot,.,comma,%2C" + ]], + ["{&keys*}", [ + "&comma=%2C&dot=.&semi=%3B", + "&comma=%2C&semi=%3B&dot=.", + "&dot=.&comma=%2C&semi=%3B", + "&dot=.&semi=%3B&comma=%2C", + "&semi=%3B&comma=%2C&dot=.", + "&semi=%3B&dot=.&comma=%2C" + ]] + ] + } +} diff --git a/tests/fixtures/transform-json-tests.xslt b/tests/fixtures/transform-json-tests.xslt new file mode 100644 index 0000000..d956b6b --- /dev/null +++ b/tests/fixtures/transform-json-tests.xslt @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> + +<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + + <xsl:include href="json2xml.xslt"/> + + <!-- the input test files need to be supplied as a sequence of file names only (no extension) --> + <xsl:param name="tests" select="('extended-tests', 'negative-tests', 'spec-examples-by-section', 'spec-examples')"/> + <xsl:param name="json-ext" select="'json'"/> + <xsl:param name="xml-ext" select="'xml'"/> + + <!-- run this stylesheet with any input to generate the XML test files at output. --> + <!-- (a popular way of doing this is to supply the XSLT to itself as the input file.) --> + + <xsl:template match="/"> + <xsl:for-each select="$tests"> + <xsl:variable name="file" select="current()"/> + <xsl:variable name="json"> + <json> + <xsl:value-of select="unparsed-text(concat($file, '.', $json-ext))"/> + </json> + </xsl:variable> + <xsl:variable name="xml"> + <xsl:apply-templates select="$json/json"/> + </xsl:variable> + <xsl:result-document href="{$file}.{$xml-ext}" method="xml" indent="yes"> + <tests> + <xsl:for-each select="$xml/json/object/field"> + <test name="{@name}"> + <xsl:if test="exists(object/field[1][@name eq 'level'])"> + <xsl:attribute name="level" select="object/field[1][@name eq 'level']/integer/text()"/> + </xsl:if> + <variables> + <xsl:for-each select="object/field[@name eq 'variables']/object/field"> + <variable name="{@name}"> + <xsl:copy-of select="*"/> + </variable> + </xsl:for-each> + </variables> + <testcases> + <xsl:for-each select="object/field[@name eq 'testcases']/array/array"> + <testcase template="{*[1]}" result="{*[2]}"/> + </xsl:for-each> </testcases> + </test> + </xsl:for-each> + </tests> + </xsl:result-document> + </xsl:for-each> + </xsl:template> + +</xsl:stylesheet>
\ No newline at end of file diff --git a/tests/test_from_fixtures.py b/tests/test_from_fixtures.py new file mode 100644 index 0000000..70d6553 --- /dev/null +++ b/tests/test_from_fixtures.py @@ -0,0 +1,116 @@ +import json +import os.path + +import uritemplate + + +def fixture_file_path(filename): + absolute_dir = os.path.abspath(os.path.dirname(__file__)) + filename = filename + '.json' + return os.path.join(absolute_dir, 'fixtures', filename) + + +def load_examples(filename): + path = fixture_file_path(filename) + with open(path, 'r') as examples_file: + examples = json.load(examples_file) + return examples + + +def expected_set(expected): + if isinstance(expected, list): + return set(expected) + return set([expected]) + + +class FixtureMixin(object): + def _get_test(self, section): + test = self.examples.get(section, {}) + return test.get('variables', {}), test.get('testcases', []) + + def _test(self, testname): + variables, testcases = self._get_test(testname) + for template, expected in testcases: + expected = expected_set(expected) + expanded = uritemplate.expand(template, variables) + assert expanded in expected + + +class TestSpecExamples(FixtureMixin): + examples = load_examples('spec-examples') + + def test_level_1(self): + """Check that uritemplate.expand matches Level 1 expectations.""" + self._test('Level 1 Examples') + + def test_level_2(self): + """Check that uritemplate.expand matches Level 2 expectations.""" + self._test('Level 2 Examples') + + def test_level_3(self): + """Check that uritemplate.expand matches Level 3 expectations.""" + self._test('Level 3 Examples') + + def test_level_4(self): + """Check that uritemplate.expand matches Level 4 expectations.""" + self._test('Level 4 Examples') + + +class TestSpecExamplesByRFCSection(FixtureMixin): + examples = load_examples('spec-examples-by-section') + + def test_variable_expansion(self): + """Check variable expansion.""" + self._test('3.2.1 Variable Expansion') + + def test_simple_string_expansion(self): + """Check simple string expansion.""" + self._test('3.2.2 Simple String Expansion') + + def test_reserved_expansion(self): + """Check reserved expansion.""" + self._test('3.2.3 Reserved Expansion') + + def test_fragment_expansion(self): + """Check fragment expansion.""" + self._test('3.2.4 Fragment Expansion') + + def test_dot_prefixed_label_expansion(self): + """Check label expansion with dot-prefix.""" + self._test('3.2.5 Label Expansion with Dot-Prefix') + + def test_path_segment_expansion(self): + """Check path segment expansion.""" + self._test('3.2.6 Path Segment Expansion') + + def test_path_style_parameter_expansion(self): + """Check path-style param expansion.""" + self._test('3.2.7 Path-Style Parameter Expansion') + + def test_form_style_query_expansion(self): + """Check form-style query expansion.""" + self._test('3.2.8 Form-Style Query Expansion') + + def test_form_style_query_cntinuation(self): + """Check form-style query continuation.""" + self._test('3.2.9 Form-Style Query Continuation') + + +class TestExtendedTests(FixtureMixin): + examples = load_examples('extended-tests') + + def test_additional_examples_1(self): + """Check Additional Examples 1.""" + self._test('Additional Examples 1') + + def test_additional_examples_2(self): + """Check Additional Examples 2.""" + self._test('Additional Examples 2') + + def test_additional_examples_3(self): + """Check Additional Examples 3.""" + self._test('Additional Examples 3: Empty Variables') + + def test_additional_examples_4(self): + """Check Additional Examples 4.""" + self._test('Additional Examples 4: Numeric Keys') diff --git a/tests/test_uritemplate.py b/tests/test_uritemplate.py new file mode 100644 index 0000000..b1abc96 --- /dev/null +++ b/tests/test_uritemplate.py @@ -0,0 +1,589 @@ +from unittest import TestCase, main +from uritemplate import URITemplate, expand, partial, variables +from uritemplate import variable + + +def merge_dicts(*args): + d = {} + for arg in args: + d.update(arg) + return d + + +class RFCTemplateExamples(type): + var = {'var': 'value'} + hello = {'hello': 'Hello World!'} + path = {'path': '/foo/bar'} + x = {'x': '1024'} + y = {'y': '768'} + empty = {'empty': ''} + merged_x_y = merge_dicts(x, y) + list_ex = {'list': ['red', 'green', 'blue']} + keys = {'keys': [('semi', ';'), ('dot', '.'), ('comma', ',')]} + + # # Level 1 + # Simple string expansion + level1_examples = { + '{var}': { + 'expansion': var, + 'expected': 'value', + }, + '{hello}': { + 'expansion': hello, + 'expected': 'Hello%20World%21', + }, + } + + # # Level 2 + # Reserved string expansion + level2_reserved_examples = { + '{+var}': { + 'expansion': var, + 'expected': 'value', + }, + '{+hello}': { + 'expansion': hello, + 'expected': 'Hello%20World!', + }, + '{+path}/here': { + 'expansion': path, + 'expected': '/foo/bar/here', + }, + 'here?ref={+path}': { + 'expansion': path, + 'expected': 'here?ref=/foo/bar', + }, + } + + # Fragment expansion, crosshatch-prefixed + level2_fragment_examples = { + 'X{#var}': { + 'expansion': var, + 'expected': 'X#value', + }, + 'X{#hello}': { + 'expansion': hello, + 'expected': 'X#Hello%20World!' + }, + } + + # # Level 3 + # String expansion with multiple variables + level3_multiple_variable_examples = { + 'map?{x,y}': { + 'expansion': merged_x_y, + 'expected': 'map?1024,768', + }, + '{x,hello,y}': { + 'expansion': merge_dicts(x, y, hello), + 'expected': '1024,Hello%20World%21,768', + }, + } + + # Reserved expansion with multiple variables + level3_reserved_examples = { + '{+x,hello,y}': { + 'expansion': merge_dicts(x, y, hello), + 'expected': '1024,Hello%20World!,768', + }, + '{+path,x}/here': { + 'expansion': merge_dicts(path, x), + 'expected': '/foo/bar,1024/here', + }, + } + + # Fragment expansion with multiple variables + level3_fragment_examples = { + '{#x,hello,y}': { + 'expansion': merge_dicts(x, y, hello), + 'expected': '#1024,Hello%20World!,768', + }, + '{#path,x}/here': { + 'expansion': merge_dicts(path, x), + 'expected': '#/foo/bar,1024/here' + }, + } + + # Label expansion, dot-prefixed + level3_label_examples = { + 'X{.var}': { + 'expansion': var, + 'expected': 'X.value', + }, + 'X{.x,y}': { + 'expansion': merged_x_y, + 'expected': 'X.1024.768', + } + } + + # Path segments, slash-prefixed + level3_path_segment_examples = { + '{/var}': { + 'expansion': var, + 'expected': '/value', + }, + '{/var,x}/here': { + 'expansion': merge_dicts(var, x), + 'expected': '/value/1024/here', + }, + } + + # Path-style parameters, semicolon-prefixed + level3_path_semi_examples = { + '{;x,y}': { + 'expansion': merged_x_y, + 'expected': ';x=1024;y=768', + }, + '{;x,y,empty}': { + 'expansion': merge_dicts(x, y, empty), + 'expected': ';x=1024;y=768;empty', + }, + } + + # Form-style query, ampersand-separated + level3_form_amp_examples = { + '{?x,y}': { + 'expansion': merged_x_y, + 'expected': '?x=1024&y=768', + }, + '{?x,y,empty}': { + 'expansion': merge_dicts(x, y, empty), + 'expected': '?x=1024&y=768&empty=', + }, + } + + # Form-style query continuation + level3_form_cont_examples = { + '?fixed=yes{&x}': { + 'expansion': x, + 'expected': '?fixed=yes&x=1024', + }, + '{&x,y,empty}': { + 'expansion': merge_dicts(x, y, empty), + 'expected': '&x=1024&y=768&empty=', + } + } + + # # Level 4 + # String expansion with value modifiers + level4_value_modifier_examples = { + '{var:3}': { + 'expansion': var, + 'expected': 'val', + }, + '{var:30}': { + 'expansion': var, + 'expected': 'value', + }, + '{list}': { + 'expansion': list_ex, + 'expected': 'red,green,blue', + }, + '{list*}': { + 'expansion': list_ex, + 'expected': 'red,green,blue', + }, + '{keys}': { + 'expansion': keys, + 'expected': 'semi,%3B,dot,.,comma,%2C', + }, + '{keys*}': { + 'expansion': keys, + 'expected': 'semi=%3B,dot=.,comma=%2C', + }, + } + + # Reserved expansion with value modifiers + level4_reserved_examples = { + '{+path:6}/here': { + 'expansion': path, + 'expected': '/foo/b/here', + }, + '{+list}': { + 'expansion': list_ex, + 'expected': 'red,green,blue', + }, + '{+list*}': { + 'expansion': list_ex, + 'expected': 'red,green,blue', + }, + '{+keys}': { + 'expansion': keys, + 'expected': 'semi,;,dot,.,comma,,', + }, + '{+keys*}': { + 'expansion': keys, + 'expected': 'semi=;,dot=.,comma=,', + }, + } + + # Fragment expansion with value modifiers + level4_fragment_examples = { + '{#path:6}/here': { + 'expansion': path, + 'expected': '#/foo/b/here', + }, + '{#list}': { + 'expansion': list_ex, + 'expected': '#red,green,blue', + }, + '{#list*}': { + 'expansion': list_ex, + 'expected': '#red,green,blue', + }, + '{#keys}': { + 'expansion': keys, + 'expected': '#semi,;,dot,.,comma,,' + }, + '{#keys*}': { + 'expansion': keys, + 'expected': '#semi=;,dot=.,comma=,' + }, + } + + # Label expansion, dot-prefixed + level4_label_examples = { + 'X{.var:3}': { + 'expansion': var, + 'expected': 'X.val', + }, + 'X{.list}': { + 'expansion': list_ex, + 'expected': 'X.red,green,blue', + }, + 'X{.list*}': { + 'expansion': list_ex, + 'expected': 'X.red.green.blue', + }, + 'X{.keys}': { + 'expansion': keys, + 'expected': 'X.semi,%3B,dot,.,comma,%2C', + }, + 'X{.keys*}': { + 'expansion': keys, + 'expected': 'X.semi=%3B.dot=..comma=%2C', + }, + } + + # Path segments, slash-prefixed + level4_path_slash_examples = { + '{/var:1,var}': { + 'expansion': var, + 'expected': '/v/value', + }, + '{/list}': { + 'expansion': list_ex, + 'expected': '/red,green,blue', + }, + '{/list*}': { + 'expansion': list_ex, + 'expected': '/red/green/blue', + }, + '{/list*,path:4}': { + 'expansion': merge_dicts(list_ex, path), + 'expected': '/red/green/blue/%2Ffoo', + }, + '{/keys}': { + 'expansion': keys, + 'expected': '/semi,%3B,dot,.,comma,%2C', + }, + '{/keys*}': { + 'expansion': keys, + 'expected': '/semi=%3B/dot=./comma=%2C', + }, + } + + # Path-style parameters, semicolon-prefixed + level4_path_semi_examples = { + '{;hello:5}': { + 'expansion': hello, + 'expected': ';hello=Hello', + }, + '{;list}': { + 'expansion': list_ex, + 'expected': ';list=red,green,blue', + }, + '{;list*}': { + 'expansion': list_ex, + 'expected': ';list=red;list=green;list=blue', + }, + '{;keys}': { + 'expansion': keys, + 'expected': ';keys=semi,%3B,dot,.,comma,%2C', + }, + '{;keys*}': { + 'expansion': keys, + 'expected': ';semi=%3B;dot=.;comma=%2C', + }, + } + + # Form-style query, ampersand-separated + level4_form_amp_examples = { + '{?var:3}': { + 'expansion': var, + 'expected': '?var=val', + }, + '{?list}': { + 'expansion': list_ex, + 'expected': '?list=red,green,blue', + }, + '{?list*}': { + 'expansion': list_ex, + 'expected': '?list=red&list=green&list=blue', + }, + '{?keys}': { + 'expansion': keys, + 'expected': '?keys=semi,%3B,dot,.,comma,%2C', + }, + '{?keys*}': { + 'expansion': keys, + 'expected': '?semi=%3B&dot=.&comma=%2C', + }, + } + + # Form-style query continuation + level4_form_query_examples = { + '{&var:3}': { + 'expansion': var, + 'expected': '&var=val', + }, + '{&list}': { + 'expansion': list_ex, + 'expected': '&list=red,green,blue', + }, + '{&list*}': { + 'expansion': list_ex, + 'expected': '&list=red&list=green&list=blue', + }, + '{&keys}': { + 'expansion': keys, + 'expected': '&keys=semi,%3B,dot,.,comma,%2C', + }, + '{&keys*}': { + 'expansion': keys, + 'expected': '&semi=%3B&dot=.&comma=%2C', + }, + } + + def __new__(cls, name, bases, attrs): + def make_test(d): + def _test_(self): + for k, v in d.items(): + t = URITemplate(k) + self.assertEqual(t.expand(v['expansion']), v['expected']) + return _test_ + + examples = [ + ( + n, getattr(RFCTemplateExamples, n) + ) for n in dir(RFCTemplateExamples) if n.startswith('level') + ] + + for name, value in examples: + testname = 'test_%s' % name + attrs[testname] = make_test(value) + + return type.__new__(cls, name, bases, attrs) + + +class TestURITemplate(RFCTemplateExamples('RFCMeta', (TestCase,), {})): + def test_no_variables_in_uri(self): + """ + This test ensures that if there are no variables present, the + template evaluates to itself. + """ + uri = 'https://api.github.com/users' + t = URITemplate(uri) + self.assertEqual(t.expand(), uri) + self.assertEqual(t.expand(users='foo'), uri) + + def test_all_variables_parsed(self): + """ + This test ensures that all variables are parsed. + """ + uris = [ + 'https://api.github.com', + 'https://api.github.com/users{/user}', + 'https://api.github.com/repos{/user}{/repo}', + 'https://api.github.com/repos{/user}{/repo}/issues{/issue}' + ] + + for i, uri in enumerate(uris): + t = URITemplate(uri) + self.assertEqual(len(t.variables), i) + + def test_expand(self): + """ + This test ensures that expansion works as expected. + """ + # Single + t = URITemplate('https://api.github.com/users{/user}') + expanded = 'https://api.github.com/users/sigmavirus24' + self.assertEqual(t.expand(user='sigmavirus24'), expanded) + v = t.variables[0] + self.assertEqual(v.expand({'user': None}), {'/user': ''}) + + # Multiple + t = URITemplate('https://api.github.com/users{/user}{/repo}') + expanded = 'https://api.github.com/users/sigmavirus24/github3.py' + self.assertEqual( + t.expand({'repo': 'github3.py'}, user='sigmavirus24'), + expanded + ) + + def test_str_repr(self): + uri = 'https://api.github.com{/endpoint}' + t = URITemplate(uri) + self.assertEqual(str(t), uri) + self.assertEqual(str(t.variables[0]), '/endpoint') + self.assertEqual(repr(t), 'URITemplate("%s")' % uri) + self.assertEqual(repr(t.variables[0]), 'URIVariable(/endpoint)') + + def test_hash(self): + uri = 'https://api.github.com{/endpoint}' + self.assertEqual(hash(URITemplate(uri)), hash(uri)) + + def test_default_value(self): + uri = 'https://api.github.com/user{/user=sigmavirus24}' + t = URITemplate(uri) + self.assertEqual(t.expand(), + 'https://api.github.com/user/sigmavirus24') + self.assertEqual(t.expand(user='lukasa'), + 'https://api.github.com/user/lukasa') + + def test_query_expansion(self): + t = URITemplate('{foo}') + self.assertEqual( + t.variables[0]._query_expansion('foo', None, False, False), None + ) + + def test_label_path_expansion(self): + t = URITemplate('{foo}') + self.assertEqual( + t.variables[0]._label_path_expansion('foo', None, False, False), + None + ) + + def test_semi_path_expansion(self): + t = URITemplate('{foo}') + v = t.variables[0] + self.assertEqual( + v._semi_path_expansion('foo', None, False, False), + None + ) + t.variables[0].operator = '?' + self.assertEqual( + v._semi_path_expansion('foo', ['bar', 'bogus'], True, False), + 'foo=bar&foo=bogus' + ) + + def test_string_expansion(self): + t = URITemplate('{foo}') + self.assertEqual( + t.variables[0]._string_expansion('foo', None, False, False), + None + ) + + def test_hashability(self): + t = URITemplate('{foo}') + u = URITemplate('{foo}') + d = {t: 1} + d[u] += 1 + self.assertEqual(d, {t: 2}) + + def test_no_mutate(self): + args = {} + t = URITemplate('') + t.expand(args, key=1) + self.assertEqual(args, {}) + + +class TestURIVariable(TestCase): + def setUp(self): + self.v = variable.URIVariable('{foo}') + + def test_post_parse(self): + v = self.v + self.assertEqual(v.join_str, ',') + self.assertEqual(v.operator, '') + self.assertEqual(v.safe, '') + self.assertEqual(v.start, '') + + def test_post_parse_plus(self): + v = self.v + v.operator = '+' + v.post_parse() + self.assertEqual(v.join_str, ',') + self.assertEqual(v.safe, variable.URIVariable.reserved) + self.assertEqual(v.start, '') + + def test_post_parse_octothorpe(self): + v = self.v + v.operator = '#' + v.post_parse() + self.assertEqual(v.join_str, ',') + self.assertEqual(v.safe, variable.URIVariable.reserved) + self.assertEqual(v.start, '#') + + def test_post_parse_question(self): + v = self.v + v.operator = '?' + v.post_parse() + self.assertEqual(v.join_str, '&') + self.assertEqual(v.safe, '') + self.assertEqual(v.start, '?') + + def test_post_parse_ampersand(self): + v = self.v + v.operator = '&' + v.post_parse() + self.assertEqual(v.join_str, '&') + self.assertEqual(v.safe, '') + self.assertEqual(v.start, '&') + + +class TestVariableModule(TestCase): + def test_is_list_of_tuples(self): + l = [(1, 2), (3, 4)] + self.assertEqual(variable.is_list_of_tuples(l), (True, l)) + + l = [1, 2, 3, 4] + self.assertEqual(variable.is_list_of_tuples(l), (False, None)) + + def test_list_test(self): + l = [1, 2, 3, 4] + self.assertEqual(variable.list_test(l), True) + + l = str([1, 2, 3, 4]) + self.assertEqual(variable.list_test(l), False) + + def test_list_of_tuples_test(self): + l = [(1, 2), (3, 4)] + self.assertEqual(variable.dict_test(l), False) + + d = dict(l) + self.assertEqual(variable.dict_test(d), True) + + +class TestAPI(TestCase): + uri = 'https://api.github.com{/endpoint}' + + def test_expand(self): + self.assertEqual(expand(self.uri, {'endpoint': 'users'}), + 'https://api.github.com/users') + + def test_partial(self): + self.assertEqual(partial(self.uri), URITemplate(self.uri)) + uri = self.uri + '/sigmavirus24{/other}' + self.assertEqual( + partial(uri, endpoint='users'), + URITemplate('https://api.github.com/users/sigmavirus24{/other}') + ) + + def test_variables(self): + self.assertEqual(variables(self.uri), + URITemplate(self.uri).variable_names) + + +if __name__ == '__main__': + main() @@ -0,0 +1,31 @@ +[tox] +envlist = + py26, + py27, + py32, + py33, + py34, + py35, + pep8, + +[testenv] +deps = + pytest +commands = py.test {posargs} + +[testenv:pep8] +deps = + flake8 +commands = + flake8 {posargs} uritemplate tests setup.py + +[testenv:release] +deps = + wheel + twine>=1.8.0 +commands = + python setup.py -q sdist bdist_wheel + twine upload --skip-existing dist/* + +[flake8] +exclude = docs/ diff --git a/uritemplate/Android.bp b/uritemplate/Android.bp new file mode 100644 index 0000000..706dba5 --- /dev/null +++ b/uritemplate/Android.bp @@ -0,0 +1,30 @@ +// Copyright 2018 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +python_library { + name: "py-uritemplate", + host_supported: true, + srcs: [ + "*.py", + ], + version: { + py2: { + enabled: true, + }, + py3: { + enabled: true, + }, + }, + pkg_path: "uritemplate", +} + diff --git a/uritemplate/__init__.py b/uritemplate/__init__.py new file mode 100644 index 0000000..40c0320 --- /dev/null +++ b/uritemplate/__init__.py @@ -0,0 +1,26 @@ +""" + +uritemplate +=========== + +The URI templating library for humans. + +See http://uritemplate.rtfd.org/ for documentation + +:copyright: (c) 2013-2015 Ian Cordasco +:license: Modified BSD, see LICENSE for more details + +""" + +__title__ = 'uritemplate' +__author__ = 'Ian Cordasco' +__license__ = 'Modified BSD or Apache License, Version 2.0' +__copyright__ = 'Copyright 2013 Ian Cordasco' +__version__ = '3.0.0' +__version_info__ = tuple(int(i) for i in __version__.split('.') if i.isdigit()) + +from uritemplate.api import ( + URITemplate, expand, partial, variables # noqa: E402 +) + +__all__ = ('URITemplate', 'expand', 'partial', 'variables') diff --git a/uritemplate/api.py b/uritemplate/api.py new file mode 100644 index 0000000..37c7c45 --- /dev/null +++ b/uritemplate/api.py @@ -0,0 +1,71 @@ +""" + +uritemplate.api +=============== + +This module contains the very simple API provided by uritemplate. + +""" +from uritemplate.template import URITemplate + + +def expand(uri, var_dict=None, **kwargs): + """Expand the template with the given parameters. + + :param str uri: The templated URI to expand + :param dict var_dict: Optional dictionary with variables and values + :param kwargs: Alternative way to pass arguments + :returns: str + + Example:: + + expand('https://api.github.com{/end}', {'end': 'users'}) + expand('https://api.github.com{/end}', end='gists') + + .. note:: Passing values by both parts, may override values in + ``var_dict``. For example:: + + expand('https://{var}', {'var': 'val1'}, var='val2') + + ``val2`` will be used instead of ``val1``. + + """ + return URITemplate(uri).expand(var_dict, **kwargs) + + +def partial(uri, var_dict=None, **kwargs): + """Partially expand the template with the given parameters. + + If all of the parameters for the template are not given, return a + partially expanded template. + + :param dict var_dict: Optional dictionary with variables and values + :param kwargs: Alternative way to pass arguments + :returns: :class:`URITemplate` + + Example:: + + t = URITemplate('https://api.github.com{/end}') + t.partial() # => URITemplate('https://api.github.com{/end}') + + """ + return URITemplate(uri).partial(var_dict, **kwargs) + + +def variables(uri): + """Parse the variables of the template. + + This returns all of the variable names in the URI Template. + + :returns: Set of variable names + :rtype: set + + Example:: + + variables('https://api.github.com{/end}) + # => {'end'} + variables('https://api.github.com/repos{/username}{/repository}') + # => {'username', 'repository'} + + """ + return set(URITemplate(uri).variable_names) diff --git a/uritemplate/template.py b/uritemplate/template.py new file mode 100644 index 0000000..c9d7c7e --- /dev/null +++ b/uritemplate/template.py @@ -0,0 +1,150 @@ +""" + +uritemplate.template +==================== + +This module contains the essential inner workings of uritemplate. + +What treasures await you: + +- URITemplate class + +You see a treasure chest of knowledge in front of you. +What do you do? +> + +""" + +import re +from uritemplate.variable import URIVariable + +template_re = re.compile('{([^\}]+)}') + + +def _merge(var_dict, overrides): + if var_dict: + opts = var_dict.copy() + opts.update(overrides) + return opts + return overrides + + +class URITemplate(object): + + """This parses the template and will be used to expand it. + + This is the most important object as the center of the API. + + Example:: + + from uritemplate import URITemplate + import requests + + + t = URITemplate( + 'https://api.github.com/users/sigmavirus24/gists{/gist_id}' + ) + uri = t.expand(gist_id=123456) + resp = requests.get(uri) + for gist in resp.json(): + print(gist['html_url']) + + Please note:: + + str(t) + # 'https://api.github.com/users/sigmavirus24/gists{/gistid}' + repr(t) # is equivalent to + # URITemplate(str(t)) + # Where str(t) is interpreted as the URI string. + + Also, ``URITemplates`` are hashable so they can be used as keys in + dictionaries. + + """ + + def __init__(self, uri): + #: The original URI to be parsed. + self.uri = uri + #: A list of the variables in the URI. They are stored as + #: :class:`URIVariable`\ s + self.variables = [ + URIVariable(m.groups()[0]) for m in template_re.finditer(self.uri) + ] + #: A set of variable names in the URI. + self.variable_names = set() + for variable in self.variables: + self.variable_names.update(variable.variable_names) + + def __repr__(self): + return 'URITemplate("%s")' % self + + def __str__(self): + return self.uri + + def __eq__(self, other): + return self.uri == other.uri + + def __hash__(self): + return hash(self.uri) + + def _expand(self, var_dict, replace): + if not self.variables: + return self.uri + + expansion = var_dict + expanded = {} + for v in self.variables: + expanded.update(v.expand(expansion)) + + def replace_all(match): + return expanded.get(match.groups()[0], '') + + def replace_partial(match): + match = match.groups()[0] + var = '{%s}' % match + return expanded.get(match) or var + + replace = replace_partial if replace else replace_all + + return template_re.sub(replace, self.uri) + + def expand(self, var_dict=None, **kwargs): + """Expand the template with the given parameters. + + :param dict var_dict: Optional dictionary with variables and values + :param kwargs: Alternative way to pass arguments + :returns: str + + Example:: + + t = URITemplate('https://api.github.com{/end}') + t.expand({'end': 'users'}) + t.expand(end='gists') + + .. note:: Passing values by both parts, may override values in + ``var_dict``. For example:: + + expand('https://{var}', {'var': 'val1'}, var='val2') + + ``val2`` will be used instead of ``val1``. + + """ + return self._expand(_merge(var_dict, kwargs), False) + + def partial(self, var_dict=None, **kwargs): + """Partially expand the template with the given parameters. + + If all of the parameters for the template are not given, return a + partially expanded template. + + :param dict var_dict: Optional dictionary with variables and values + :param kwargs: Alternative way to pass arguments + :returns: :class:`URITemplate` + + Example:: + + t = URITemplate('https://api.github.com{/end}') + t.partial() # => URITemplate('https://api.github.com{/end}') + + """ + return URITemplate(self._expand(_merge(var_dict, kwargs), True)) diff --git a/uritemplate/variable.py b/uritemplate/variable.py new file mode 100644 index 0000000..1842830 --- /dev/null +++ b/uritemplate/variable.py @@ -0,0 +1,384 @@ +""" + +uritemplate.variable +==================== + +This module contains the URIVariable class which powers the URITemplate class. + +What treasures await you: + +- URIVariable class + +You see a hammer in front of you. +What do you do? +> + +""" + +import collections +import sys + +if (2, 6) <= sys.version_info < (2, 8): + import urllib +elif (3, 3) <= sys.version_info < (4, 0): + import urllib.parse as urllib + + +class URIVariable(object): + + """This object validates everything inside the URITemplate object. + + It validates template expansions and will truncate length as decided by + the template. + + Please note that just like the :class:`URITemplate <URITemplate>`, this + object's ``__str__`` and ``__repr__`` methods do not return the same + information. Calling ``str(var)`` will return the original variable. + + This object does the majority of the heavy lifting. The ``URITemplate`` + object finds the variables in the URI and then creates ``URIVariable`` + objects. Expansions of the URI are handled by each ``URIVariable`` + object. ``URIVariable.expand()`` returns a dictionary of the original + variable and the expanded value. Check that method's documentation for + more information. + + """ + + operators = ('+', '#', '.', '/', ';', '?', '&', '|', '!', '@') + reserved = ":/?#[]@!$&'()*+,;=" + + def __init__(self, var): + #: The original string that comes through with the variable + self.original = var + #: The operator for the variable + self.operator = '' + #: List of safe characters when quoting the string + self.safe = '' + #: List of variables in this variable + self.variables = [] + #: List of variable names + self.variable_names = [] + #: List of defaults passed in + self.defaults = {} + # Parse the variable itself. + self.parse() + self.post_parse() + + def __repr__(self): + return 'URIVariable(%s)' % self + + def __str__(self): + return self.original + + def parse(self): + """Parse the variable. + + This finds the: + - operator, + - set of safe characters, + - variables, and + - defaults. + + """ + var_list = self.original + if self.original[0] in URIVariable.operators: + self.operator = self.original[0] + var_list = self.original[1:] + + if self.operator in URIVariable.operators[:2]: + self.safe = URIVariable.reserved + + var_list = var_list.split(',') + + for var in var_list: + default_val = None + name = var + if '=' in var: + name, default_val = tuple(var.split('=', 1)) + + explode = False + if name.endswith('*'): + explode = True + name = name[:-1] + + prefix = None + if ':' in name: + name, prefix = tuple(name.split(':', 1)) + prefix = int(prefix) + + if default_val: + self.defaults[name] = default_val + + self.variables.append( + (name, {'explode': explode, 'prefix': prefix}) + ) + + self.variable_names = [varname for (varname, _) in self.variables] + + def post_parse(self): + """Set ``start``, ``join_str`` and ``safe`` attributes. + + After parsing the variable, we need to set up these attributes and it + only makes sense to do it in a more easily testable way. + """ + self.safe = '' + self.start = self.join_str = self.operator + if self.operator == '+': + self.start = '' + if self.operator in ('+', '#', ''): + self.join_str = ',' + if self.operator == '#': + self.start = '#' + if self.operator == '?': + self.start = '?' + self.join_str = '&' + + if self.operator in ('+', '#'): + self.safe = URIVariable.reserved + + def _query_expansion(self, name, value, explode, prefix): + """Expansion method for the '?' and '&' operators.""" + if value is None: + return None + + tuples, items = is_list_of_tuples(value) + + safe = self.safe + if list_test(value) and not tuples: + if not value: + return None + if explode: + return self.join_str.join( + '%s=%s' % (name, quote(v, safe)) for v in value + ) + else: + value = ','.join(quote(v, safe) for v in value) + return '%s=%s' % (name, value) + + if dict_test(value) or tuples: + if not value: + return None + items = items or sorted(value.items()) + if explode: + return self.join_str.join( + '%s=%s' % ( + quote(k, safe), quote(v, safe) + ) for k, v in items + ) + else: + value = ','.join( + '%s,%s' % ( + quote(k, safe), quote(v, safe) + ) for k, v in items + ) + return '%s=%s' % (name, value) + + if value: + value = value[:prefix] if prefix else value + return '%s=%s' % (name, quote(value, safe)) + return name + '=' + + def _label_path_expansion(self, name, value, explode, prefix): + """Label and path expansion method. + + Expands for operators: '/', '.' + + """ + join_str = self.join_str + safe = self.safe + + if value is None or (len(value) == 0 and value != ''): + return None + + tuples, items = is_list_of_tuples(value) + + if list_test(value) and not tuples: + if not explode: + join_str = ',' + + expanded = join_str.join( + quote(v, safe) for v in value if value is not None + ) + return expanded if expanded else None + + if dict_test(value) or tuples: + items = items or sorted(value.items()) + format_str = '%s=%s' + if not explode: + format_str = '%s,%s' + join_str = ',' + + expanded = join_str.join( + format_str % ( + quote(k, safe), quote(v, safe) + ) for k, v in items if v is not None + ) + return expanded if expanded else None + + value = value[:prefix] if prefix else value + return quote(value, safe) + + def _semi_path_expansion(self, name, value, explode, prefix): + """Expansion method for ';' operator.""" + join_str = self.join_str + safe = self.safe + + if value is None: + return None + + if self.operator == '?': + join_str = '&' + + tuples, items = is_list_of_tuples(value) + + if list_test(value) and not tuples: + if explode: + expanded = join_str.join( + '%s=%s' % ( + name, quote(v, safe) + ) for v in value if v is not None + ) + return expanded if expanded else None + else: + value = ','.join(quote(v, safe) for v in value) + return '%s=%s' % (name, value) + + if dict_test(value) or tuples: + items = items or sorted(value.items()) + + if explode: + return join_str.join( + '%s=%s' % ( + quote(k, safe), quote(v, safe) + ) for k, v in items if v is not None + ) + else: + expanded = ','.join( + '%s,%s' % ( + quote(k, safe), quote(v, safe) + ) for k, v in items if v is not None + ) + return '%s=%s' % (name, expanded) + + value = value[:prefix] if prefix else value + if value: + return '%s=%s' % (name, quote(value, safe)) + + return name + + def _string_expansion(self, name, value, explode, prefix): + if value is None: + return None + + tuples, items = is_list_of_tuples(value) + + if list_test(value) and not tuples: + return ','.join(quote(v, self.safe) for v in value) + + if dict_test(value) or tuples: + items = items or sorted(value.items()) + format_str = '%s=%s' if explode else '%s,%s' + + return ','.join( + format_str % ( + quote(k, self.safe), quote(v, self.safe) + ) for k, v in items + ) + + value = value[:prefix] if prefix else value + return quote(value, self.safe) + + def expand(self, var_dict=None): + """Expand the variable in question. + + Using ``var_dict`` and the previously parsed defaults, expand this + variable and subvariables. + + :param dict var_dict: dictionary of key-value pairs to be used during + expansion + :returns: dict(variable=value) + + Examples:: + + # (1) + v = URIVariable('/var') + expansion = v.expand({'var': 'value'}) + print(expansion) + # => {'/var': '/value'} + + # (2) + v = URIVariable('?var,hello,x,y') + expansion = v.expand({'var': 'value', 'hello': 'Hello World!', + 'x': '1024', 'y': '768'}) + print(expansion) + # => {'?var,hello,x,y': + # '?var=value&hello=Hello%20World%21&x=1024&y=768'} + + """ + return_values = [] + + for name, opts in self.variables: + value = var_dict.get(name, None) + if not value and value != '' and name in self.defaults: + value = self.defaults[name] + + if value is None: + continue + + expanded = None + if self.operator in ('/', '.'): + expansion = self._label_path_expansion + elif self.operator in ('?', '&'): + expansion = self._query_expansion + elif self.operator == ';': + expansion = self._semi_path_expansion + else: + expansion = self._string_expansion + + expanded = expansion(name, value, opts['explode'], opts['prefix']) + + if expanded is not None: + return_values.append(expanded) + + value = '' + if return_values: + value = self.start + self.join_str.join(return_values) + return {self.original: value} + + +def is_list_of_tuples(value): + if (not value or + not isinstance(value, (list, tuple)) or + not all(isinstance(t, tuple) and len(t) == 2 for t in value)): + return False, None + + return True, value + + +def list_test(value): + return isinstance(value, (list, tuple)) + + +def dict_test(value): + return isinstance(value, (dict, collections.MutableMapping)) + + +try: + texttype = unicode +except NameError: # Python 3 + texttype = str + +stringlikes = (texttype, bytes) + + +def _encode(value, encoding='utf-8'): + if (isinstance(value, texttype) and + getattr(value, 'encode', None) is not None): + return value.encode(encoding) + return value + + +def quote(value, safe): + if not isinstance(value, stringlikes): + value = str(value) + return urllib.quote(_encode(value), safe) |