Merge commit '623fce3' into uritemplate 3.0.0. Initial commitcd  of uritemplate 3.0.0. am: 43c5a5f39f
am: eaee30993c

Change-Id: I3b3a1a74bf31a87ade2647fb0090d6cf7eb52507
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)