aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcylan <cylan@google.com>2018-06-13 11:46:30 -0700
committerandroid-build-merger <android-build-merger@google.com>2018-06-13 11:46:30 -0700
commitab37609af41f79288be1573bb1de4ae68f36879a (patch)
tree5b580b38af5f26a80363afdf694cb666d95a3d7e
parent4edb22282e4cfaa9250d3fdb2d2f7be9c0a34fb4 (diff)
parent87f7b2447ed02e26bfe1fc3db379d8e1ff166572 (diff)
downloaduritemplates-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
-rw-r--r--.gitignore12
-rw-r--r--.travis.yml31
-rw-r--r--AUTHORS.rst4
-rw-r--r--HISTORY.rst68
-rw-r--r--LICENSE3
-rw-r--r--LICENSE.APACHE202
-rw-r--r--LICENSE.BSD23
-rw-r--r--MANIFEST.in12
-rw-r--r--METADATA16
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--MODULE_LICENSE_BSD0
-rw-r--r--NOTICE230
-rw-r--r--README.rst64
-rw-r--r--docs/Makefile130
-rw-r--r--docs/conf.py240
-rw-r--r--docs/index.rst81
-rw-r--r--old/uritemplate.py/uritemplatepy-setup.py31
-rw-r--r--setup.cfg2
-rw-r--r--setup.py39
-rw-r--r--tests/fixtures/README.md91
-rw-r--r--tests/fixtures/extended-tests.json118
-rw-r--r--tests/fixtures/json2xml.xslt201
-rw-r--r--tests/fixtures/negative-tests.json57
-rw-r--r--tests/fixtures/spec-examples-by-section.json439
-rw-r--r--tests/fixtures/spec-examples.json218
-rw-r--r--tests/fixtures/transform-json-tests.xslt51
-rw-r--r--tests/test_from_fixtures.py116
-rw-r--r--tests/test_uritemplate.py589
-rw-r--r--tox.ini31
-rw-r--r--uritemplate/Android.bp30
-rw-r--r--uritemplate/__init__.py26
-rw-r--r--uritemplate/api.py71
-rw-r--r--uritemplate/template.py150
-rw-r--r--uritemplate/variable.py384
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..26e92ad
--- /dev/null
+++ b/LICENSE
@@ -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
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..b7c4c5f
--- /dev/null
+++ b/NOTICE
@@ -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', '/\*(.*?)\*/', '(''|&quot;)(([^\\]|\\[\\&quot;''/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>&#10;</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="\\([\\&quot;'/btnvfr])" flags="s">
+ <xsl:matching-substring>
+ <xsl:variable name="s" select="regex-group(1)"/>
+ <xsl:choose>
+ <xsl:when test="$s=('\', '&quot;', '''', '/')">
+ <xsl:value-of select="regex-group(1)"/>
+ </xsl:when>
+ <xsl:when test="$s='b'">
+ <!--xsl:text>&#8;</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>&#9;</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='n'">
+ <xsl:text>&#10;</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='v'">
+ <!--xsl:text>&#11;</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>&#12;</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>&#13;</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}&amp;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()
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..9d86da0
--- /dev/null
+++ b/tox.ini
@@ -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)