New Upstream Release - python-strictyaml

Ready changes

Summary

Merged new upstream version: 1.7.3 (was: 1.6.1).

Resulting package

Built on 2023-03-16T01:31 (took 3m55s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-strictyaml

Lintian Result

Diff

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..5b672b1
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.story linguist-language=YAML
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..5487739
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1,7 @@
+Feel free to raise an issue for any of the following:
+
+* Support request
+* Feature request
+* Argument
+* Bug
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..19af17a
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,24 @@
+Qualities of an ideal pull request:
+
+Code changes come with a new story or an amendment to an existing story
+in order to exercise the code.
+
+Commit messages that adhere to the following pattern:
+
+TYPEOFCOMMIT : Quick explanation of what changed and why.
+
+Where TYPEOFCOMMIT is one of the following:
+
+* FEATURE - for new features and accompanying stories.
+* BUG - for bugfixes and accompanying stories (or amendments to an existing story).
+* DOCS - for changes to README, etc. and non-functional changes to stories.
+* MISC - Anything else.
+
+Ideal code qualities:
+
+* Loosely coupled
+* DRY
+* Clear and straightforward is preferable to clever
+* Docstrings that explain why rather than what
+* Clearly disambiguated Variable/method/class names
+* Passes flake8 linting with < 100 character lines
diff --git a/.github/workflows/envirotest.yml b/.github/workflows/envirotest.yml
new file mode 100644
index 0000000..ea8796b
--- /dev/null
+++ b/.github/workflows/envirotest.yml
@@ -0,0 +1,26 @@
+name: Envirotest
+
+on:
+  workflow_dispatch:
+  #schedule:
+    # Run every friday evening, to catch before weekend
+    #- cron: '0 17 * * Fri'
+
+jobs:
+  envirotest:
+    timeout-minutes: 30
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout repo
+        uses: actions/checkout@v2
+
+      - name: build
+        run: |
+          mkdir -p ~/.ssh/
+          touch ~/.ssh/id_rsa
+          touch ~/.ssh/id_rsa.pub
+          ./key.sh make
+
+      - name: envirotest
+        run: |
+          ./key.sh envirotest full
diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml
new file mode 100644
index 0000000..9d4646e
--- /dev/null
+++ b/.github/workflows/regression.yml
@@ -0,0 +1,27 @@
+name: Regression
+
+on:
+  workflow_dispatch:
+  push:
+    branches:
+    - master
+  pull_request:
+
+jobs:
+  regression:
+    timeout-minutes: 30
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout repo
+        uses: actions/checkout@v2
+
+      - name: build
+        run: |
+          mkdir -p ~/.ssh/
+          touch ~/.ssh/id_rsa
+          touch ~/.ssh/id_rsa.pub
+          ./key.sh make
+
+      - name: regression
+        run: |
+          ./key.sh regression
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f0e871d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+build/
+dist/
+.cache
+strictyaml.egg-info/
+tests/.hitch
+state/
+__pycache__
+*.pyc
+*.kate-swp
+hitch/gen
+hitch/personalsettings.yml
+temp/
+.vscode/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1323c52..47f0064 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,11 @@
 
 ### Latest
 
+* BUGFIX : Fully parse URL, fix #141
+
+
+### 1.6.1
+
 * BUG: Don't check indentation consistency when allowing flow.
 
 
diff --git a/MANIFEST.in b/MANIFEST.in
index 2c56047..5caeeee 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,4 @@
-include VERSION
-include LICENSE.txt
-include README.md
-recursive-include hitch *
-prune hitch/__pycache__
-prune hitch/gen
+include VERSION LICENSE.txt README.md CHANGELOG.md
+recursive-include strictyaml/
+global-exclude __pycache__
+global-exclude *.py[co]
diff --git a/VERSION b/VERSION
index 2eda823..661e7ae 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.6.1
\ No newline at end of file
+1.7.3
diff --git a/debian/changelog b/debian/changelog
index f00c693..b372d0c 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-strictyaml (1.7.3-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 16 Mar 2023 01:27:51 -0000
+
 python-strictyaml (1.6.1-3) unstable; urgency=medium
 
   * Update metadata
diff --git a/docs/public/changelog.md b/docs/public/changelog.md
index 1323c52..47f0064 100644
--- a/docs/public/changelog.md
+++ b/docs/public/changelog.md
@@ -3,6 +3,11 @@
 
 ### Latest
 
+* BUGFIX : Fully parse URL, fix #141
+
+
+### 1.6.1
+
 * BUG: Don't check indentation consistency when allowing flow.
 
 
diff --git a/docs/public/features-removed.md b/docs/public/features-removed.md
index d3b7617..100da9d 100644
--- a/docs/public/features-removed.md
+++ b/docs/public/features-removed.md
@@ -5,7 +5,7 @@ title: What YAML features does StrictYAML remove?
 StrictYAML restricts you from parsing a number of things which
 the YAML specification says should be parsed. An issue has
 been [raised](https://github.com/yaml/YAML2/issues/8) by
-[David Seaward](https://inkwell.za.net/) about this critique
+[David Seaward](https://github.com/lofidevops) about this critique
 on the official YAML repository.
 
 This document lists those of those features:
diff --git a/docs/public/index.md b/docs/public/index.md
index fa11d63..cbaa8bb 100644
--- a/docs/public/index.md
+++ b/docs/public/index.md
@@ -2,7 +2,10 @@
 title: StrictYAML
 ---
 
-{{< github-stars user="crdoconnor" project="strictyaml" >}}
+
+<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/crdoconnor/strictyaml?style=social"> 
+<img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/strictyaml">
+
 
 
 StrictYAML is a [type-safe](https://en.wikipedia.org/wiki/Type_safety) YAML parser
diff --git a/docs/public/using/alpha/compound/fixed-length-sequences.md b/docs/public/using/alpha/compound/fixed-length-sequences.md
index a40f4c4..e2a5bd1 100644
--- a/docs/public/using/alpha/compound/fixed-length-sequences.md
+++ b/docs/public/using/alpha/compound/fixed-length-sequences.md
@@ -1,6 +1,5 @@
 ---
 title: Fixed length sequences (FixedSeq)
-type: using
 ---
 
 
@@ -128,6 +127,9 @@ found a sequence of 2 elements
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/fixed-sequence.story">fixed-sequence.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/fixed-sequence.story">fixed-sequence.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/compound/map-combined.md b/docs/public/using/alpha/compound/map-combined.md
index 9adb2de..0dd1669 100644
--- a/docs/public/using/alpha/compound/map-combined.md
+++ b/docs/public/using/alpha/compound/map-combined.md
@@ -1,11 +1,10 @@
 ---
 title: Mappings combining defined and undefined keys (MapCombined)
-type: using
 ---
 
-{{< warning title="Experimental" >}}
-This feature is in alpha. The API may change on a minor version increment.
-{{< /warning >}}
+!!! warning "Experimental"
+
+    This feature is in alpha. The API may change on a minor version increment.
 
 
 When you wish to support arbitrary optional keys in
@@ -184,6 +183,9 @@ found arbitrary text
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/map-combined.story">map-combined.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/map-combined.story">map-combined.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/compound/map-pattern.md b/docs/public/using/alpha/compound/map-pattern.md
index 0f3f7c2..dbf36f2 100644
--- a/docs/public/using/alpha/compound/map-pattern.md
+++ b/docs/public/using/alpha/compound/map-pattern.md
@@ -1,6 +1,5 @@
 ---
 title: Mappings with arbitrary key names (MapPattern)
-type: using
 ---
 
 
@@ -244,6 +243,9 @@ found arbitrary text
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/mappattern.story">mappattern.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/mappattern.story">mappattern.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/compound/mapping-with-slug-keys.md b/docs/public/using/alpha/compound/mapping-with-slug-keys.md
index 0296c3d..4a960a6 100644
--- a/docs/public/using/alpha/compound/mapping-with-slug-keys.md
+++ b/docs/public/using/alpha/compound/mapping-with-slug-keys.md
@@ -1,11 +1,10 @@
 ---
 title: Mapping with defined keys and a custom key validator (Map)
-type: using
 ---
 
-{{< warning title="Experimental" >}}
-This feature is in alpha. The API may change on a minor version increment.
-{{< /warning >}}
+!!! warning "Experimental"
+
+    This feature is in alpha. The API may change on a minor version increment.
 
 
 A typical mapping except that the key values are determined
@@ -64,6 +63,9 @@ Ensure(load(yaml_snippet, schema).data).equals(
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/map-with-key-validator.story">map-with-key-validator.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/map-with-key-validator.story">map-with-key-validator.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/compound/mapping-yaml-object.md b/docs/public/using/alpha/compound/mapping-yaml-object.md
index 4b3a31a..9a7ef34 100644
--- a/docs/public/using/alpha/compound/mapping-yaml-object.md
+++ b/docs/public/using/alpha/compound/mapping-yaml-object.md
@@ -1,6 +1,5 @@
 ---
 title: Using a YAML object of a parsed mapping
-type: using
 ---
 
 
@@ -160,6 +159,9 @@ Ensure(len(load(yaml_snippet, schema))).equals(3)
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/mapping-representation.story">mapping-representation.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/mapping-representation.story">mapping-representation.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/compound/mapping.md b/docs/public/using/alpha/compound/mapping.md
index 113fe86..9107cc9 100644
--- a/docs/public/using/alpha/compound/mapping.md
+++ b/docs/public/using/alpha/compound/mapping.md
@@ -1,6 +1,5 @@
 ---
 title: Mappings with defined keys (Map)
-type: using
 ---
 
 
@@ -269,6 +268,9 @@ assert as_document(OrderedDict([(u"â", 1), ("b", 2), ("c", 3)]), schema_2).as_y
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/map.story">map.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/map.story">map.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/compound/optional-keys-with-defaults.md b/docs/public/using/alpha/compound/optional-keys-with-defaults.md
index 76160b8..c5fa3f8 100644
--- a/docs/public/using/alpha/compound/optional-keys-with-defaults.md
+++ b/docs/public/using/alpha/compound/optional-keys-with-defaults.md
@@ -1,11 +1,10 @@
 ---
 title: Optional keys with defaults (Map/Optional)
-type: using
 ---
 
-{{< warning title="Experimental" >}}
-This feature is in alpha. The API may change on a minor version increment.
-{{< /warning >}}
+!!! warning "Experimental"
+
+    This feature is in alpha. The API may change on a minor version increment.
 
 
 
@@ -83,6 +82,9 @@ Ensure(load(yaml_snippet, schema).data).equals(OrderedDict([("a", 1), ("b", None
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/optional-with-defaults.story">optional-with-defaults.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/optional-with-defaults.story">optional-with-defaults.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/compound/optional-keys.md b/docs/public/using/alpha/compound/optional-keys.md
index e522a0a..63db59e 100644
--- a/docs/public/using/alpha/compound/optional-keys.md
+++ b/docs/public/using/alpha/compound/optional-keys.md
@@ -1,6 +1,5 @@
 ---
 title: Validating optional keys in mappings (Map)
-type: using
 ---
 
 
@@ -125,6 +124,9 @@ unexpected key not in schema 'c'
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/optional.story">optional.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/optional.story">optional.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/compound/sequences-of-unique-items.md b/docs/public/using/alpha/compound/sequences-of-unique-items.md
index a67c87c..acf9097 100644
--- a/docs/public/using/alpha/compound/sequences-of-unique-items.md
+++ b/docs/public/using/alpha/compound/sequences-of-unique-items.md
@@ -1,6 +1,5 @@
 ---
 title: Sequences of unique items (UniqueSeq)
-type: using
 ---
 
 
@@ -118,6 +117,9 @@ Expecting all unique items, but duplicates were found in '['A', 'B', 'B']'.
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/unique-sequence.story">unique-sequence.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/unique-sequence.story">unique-sequence.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/compound/sequences.md b/docs/public/using/alpha/compound/sequences.md
index 4e43ed0..1f0bd80 100644
--- a/docs/public/using/alpha/compound/sequences.md
+++ b/docs/public/using/alpha/compound/sequences.md
@@ -1,6 +1,5 @@
 ---
 title: Sequence/list validator (Seq)
-type: using
 ---
 
 
@@ -228,6 +227,9 @@ found an arbitrary number
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/sequence.story">sequence.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/sequence.story">sequence.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/compound/update.md b/docs/public/using/alpha/compound/update.md
index c450a6f..45f01c5 100644
--- a/docs/public/using/alpha/compound/update.md
+++ b/docs/public/using/alpha/compound/update.md
@@ -1,6 +1,5 @@
 ---
 title: Updating document with a schema
-type: using
 ---
 
 
@@ -138,6 +137,9 @@ assert doc['b'] == 9
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/update-with-schema.story">update-with-schema.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/update-with-schema.story">update-with-schema.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/howto/build-yaml-document.md b/docs/public/using/alpha/howto/build-yaml-document.md
index 1850c7d..8a5386e 100644
--- a/docs/public/using/alpha/howto/build-yaml-document.md
+++ b/docs/public/using/alpha/howto/build-yaml-document.md
@@ -1,6 +1,5 @@
 ---
 title: Build a YAML document from scratch in code
-type: using
 ---
 
 
@@ -111,6 +110,9 @@ Ensure(yaml.start_line).equals(1)
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/build-yaml-document-from-scratch.story">build-yaml-document-from-scratch.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/build-yaml-document-from-scratch.story">build-yaml-document-from-scratch.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/howto/either-or-validation.md b/docs/public/using/alpha/howto/either-or-validation.md
index 4e744ef..9053089 100644
--- a/docs/public/using/alpha/howto/either-or-validation.md
+++ b/docs/public/using/alpha/howto/either-or-validation.md
@@ -1,6 +1,5 @@
 ---
 title: Either/or schema validation of different, equally valid different kinds of YAML
-type: using
 ---
 
 
@@ -156,6 +155,9 @@ assert yaml['a'] == 5
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/or.story">or.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/or.story">or.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/howto/label-exceptions.md b/docs/public/using/alpha/howto/label-exceptions.md
index aeb310a..3a3effa 100644
--- a/docs/public/using/alpha/howto/label-exceptions.md
+++ b/docs/public/using/alpha/howto/label-exceptions.md
@@ -1,6 +1,5 @@
 ---
 title: Labeling exceptions
-type: using
 ---
 
 
@@ -52,6 +51,9 @@ found a sequence
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/handle-exceptions.story">handle-exceptions.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/handle-exceptions.story">handle-exceptions.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/howto/merge-yaml-documents.md b/docs/public/using/alpha/howto/merge-yaml-documents.md
index 29421db..9ef4d72 100644
--- a/docs/public/using/alpha/howto/merge-yaml-documents.md
+++ b/docs/public/using/alpha/howto/merge-yaml-documents.md
@@ -1,6 +1,5 @@
 ---
 title: Merge YAML documents
-type: using
 ---
 
 
@@ -53,6 +52,9 @@ c:
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/merge-documents.story">merge-documents.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/merge-documents.story">merge-documents.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/howto/revalidation.md b/docs/public/using/alpha/howto/revalidation.md
index 0a35075..5c15c2a 100644
--- a/docs/public/using/alpha/howto/revalidation.md
+++ b/docs/public/using/alpha/howto/revalidation.md
@@ -1,6 +1,5 @@
 ---
 title: Revalidate an already validated document
-type: using
 ---
 
 
@@ -100,6 +99,9 @@ unexpected key not in schema 'France'
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/revalidation.story">revalidation.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/revalidation.story">revalidation.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/howto/roundtripping.md b/docs/public/using/alpha/howto/roundtripping.md
index 38ab847..baf8d8b 100644
--- a/docs/public/using/alpha/howto/roundtripping.md
+++ b/docs/public/using/alpha/howto/roundtripping.md
@@ -1,6 +1,5 @@
 ---
 title: Reading in YAML, editing it and writing it back out
-type: using
 ---
 
 
@@ -217,6 +216,9 @@ c:
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/roundtrip.story">roundtrip.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/roundtrip.story">roundtrip.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/howto/what-line.md b/docs/public/using/alpha/howto/what-line.md
index d9b9d9e..ecdb3e7 100644
--- a/docs/public/using/alpha/howto/what-line.md
+++ b/docs/public/using/alpha/howto/what-line.md
@@ -1,6 +1,5 @@
 ---
 title: Get line numbers of YAML elements
-type: using
 ---
 
 
@@ -128,6 +127,9 @@ a: |
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/whatline.story">whatline.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/whatline.story">whatline.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/howto/without-a-schema.md b/docs/public/using/alpha/howto/without-a-schema.md
index e9bfa58..9ff5940 100644
--- a/docs/public/using/alpha/howto/without-a-schema.md
+++ b/docs/public/using/alpha/howto/without-a-schema.md
@@ -1,6 +1,5 @@
 ---
 title: Parsing YAML without a schema
-type: using
 ---
 
 
@@ -73,6 +72,9 @@ Ensure(load(yaml_snippet, MapPattern(Str(), Any()))).equals({"a": {"x": "9", "y"
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/non-schema-validation.story">non-schema-validation.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/non-schema-validation.story">non-schema-validation.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/restrictions/disallowed-yaml.md b/docs/public/using/alpha/restrictions/disallowed-yaml.md
index 726732c..a5b62ab 100644
--- a/docs/public/using/alpha/restrictions/disallowed-yaml.md
+++ b/docs/public/using/alpha/restrictions/disallowed-yaml.md
@@ -1,6 +1,5 @@
 ---
 title: Disallowed YAML
-type: using
 ---
 
 
@@ -210,6 +209,9 @@ Found mapping with indentation inconsistent with previous mapping
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/disallow.story">disallow.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/disallow.story">disallow.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/restrictions/duplicate-keys.md b/docs/public/using/alpha/restrictions/duplicate-keys.md
index b34cf6c..3eeef6c 100644
--- a/docs/public/using/alpha/restrictions/duplicate-keys.md
+++ b/docs/public/using/alpha/restrictions/duplicate-keys.md
@@ -1,6 +1,5 @@
 ---
 title: Duplicate keys
-type: using
 ---
 
 
@@ -78,6 +77,9 @@ Duplicate key 'a' found
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/duplicatekeys.story">duplicatekeys.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/duplicatekeys.story">duplicatekeys.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/restrictions/loading-dirty-yaml.md b/docs/public/using/alpha/restrictions/loading-dirty-yaml.md
index 3ef4966..35a82d0 100644
--- a/docs/public/using/alpha/restrictions/loading-dirty-yaml.md
+++ b/docs/public/using/alpha/restrictions/loading-dirty-yaml.md
@@ -1,6 +1,5 @@
 ---
 title: Dirty load
-type: using
 ---
 
 
@@ -43,6 +42,9 @@ assert dirty_load(yaml_snippet, schema, allow_flow_style=True) == {"foo": {"a":
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/dirty-load.story">dirty-load.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/dirty-load.story">dirty-load.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/boolean.md b/docs/public/using/alpha/scalar/boolean.md
index 96909df..0d7ba0e 100644
--- a/docs/public/using/alpha/scalar/boolean.md
+++ b/docs/public/using/alpha/scalar/boolean.md
@@ -1,6 +1,5 @@
 ---
 title: Boolean (Bool)
-type: using
 ---
 
 
@@ -166,6 +165,9 @@ found arbitrary text
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/boolean.story">boolean.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/boolean.story">boolean.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/comma-separated.md b/docs/public/using/alpha/scalar/comma-separated.md
index 16f4cf9..57cb495 100644
--- a/docs/public/using/alpha/scalar/comma-separated.md
+++ b/docs/public/using/alpha/scalar/comma-separated.md
@@ -1,6 +1,5 @@
 ---
 title: Parsing comma separated items (CommaSeparated)
-type: using
 ---
 
 
@@ -182,6 +181,9 @@ expected string or list, got '1' of type 'int'
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/commaseparated.story">commaseparated.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/commaseparated.story">commaseparated.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/datetime.md b/docs/public/using/alpha/scalar/datetime.md
index d78a86f..edc5abd 100644
--- a/docs/public/using/alpha/scalar/datetime.md
+++ b/docs/public/using/alpha/scalar/datetime.md
@@ -1,6 +1,5 @@
 ---
 title: Datetimes (Datetime)
-type: using
 ---
 
 
@@ -163,6 +162,9 @@ expected a datetime, got '55' of type 'int'
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/datetime.story">datetime.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/datetime.story">datetime.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/decimal.md b/docs/public/using/alpha/scalar/decimal.md
index f0a0d61..c1ac5ef 100644
--- a/docs/public/using/alpha/scalar/decimal.md
+++ b/docs/public/using/alpha/scalar/decimal.md
@@ -1,6 +1,5 @@
 ---
 title: Decimal numbers (Decimal)
-type: using
 ---
 
 
@@ -145,6 +144,9 @@ found arbitrary text
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/decimal.story">decimal.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/decimal.story">decimal.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/email-and-url.md b/docs/public/using/alpha/scalar/email-and-url.md
index 0db5fdf..6e26c38 100644
--- a/docs/public/using/alpha/scalar/email-and-url.md
+++ b/docs/public/using/alpha/scalar/email-and-url.md
@@ -1,6 +1,5 @@
 ---
 title: Email and URL validators
-type: using
 ---
 
 
@@ -24,13 +23,13 @@ Parsed:
 
 ```yaml
 a: billg@microsoft.com
-b: http://www.twitter.com/@realDonaldTrump
+b: https://user:pass@example.com:443/path?k=v#frag
 
 ```
 
 
 ```python
-Ensure(load(yaml_snippet, schema)).equals({"a": "billg@microsoft.com", "b": "http://www.twitter.com/@realDonaldTrump"})
+Ensure(load(yaml_snippet, schema)).equals({"a": "billg@microsoft.com", "b": "https://user:pass@example.com:443/path?k=v#frag"})
 
 ```
 
@@ -65,6 +64,9 @@ found non-matching string
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/email-url.story">email-url.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/email-url.story">email-url.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/empty.md b/docs/public/using/alpha/scalar/empty.md
index 5869bda..8e8052d 100644
--- a/docs/public/using/alpha/scalar/empty.md
+++ b/docs/public/using/alpha/scalar/empty.md
@@ -1,6 +1,5 @@
 ---
 title: Empty key validation
-type: using
 ---
 
 
@@ -180,6 +179,9 @@ a:
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/empty.story">empty.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/empty.story">empty.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/enum.md b/docs/public/using/alpha/scalar/enum.md
index f56acad..b632768 100644
--- a/docs/public/using/alpha/scalar/enum.md
+++ b/docs/public/using/alpha/scalar/enum.md
@@ -1,6 +1,5 @@
 ---
 title: Enumerated scalars (Enum)
-type: using
 ---
 
 
@@ -178,6 +177,9 @@ Got 'D' when  expecting one of: A, B, C
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/enum.story">enum.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/enum.story">enum.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/float.md b/docs/public/using/alpha/scalar/float.md
index e666953..1b9e2fd 100644
--- a/docs/public/using/alpha/scalar/float.md
+++ b/docs/public/using/alpha/scalar/float.md
@@ -1,6 +1,5 @@
 ---
 title: Floating point numbers (Float)
-type: using
 ---
 
 
@@ -295,6 +294,9 @@ a: yes
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/float.story">float.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/float.story">float.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/hexadecimal-integer.md b/docs/public/using/alpha/scalar/hexadecimal-integer.md
index 217e74f..b97d2ba 100644
--- a/docs/public/using/alpha/scalar/hexadecimal-integer.md
+++ b/docs/public/using/alpha/scalar/hexadecimal-integer.md
@@ -1,6 +1,5 @@
 ---
 title: Hexadecimal Integers (HexInt)
-type: using
 ---
 
 
@@ -59,6 +58,9 @@ Ensure(load(yaml_snippet, schema).as_yaml()).equals("x: 0X1A\n")
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/scalar-hexadecimal-integer.story">scalar-hexadecimal-integer.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/scalar-hexadecimal-integer.story">scalar-hexadecimal-integer.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/integer.md b/docs/public/using/alpha/scalar/integer.md
index 6f6e9fe..cc90812 100644
--- a/docs/public/using/alpha/scalar/integer.md
+++ b/docs/public/using/alpha/scalar/integer.md
@@ -1,6 +1,5 @@
 ---
 title: Integers (Int)
-type: using
 ---
 
 
@@ -131,6 +130,9 @@ Use bool(yamlobj.data) or bool(yamlobj.text) instead.
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/scalar-integer.story">scalar-integer.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/scalar-integer.story">scalar-integer.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/regular-expressions.md b/docs/public/using/alpha/scalar/regular-expressions.md
index f200dce..7f99c95 100644
--- a/docs/public/using/alpha/scalar/regular-expressions.md
+++ b/docs/public/using/alpha/scalar/regular-expressions.md
@@ -1,6 +1,5 @@
 ---
 title: Validating strings with regexes (Regex)
-type: using
 ---
 
 
@@ -143,6 +142,9 @@ when expecting string matching [1-4] got '1' of type int.
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/regexp.story">regexp.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/regexp.story">regexp.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/using/alpha/scalar/string.md b/docs/public/using/alpha/scalar/string.md
index 5cbc602..6557320 100644
--- a/docs/public/using/alpha/scalar/string.md
+++ b/docs/public/using/alpha/scalar/string.md
@@ -1,6 +1,5 @@
 ---
 title: Parsing strings (Str)
-type: using
 ---
 
 
@@ -90,6 +89,9 @@ Use bool(yamlobj.data) or bool(yamlobj.text) instead.
 
 
 
-{{< note title="Executable specification" >}}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/scalar-string.story">scalar-string.story</a>.
-{{< /note >}}
\ No newline at end of file
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/scalar-string.story">scalar-string.story
+    storytests.
\ No newline at end of file
diff --git a/docs/public/why-not/hjson.md b/docs/public/why-not/hjson.md
index f7f346c..7082e75 100644
--- a/docs/public/why-not/hjson.md
+++ b/docs/public/why-not/hjson.md
@@ -2,9 +2,10 @@
 title: Why not use HJSON?
 ---
 
-{{< note title="No longer supported" >}}
-HJSON is no longer supported.
-{{< /note >}}
+!!! note "No longer supported"
+
+    HJSON is no longer supported.
+
 
 [HJSON](http://hjson.org/) is an attempt at fixing the aforementioned lack of readability of JSON.
 
diff --git a/docs/public/why-not/json-schema.md b/docs/public/why-not/json-schema.md
index 46b2b1e..d604380 100644
--- a/docs/public/why-not/json-schema.md
+++ b/docs/public/why-not/json-schema.md
@@ -3,11 +3,47 @@ title: Why not use JSON Schema for validation?
 ---
 
 JSON schema can also be used to validate YAML. This presumes that
-you might want to use jsonschema and pyyaml/ruamel.yaml together.
+you might want to use jsonschema and yaml together.
 
-[ TODO Flesh out ]
+StrictYAML was inspired by the frustration of trying to use
+pyyaml with [pykwalify](https://pykwalify.readthedocs.io/),
+in fact.
 
-- Line numbers
-- Simpler errors in StrictYAML
-- StrictYAML is a more flexible schema
-- Turing incompleteness / inflexibility
+## Loss of line numbers
+
+Because parsing first with pyyaml and then passing the result
+to pykwalify loses line numbers, validation errors lose the
+line number where the validation error occurred.
+
+This makes tracking down the location of errors tricky. If
+a schema is a little repetitive it can make tracking down
+the exact location of the error hellish.
+
+# Simpler errors in StrictYAML
+
+StrictYAML has an emphasis on the friendliness of schema
+validation errors. Ideally every schema validation error
+should be extremely obvious and show only the information
+necessary.
+
+# StrictYAML schemas are more flexible
+
+Because schemas are written in python and strictyaml allows
+revalidation, strictyaml schemas are much more flexible:
+
+Example:
+
+```python
+from strictyaml import load, Seq, Enum
+import pycountry    # updated list of country codes
+
+
+load(
+    strictyaml_string,
+    Map(
+        {
+            "countries": Seq(Enum([country.alpha_2 for country in pycountry.countries]))
+        }
+    )
+)
+```
diff --git a/docs/public/why-not/ordinary-yaml.md b/docs/public/why-not/ordinary-yaml.md
index c55a369..8e472de 100644
--- a/docs/public/why-not/ordinary-yaml.md
+++ b/docs/public/why-not/ordinary-yaml.md
@@ -6,7 +6,7 @@ title: Why not use the YAML 1.2 standard? - we don't need a new standard!
 
 StrictYAML is composed of two parts:
 
-- A new YAML specification which parses a restricted subset of the [YAML 1.2 specification](https://github.com/yaml/yaml-spec/tree/spec-1.2)
+- A new YAML specification which parses a restricted subset of the [YAML 1.2 specification](https://yaml.org/spec/1.2.2/)
   and *only* parses to ordered dict, list or string.
 - An optional validator (which will, as requested, validate and cast parse some of those scalar string values to ints, floats, datetimes, etc.).
 
diff --git a/docs/public/why/implicit-typing-removed.md b/docs/public/why/implicit-typing-removed.md
index e50a8b2..1b04f99 100644
--- a/docs/public/why/implicit-typing-removed.md
+++ b/docs/public/why/implicit-typing-removed.md
@@ -63,7 +63,7 @@ countries:
 ```
 
 The most tragic aspect of this bug, however, is that it is
-*intended* behavior according to the [YAML 1.2 specification](https://github.com/yaml/yaml-spec/tree/spec-1.2).
+*intended* behavior according to the [YAML 1.2 specification](https://yaml.org/spec/1.2.2/).
 The real fix requires explicitly disregarding the spec - which
 is why most YAML parsers have it.
 
diff --git a/docs/src/features-removed.md b/docs/src/features-removed.md
index d3b7617..100da9d 100644
--- a/docs/src/features-removed.md
+++ b/docs/src/features-removed.md
@@ -5,7 +5,7 @@ title: What YAML features does StrictYAML remove?
 StrictYAML restricts you from parsing a number of things which
 the YAML specification says should be parsed. An issue has
 been [raised](https://github.com/yaml/YAML2/issues/8) by
-[David Seaward](https://inkwell.za.net/) about this critique
+[David Seaward](https://github.com/lofidevops) about this critique
 on the official YAML repository.
 
 This document lists those of those features:
diff --git a/docs/src/index.md b/docs/src/index.md
index d704890..6d7cfdb 100644
--- a/docs/src/index.md
+++ b/docs/src/index.md
@@ -5,7 +5,10 @@
 title: StrictYAML
 ---
 
-{% raw %}{{< github-stars user="crdoconnor" project="strictyaml" >}}{% endraw %}
+{% raw %}
+<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/crdoconnor/strictyaml?style=social"> 
+<img alt="PyPI - Downloads" src="https://img.shields.io/pypi/dm/strictyaml">
+{% endraw %}
 {% endif %}
 
 StrictYAML is a [type-safe](https://en.wikipedia.org/wiki/Type_safety) YAML parser
diff --git a/docs/src/template/story.jinja2 b/docs/src/template/story.jinja2
index 0512160..ccea1a9 100644
--- a/docs/src/template/story.jinja2
+++ b/docs/src/template/story.jinja2
@@ -1,12 +1,11 @@
 ---
 title: {{ story.name }}
-type: using
 ---
-{% if story.info['experimental'] %}{% raw %}
-{{< warning title="Experimental" >}}
-This feature is in alpha. The API may change on a minor version increment.
-{{< /warning >}}
-{% endraw %}{% endif %}
+{% if story.info['experimental'] %}
+!!! warning "Experimental"
+
+    This feature is in alpha. The API may change on a minor version increment.
+{% endif %}
 
 {{ story.info['description'] }}
 
@@ -37,6 +36,9 @@ This feature is in alpha. The API may change on a minor version increment.
 {% with step = story.steps[0] %}{% include "step.jinja2" %}{% endwith %}
 {% endif %}
 
-{% raw %}{{< note title="Executable specification" >}}{% endraw %}
-Page automatically generated from <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/{{ story.filename.basename() }}">{{ story.filename.basename() }}</a>.
-{% raw %}{{< /note >}}{% endraw %}
+
+!!! note "Executable specification"
+
+    Documentation automatically generated from 
+    <a href="https://github.com/crdoconnor/strictyaml/blob/master/hitch/story/{{ story.filename.basename() }}">{{ story.filename.basename() }}
+    storytests.
diff --git a/docs/src/why-not/hjson.md b/docs/src/why-not/hjson.md
index f7f346c..7082e75 100644
--- a/docs/src/why-not/hjson.md
+++ b/docs/src/why-not/hjson.md
@@ -2,9 +2,10 @@
 title: Why not use HJSON?
 ---
 
-{{< note title="No longer supported" >}}
-HJSON is no longer supported.
-{{< /note >}}
+!!! note "No longer supported"
+
+    HJSON is no longer supported.
+
 
 [HJSON](http://hjson.org/) is an attempt at fixing the aforementioned lack of readability of JSON.
 
diff --git a/docs/src/why-not/json-schema.md b/docs/src/why-not/json-schema.md
index 46b2b1e..d604380 100644
--- a/docs/src/why-not/json-schema.md
+++ b/docs/src/why-not/json-schema.md
@@ -3,11 +3,47 @@ title: Why not use JSON Schema for validation?
 ---
 
 JSON schema can also be used to validate YAML. This presumes that
-you might want to use jsonschema and pyyaml/ruamel.yaml together.
+you might want to use jsonschema and yaml together.
 
-[ TODO Flesh out ]
+StrictYAML was inspired by the frustration of trying to use
+pyyaml with [pykwalify](https://pykwalify.readthedocs.io/),
+in fact.
 
-- Line numbers
-- Simpler errors in StrictYAML
-- StrictYAML is a more flexible schema
-- Turing incompleteness / inflexibility
+## Loss of line numbers
+
+Because parsing first with pyyaml and then passing the result
+to pykwalify loses line numbers, validation errors lose the
+line number where the validation error occurred.
+
+This makes tracking down the location of errors tricky. If
+a schema is a little repetitive it can make tracking down
+the exact location of the error hellish.
+
+# Simpler errors in StrictYAML
+
+StrictYAML has an emphasis on the friendliness of schema
+validation errors. Ideally every schema validation error
+should be extremely obvious and show only the information
+necessary.
+
+# StrictYAML schemas are more flexible
+
+Because schemas are written in python and strictyaml allows
+revalidation, strictyaml schemas are much more flexible:
+
+Example:
+
+```python
+from strictyaml import load, Seq, Enum
+import pycountry    # updated list of country codes
+
+
+load(
+    strictyaml_string,
+    Map(
+        {
+            "countries": Seq(Enum([country.alpha_2 for country in pycountry.countries]))
+        }
+    )
+)
+```
diff --git a/docs/src/why-not/ordinary-yaml.md b/docs/src/why-not/ordinary-yaml.md
index c55a369..8e472de 100644
--- a/docs/src/why-not/ordinary-yaml.md
+++ b/docs/src/why-not/ordinary-yaml.md
@@ -6,7 +6,7 @@ title: Why not use the YAML 1.2 standard? - we don't need a new standard!
 
 StrictYAML is composed of two parts:
 
-- A new YAML specification which parses a restricted subset of the [YAML 1.2 specification](https://github.com/yaml/yaml-spec/tree/spec-1.2)
+- A new YAML specification which parses a restricted subset of the [YAML 1.2 specification](https://yaml.org/spec/1.2.2/)
   and *only* parses to ordered dict, list or string.
 - An optional validator (which will, as requested, validate and cast parse some of those scalar string values to ints, floats, datetimes, etc.).
 
diff --git a/docs/src/why/implicit-typing-removed.md b/docs/src/why/implicit-typing-removed.md
index e50a8b2..1b04f99 100644
--- a/docs/src/why/implicit-typing-removed.md
+++ b/docs/src/why/implicit-typing-removed.md
@@ -63,7 +63,7 @@ countries:
 ```
 
 The most tragic aspect of this bug, however, is that it is
-*intended* behavior according to the [YAML 1.2 specification](https://github.com/yaml/yaml-spec/tree/spec-1.2).
+*intended* behavior according to the [YAML 1.2 specification](https://yaml.org/spec/1.2.2/).
 The real fix requires explicitly disregarding the spec - which
 is why most YAML parsers have it.
 
diff --git a/hitch/Dockerfile-hitch b/hitch/Dockerfile-hitch
new file mode 100644
index 0000000..cc1229f
--- /dev/null
+++ b/hitch/Dockerfile-hitch
@@ -0,0 +1,25 @@
+FROM ubuntu:20.04
+
+ENV DEBIAN_FRONTEND=noninteractive
+ENV LC_ALL=C.UTF-8
+ENV LANG=C.UTF-8
+ENV PYENV_ROOT=/gen/pyenv/
+
+RUN apt-get update && apt-get install \
+    libuv1-dev libncurses5-dev libncursesw5-dev xz-utils \
+    liblzma-dev tk-dev build-essential \
+    libreadline-dev libffi-dev libsqlite3-dev \
+    zlib1g-dev zlib1g libbz2-dev libssl-dev \
+    curl git make llvm wget \
+    python3-virtualenv virtualenv \
+    python3-dev python-openssl -y \
+    && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+# Isolate system python packages from those used in project
+RUN virtualenv --python=python3 /venv
+RUN mkdir /src
+
+WORKDIR /src
+COPY hitch/hitchreqs.txt /
+RUN /venv/bin/pip install setuptools-rust
+RUN /venv/bin/pip install -r /hitchreqs.txt
diff --git a/hitch/engine.py b/hitch/engine.py
index 0a4f5b5..d2c2418 100644
--- a/hitch/engine.py
+++ b/hitch/engine.py
@@ -1,4 +1,10 @@
-from hitchstory import StoryCollection, BaseEngine, exceptions, validate, no_stacktrace_for
+from hitchstory import (
+    StoryCollection,
+    BaseEngine,
+    exceptions,
+    validate,
+    no_stacktrace_for,
+)
 from hitchstory import GivenDefinition, GivenProperty, InfoDefinition, InfoProperty
 from templex import Templex
 from strictyaml import Optional, Str, Map, Int, Bool, Enum, load
@@ -11,7 +17,6 @@ from hitchrunpy import (
 )
 
 
-
 CODE_TYPE = Map({"in python 2": Str(), "in python 3": Str()}) | Str()
 
 
@@ -20,27 +25,21 @@ class Engine(BaseEngine):
 
     given_definition = GivenDefinition(
         yaml_snippet=GivenProperty(
-            Str(),
-            document="yaml_snippet:\n```yaml\n{{ yaml_snippet }}\n```"
+            Str(), document="yaml_snippet:\n```yaml\n{{ yaml_snippet }}\n```"
         ),
         yaml_snippet_1=GivenProperty(
-            Str(),
-            document="yaml_snippet_1:\n```yaml\n{{ yaml_snippet_1 }}\n```"
+            Str(), document="yaml_snippet_1:\n```yaml\n{{ yaml_snippet_1 }}\n```"
         ),
         yaml_snippet_2=GivenProperty(
-            Str(),
-            document="yaml_snippet_2:\n```yaml\n{{ yaml_snippet_2 }}\n```"
+            Str(), document="yaml_snippet_2:\n```yaml\n{{ yaml_snippet_2 }}\n```"
         ),
         modified_yaml_snippet=GivenProperty(
             Str(),
-            document="modified_yaml_snippet:\n```yaml\n{{ modified_yaml_snippet }}\n```"
+            document="modified_yaml_snippet:\n```yaml\n{{ modified_yaml_snippet }}\n```",
         ),
         python_version=GivenProperty(Str()),
         ruamel_version=GivenProperty(Str()),
-        setup=GivenProperty(
-            Str(),
-            document="```python\n{{ setup }}\n```"
-        ),
+        setup=GivenProperty(Str(), document="```python\n{{ setup }}\n```"),
     )
 
     info_definition = InfoDefinition(
@@ -65,13 +64,7 @@ class Engine(BaseEngine):
             self.path.profile.mkdir()
 
         if not self._python_path:
-            self.pylibrary = hitchpylibrarytoolkit.PyLibraryBuild(
-                "strictyaml",
-                self.path
-            ).with_python_version(self.given["python version"])\
-             .with_packages({"ruamel.yaml": self.given["ruamel version"]})
-            self.pylibrary.ensure_built()
-            self.python = self.pylibrary.bin.python
+            self.python = Path("/gen/pyenv/versions/venv3.11.2/bin/python")
         else:
             self.python = Path(self._python_path)
             assert self.python.exists()
@@ -79,9 +72,7 @@ class Engine(BaseEngine):
         self.example_py_code = (
             ExamplePythonCode(self.python, self.path.gen)
             .with_code(self.given.get("code", ""))
-            .with_setup_code(
-                self.given.get("setup", "")
-            )
+            .with_setup_code(self.given.get("setup", ""))
             .with_terminal_size(160, 100)
             .with_strings(
                 yaml_snippet_1=self.given.get("yaml_snippet_1"),
@@ -110,11 +101,13 @@ class Engine(BaseEngine):
         if in_interpreter:
             if self.given["python version"].startswith("3"):
                 code = "{0}\nprint(repr({1}))".format(
-                    "\n".join(code.strip().split("\n")[:-1]), code.strip().split("\n")[-1]
+                    "\n".join(code.strip().split("\n")[:-1]),
+                    code.strip().split("\n")[-1],
                 )
             else:
                 code = "{0}\nprint repr({1})".format(
-                    "\n".join(code.strip().split("\n")[:-1]), code.strip().split("\n")[-1]
+                    "\n".join(code.strip().split("\n")[:-1]),
+                    code.strip().split("\n")[-1],
                 )
 
         to_run = self.example_py_code.with_code(code)
diff --git a/hitch/hitchreqs.in b/hitch/hitchreqs.in
index f2fc0d5..2694477 100644
--- a/hitch/hitchreqs.in
+++ b/hitch/hitchreqs.in
@@ -1,2 +1,4 @@
+hitchpylibrarytoolkit==0.5.1
 hitchrun>=0.4.0
-hitchpylibrarytoolkit>=0.5.1
+build
+wheel
diff --git a/hitch/hitchreqs.txt b/hitch/hitchreqs.txt
index 73f5865..ecc1af2 100644
--- a/hitch/hitchreqs.txt
+++ b/hitch/hitchreqs.txt
@@ -1,20 +1,20 @@
-appdirs==1.4.4
-    # via black
 argcomplete==1.12.2
     # via hitchrun
 backcall==0.2.0
     # via ipython
-black==20.8b1
+black==21.11b1
     # via hitchpylibrarytoolkit
-bleach==3.2.1
+bleach==4.1.0
     # via readme-renderer
-certifi==2020.11.8
+build==0.1.0
+    # via build
+certifi==2021.10.8
     # via requests
-cffi==1.14.4
+cffi==1.15.0
     # via cryptography
-chardet==3.0.4
+charset-normalizer==2.0.8
     # via requests
-click==7.1.2
+click==8.0.3
     # via
     #   black
     #   hitchrun
@@ -30,20 +30,18 @@ commandlib==0.3.5
     #   hitchbuildpy
     #   hitchrun
     #   icommandlib
-cryptography==3.2.1
+cryptography==36.0.0
     # via secretstorage
-decorator==4.4.2
+decorator==5.1.0
     # via ipython
 dirtemplate==0.4.0
     # via hitchpylibrarytoolkit
-docutils==0.16
+docutils==0.18.1
     # via readme-renderer
-flake8==3.8.4
+flake8==4.0.1
     # via hitchpylibrarytoolkit
-gitdb==4.0.5
+gitdb==4.0.9
     # via gitpython
-gitpython==3.1.11
-    # via hitchpylibrarytoolkit
 hitchbuild==0.6.3
     # via
     #   dirtemplate
@@ -56,25 +54,27 @@ hitchrun==0.4.0
     # via
     #   -r hitchreqs.in
     #   hitchpylibrarytoolkit
-hitchrunpy==0.10.0
+hitchrunpy==0.11.2
     # via hitchpylibrarytoolkit
 hitchstory==0.12.1
     # via hitchpylibrarytoolkit
 icommandlib==0.5.0
     # via hitchrunpy
-idna==2.10
+idna==3.3
     # via requests
-ipython==7.16.1
+importlib-metadata==3.10.0
+    # via
+    #   keyring
+    #   twine
+ipython==7.16.3
     # via hitchpylibrarytoolkit
-ipython-genutils==0.2.0
-    # via traitlets
-jedi==0.17.2
+jedi==0.17.1
     # via ipython
-jeepney==0.6.0
+jeepney==0.7.1
     # via
     #   keyring
     #   secretstorage
-jinja2==2.11.2
+jinja2==3.0.3
     # via
     #   dirtemplate
     #   hitchrunpy
@@ -82,19 +82,21 @@ jinja2==2.11.2
     #   prettystack
 kaching==0.4.2
     # via hitchpylibrarytoolkit
-keyring==21.5.0
+keyring==23.3.0
     # via twine
-markupsafe==1.1.1
+markupsafe==2.0.1
     # via jinja2
+matplotlib-inline==0.1.3
+    # via ipython
 mccabe==0.6.1
     # via flake8
 mypy-extensions==0.4.3
     # via black
-packaging==20.7
+packaging==21.3
     # via bleach
 parso==0.7.1
     # via jedi
-path==15.0.0
+path==16.2.0
     # via path.py
 path.py==12.5.0
     # via
@@ -110,9 +112,9 @@ pathquery==0.3.0
     #   hitchbuild
     #   hitchbuildpy
     #   hitchstory
-pathspec==0.8.1
+pathspec==0.9.0
     # via black
-peewee==3.14.0
+peewee==3.14.8
     # via hitchbuild
 pep517==0.12.0
     # via pip-tools
@@ -122,36 +124,38 @@ pickleshare==0.7.5
     # via ipython
 pip-tools==6.4.0
     # via hitchrun
-pkginfo==1.6.1
+pkginfo==1.8.1
     # via twine
+platformdirs==2.4.0
+    # via black
 prettystack==0.3.0
     # via
     #   hitchrun
     #   hitchrunpy
     #   hitchstory
-prompt-toolkit==3.0.8
+prompt-toolkit==3.0.23
     # via ipython
-psutil==5.7.3
+psutil==5.8.0
     # via icommandlib
-ptyprocess==0.6.0
+ptyprocess==0.7.0
     # via pexpect
-pycodestyle==2.6.0
+pycodestyle==2.8.0
     # via flake8
-pycparser==2.20
+pycparser==2.21
     # via cffi
-pyflakes==2.2.0
+pyflakes==2.4.0
     # via flake8
-pygments==2.7.2
+pygments==2.10.0
     # via
     #   ipython
     #   readme-renderer
-pyparsing==2.4.7
+pyparsing==3.0.6
     # via packaging
 pyte==0.8.0
     # via icommandlib
-python-dateutil==2.8.1
+python-dateutil==2.8.2
     # via strictyaml
-python-slugify==4.0.1
+python-slugify==5.0.2
     # via
     #   dirtemplate
     #   hitchbuild
@@ -160,29 +164,27 @@ pyuv==1.4.0
     # via icommandlib
 q==2.6
     # via hitchpylibrarytoolkit
-readme-renderer==28.0
+readme-renderer==30.0
     # via twine
-regex==2020.11.13
+regex==2021.11.10
     # via black
-requests==2.25.0
+requests==2.26.0
     # via
     #   requests-toolbelt
     #   twine
 requests-toolbelt==0.9.1
     # via twine
-rfc3986==1.4.0
+rfc3986==1.5.0
     # via twine
-secretstorage==3.3.0
+secretstorage==3.3.1
     # via keyring
-six==1.15.0
+six==1.16.0
     # via
     #   bleach
-    #   cryptography
     #   python-dateutil
-    #   readme-renderer
-smmap==3.0.4
+smmap==5.0.0
     # via gitdb
-strictyaml==1.5.0
+strictyaml==1.6.0
     # via
     #   dirtemplate
     #   hitchstory
@@ -190,23 +192,25 @@ templex==0.2.0
     # via hitchpylibrarytoolkit
 text-unidecode==1.3
     # via python-slugify
-toml==0.10.2
-    # via black
 tomli==1.2.2
-    # via pep517
-tqdm==4.54.0
+    # via
+    #   black
+    #   pep517
+tqdm==4.62.3
     # via twine
-traitlets==5.0.4
-    # via ipython
-twine==3.2.0
+traitlets==4.3.3
+    # via
+    #   ipython
+    #   matplotlib-inline
+twine==3.6.0
     # via hitchpylibrarytoolkit
-typed-ast==1.4.1
-    # via black
-typing-extensions==3.7.4.3
-    # via black
-urllib3==1.26.2
+typing-extensions==4.0.0
+    # via
+    #   black
+    #   gitpython
+urllib3==1.26.7
     # via requests
-uvloop==0.14.0
+uvloop==0.11.2
     # via icommandlib
 wcwidth==0.2.5
     # via
@@ -216,6 +220,8 @@ webencodings==0.5.1
     # via bleach
 wheel==0.37.0
     # via pip-tools
+zipp==3.6.0
+    # via importlib-metadata
 
 # The following packages are considered to be unsafe in a requirements file:
 # pip
diff --git a/hitch/key.py b/hitch/key.py
index 6e3411d..375839b 100644
--- a/hitch/key.py
+++ b/hitch/key.py
@@ -1,12 +1,30 @@
-from hitchstory import HitchStoryException, StoryCollection
-from hitchrun import expected
-from commandlib import CommandError
+from hitchstory import StoryCollection
 from strictyaml import Str, Map, Bool, load
+from commandlib import Command, python
+from click import argument, group, pass_context
 from pathquery import pathquery
-from hitchrun import DIR
-import dirtemplate
 import hitchpylibrarytoolkit
 from engine import Engine
+from path import Path
+import pyenv
+
+
+class Directories:
+    gen = Path("/gen")
+    key = Path("/src/hitch/")
+    project = Path("/src/")
+    share = Path("/gen")
+
+
+DIR = Directories()
+
+
+@group(invoke_without_command=True)
+@pass_context
+def cli(ctx):
+    """Integration test command line interface."""
+    pass
+
 
 PROJECT_NAME = "strictyaml"
 
@@ -67,8 +85,9 @@ RUNNABLE COMMANDS
 """
 
 
-@expected(HitchStoryException)
-def bdd(*keywords):
+@cli.command()
+@argument("keywords", nargs=-1)
+def bdd(keywords):
     """
     Run story matching keywords.
     """
@@ -77,8 +96,10 @@ def bdd(*keywords):
     ).only_uninherited().shortcut(*keywords).play()
 
 
-@expected(HitchStoryException)
-def tver(pyversion, *keywords):
+@cli.command()
+@argument("pyversion", nargs=1)
+@argument("keywords", nargs=-1)
+def tver(pyversion, keywords):
     """
     Run story against specific version of Python - e.g. tver 3.7.0 modify multi line
     """
@@ -87,8 +108,9 @@ def tver(pyversion, *keywords):
     ).only_uninherited().shortcut(*keywords).play()
 
 
-@expected(HitchStoryException)
-def rbdd(*keywords):
+@cli.command()
+@argument("keywords", nargs=-1)
+def rbdd(keywords):
     """
     Run story matching keywords and rewrite story if code changed.
     """
@@ -97,7 +119,8 @@ def rbdd(*keywords):
     ).only_uninherited().shortcut(*keywords).play()
 
 
-@expected(HitchStoryException)
+@cli.command()
+@argument("filename", nargs=1)
 def regressfile(filename):
     """
     Run all stories in filename 'filename' in python 3.7.
@@ -107,18 +130,23 @@ def regressfile(filename):
     ).ordered_by_name().play()
 
 
-@expected(HitchStoryException)
+@cli.command()
 def regression():
     """
     Run regression testing - lint and then run all tests.
     """
-    lint()
-    doctests()
-    storybook = _storybook().only_uninherited()
-    storybook.with_params(**{"python version": "3.7.0"}).ordered_by_name().play()
+    venv = pyenv.devvenv()
+    _lint()
+    _doctests(venv.python_path)
+    storybook = _storybook(python_path=venv.python_path).only_uninherited()
+    storybook.with_params(
+        **{"python version": venv.py_version.version}
+    ).ordered_by_name().play()
 
 
-@expected(HitchStoryException)
+@cli.command()
+@argument("python_path", nargs=1)
+@argument("python_version", nargs=1)
 def regression_on_python_path(python_path, python_version):
     """
     Run regression tests - e.g. hk regression_on_python_path /usr/bin/python 3.7.0
@@ -128,7 +156,7 @@ def regression_on_python_path(python_path, python_version):
     ).only_uninherited().ordered_by_name().play()
 
 
-@expected(hitchpylibrarytoolkit.ToolkitError)
+@cli.command()
 def checks():
     """
     Run all checks ensure linter, code formatter, tests and docgen all run correctly.
@@ -137,13 +165,12 @@ def checks():
     """
     toolkit.validate_reformatting()
     toolkit.lint(exclude=["__init__.py", "ruamel"])
-    toolkit.validate_readmegen(Engine(DIR))
-    toolkit.validate_docgen(Engine(DIR))
-    doctests()
+    _doctests()
     storybook = _storybook().only_uninherited()
     storybook.with_params(**{"python version": "3.7.0"}).ordered_by_name().play()
 
 
+@cli.command()
 def reformat():
     """
     Reformat using black and then relint.
@@ -151,6 +178,7 @@ def reformat():
     toolkit.reformat()
 
 
+@cli.command()
 def ipython():
     """
     Run ipython in strictyaml virtualenv.
@@ -166,22 +194,100 @@ def ipython():
     ).run()
 
 
-@expected(CommandError)
+def _lint():
+    toolkit.lint(exclude=["__init__.py", "ruamel"])
+    assert "\n" not in DIR.project.joinpath("VERSION")
+
+
+@cli.command()
 def lint():
     """
     Lint project code and hitch code.
     """
-    toolkit.lint(exclude=["__init__.py", "ruamel"])
+    _lint()
 
 
-def deploy(version):
+"""
+@cli.command()
+def draftdocs():
+    run_docgen(DIR, _storybook({}))
+
+
+@cli.command()
+def publishdocs():
+    if DIR.gen.joinpath("strictyaml").exists():
+        DIR.gen.joinpath("strictyaml").rmtree()
+
+    Path("/root/.ssh/known_hosts").write_text(
+        Command("ssh-keyscan", "github.com").output()
+    )
+    Command("git", "clone", "git@github.com:crdoconnor/strictyaml.git").in_dir(
+        DIR.gen
+    ).run()
+
+    git = Command("git").in_dir(DIR.gen / "strictyaml")
+    git("config", "user.name", "Bot").run()
+    git("config", "user.email", "bot@hitchdev.com").run()
+    git("rm", "-r", "docs/public").run()
+
+    run_docgen(DIR, _storybook({}), publish=True)
+
+    git("add", "docs/public").run()
+    git("commit", "-m", "DOCS : Regenerated docs.").run()
+
+    git("push").run()
+
+
+@cli.command()
+def readmegen():
+    run_docgen(DIR, _storybook({}), readme=True)
+    DIR.project.joinpath("docs", "draft", "index.md").copy("README.md")
+    DIR.project.joinpath("docs", "draft", "changelog.md").copy("CHANGELOG.md")
+"""
+
+
+@cli.command()
+@argument("test", nargs=1)
+def deploy(test="notest"):
     """
     Deploy to pypi as specified version.
     """
-    toolkit.deploy(version)
+    from commandlib import python
+
+    git = Command("git")
+
+    if DIR.gen.joinpath("strictyaml").exists():
+        DIR.gen.joinpath("strictyaml").rmtree()
+
+    git("clone", "git@github.com:crdoconnor/strictyaml.git").in_dir(DIR.gen).run()
+    project = DIR.gen / "strictyaml"
+    version = project.joinpath("VERSION").text().rstrip()
+    initpy = project.joinpath("strictyaml", "__init__.py")
+    original_initpy_contents = initpy.bytes().decode("utf8")
+    initpy.write_text(original_initpy_contents.replace("DEVELOPMENT_VERSION", version))
+    python("-m", "pip", "wheel", ".", "-w", "dist").in_dir(project).run()
+    python("-m", "build", "--sdist").in_dir(project).run()
+    initpy.write_text(original_initpy_contents)
+
+    # Upload to pypi
+    wheel_args = ["-m", "twine", "upload"]
+    if test == "test":
+        wheel_args += ["--repository", "testpypi"]
+    wheel_args += ["dist/{}-{}-py3-none-any.whl".format("strictyaml", version)]
+
+    python(*wheel_args).in_dir(project).run()
 
+    sdist_args = ["-m", "twine", "upload"]
+    if test == "test":
+        sdist_args += ["--repository", "testpypi"]
+    sdist_args += ["dist/{0}-{1}.tar.gz".format("strictyaml", version)]
+    python(*sdist_args).in_dir(project).run()
 
-@expected(dirtemplate.exceptions.DirTemplateException)
+    # Clean up
+    DIR.gen.joinpath("strictyaml").rmtree()
+
+
+@cli.command()
 def docgen():
     """
     Build documentation.
@@ -189,7 +295,7 @@ def docgen():
     toolkit.docgen(Engine(DIR))
 
 
-@expected(dirtemplate.exceptions.DirTemplateException)
+@cli.command()
 def readmegen():
     """
     Build README.md and CHANGELOG.md.
@@ -197,22 +303,21 @@ def readmegen():
     toolkit.readmegen(Engine(DIR))
 
 
-@expected(CommandError)
+def _doctests(python_path):
+    Command(python_path)(
+        "-m", "doctest", "-v", DIR.project.joinpath(PROJECT_NAME, "utils.py")
+    ).in_dir(DIR.project.joinpath(PROJECT_NAME)).run()
+
+
+@cli.command()
 def doctests():
     """
-    Run doctests in utils.py in python 2 and 3.
+    Run doctests in utils.py in latest version.
     """
-    for python_version in ["2.7.14", "3.7.0"]:
-        pylibrary = hitchpylibrarytoolkit.PyLibraryBuild(
-            "strictyaml",
-            DIR,
-        )
-        pylibrary.bin.python(
-            "-m", "doctest", "-v", DIR.project.joinpath(PROJECT_NAME, "utils.py")
-        ).in_dir(DIR.project.joinpath(PROJECT_NAME)).run()
+    _doctests(pyenv.devvenv().python_path)
 
 
-@expected(CommandError)
+@cli.command()
 def rerun():
     """
     Rerun last example code block with specified version of Python.
@@ -225,7 +330,7 @@ def rerun():
     ).in_dir(DIR.gen.joinpath("working")).run()
 
 
-@expected(CommandError)
+@cli.command()
 def bash():
     """
     Run bash
@@ -235,12 +340,71 @@ def bash():
     Command("bash").run()
 
 
-def build():
-    import hitchpylibrarytoolkit
+@cli.command()
+def cleanpyenv():
+    pyenv.Pyenv("/gen/pyenv").clean()
+
 
-    hitchpylibrarytoolkit.project_build(
-        "strictyaml",
-        DIR,
-        "3.7.0",
-        {"ruamel.yaml": "0.16.5"},
+@cli.command()
+def sdist():
+    """Build sdist"""
+    DIR.project.joinpath("dist").rmtree(ignore_errors=True)
+    python("setup.py", "sdist").in_dir(DIR.project).run()
+
+
+@cli.command()
+@argument("strategy_name", nargs=1)
+def envirotest(strategy_name):
+    """Run tests on package / python version combinations."""
+    import random
+
+    DIR.project.joinpath("dist").rmtree(ignore_errors=True)
+    python("setup.py", "sdist").in_dir(DIR.project).run()
+    sdist_path = DIR.project.joinpath(
+        "dist", "strictyaml-{}.tar.gz".format(_current_version())
     )
+
+    if strategy_name == "latest":
+        strategies = [
+            lambda versions: versions[-2],
+        ]
+    elif strategy_name == "earliest":
+        strategies = [
+            lambda versions: versions[0],
+        ]
+    elif strategy_name in ("full", "maxi"):
+        strategies = (
+            [
+                lambda versions: versions[0],
+            ]
+            + [random.choice] * 2
+            if strategy_name == "full"
+            else 5 + [lambda versions: versions[-2]]
+        )
+    else:
+        raise Exception(f"Strategy name {strategy_name} not found")
+
+    for strategy in strategies:
+        venv = pyenv.randomtestvenv(
+            picker=strategy,
+            local_package=sdist_path,
+        )
+        python_path = venv.python_path
+        results = (
+            _storybook(python_path=python_path)
+            .with_params(**{"python version": venv.py_version.version})
+            .only_uninherited()
+            .ordered_by_name()
+            .play()
+        )
+        assert results.all_passed
+        _doctests(python_path)
+
+
+@cli.command()
+def build():
+    pyenv.devvenv()
+
+
+if __name__ == "__main__":
+    cli()
diff --git a/hitch/pyenv.py b/hitch/pyenv.py
new file mode 100644
index 0000000..17e0062
--- /dev/null
+++ b/hitch/pyenv.py
@@ -0,0 +1,267 @@
+from commandlib import CommandPath, Command
+from distutils.version import LooseVersion, StrictVersion
+from path import Path
+import hitchbuild
+import requests
+import dateutil
+from typing import List
+
+
+def clean():
+    Pyenv("/gen/pyenv").clean()
+
+
+class ProjectVersions:
+    def __init__(self, filename, pyenv_build):
+        self._filename = filename
+        self._pyenv_build = pyenv_build
+
+    def load(self):
+        import tomli
+
+        project = tomli.loads(Path(filename).text())["project"]
+        pyversion = project["requires-python"]
+        dependencies = project["dependencies"]
+
+
+def randomtestvenv(picker=None, local_package=None):
+    pyenv_build = Pyenv("/gen/pyenv")
+    pyenv_build.ensure_built()
+
+    python_dateutil_version = picker(package_versions_above("python-dateutil", "2.6.0"))
+    python_version = picker(pyenv_build.available_versions_above_and_including("3.7.0"))
+    print("Dateutil version: {}".format(python_dateutil_version))
+    print("Python version: {}".format(python_version))
+
+    pyversion = PyVersion(
+        pyenv_build,
+        python_version,
+    )
+
+    venv = ProjectVirtualenv(
+        "randomtestvenv",
+        pyversion,
+        # requirements_files=[],
+        # requirements=[
+        # "ensure",
+        # "python-slugify",
+        # "python-dateutil=={}".format(python_dateutil_version),
+        # ],
+        # local_package=local_package,
+        packages=[
+            PythonRequirements(
+                ["ensure", "python-slugify"],
+            ),
+            PythonProjectPackage(local_package),
+            PythonRequirements(
+                [
+                    "python-dateutil=={}".format(python_dateutil_version),
+                ]
+            ),
+        ],
+    )
+    venv.clean()
+    venv.ensure_built()
+    return venv
+
+
+def devvenv():
+    pyenv_build = Pyenv("/gen/pyenv")
+    pyenv_build.ensure_built()
+
+    pyversion = PyVersion(
+        pyenv_build,
+        pyenv_build.available_versions_above_and_including("3.7.0")[-2],
+    )
+
+    venv = ProjectVirtualenv(
+        "devenv",
+        pyversion,
+        packages=[
+            PythonRequirementsFile("/src/hitch/debugrequirements.txt"),
+            PythonProjectDirectory("/src"),
+        ],
+    )
+    venv.ensure_built()
+    return venv
+
+
+def package_versions_above(package_name, minimum_version):
+    data = requests.get("https://pypi.org/pypi/{}/json".format(package_name)).json()
+    versions = list(data["releases"].keys())
+    versions.sort(key=StrictVersion)
+
+    selected_versions = [
+        version
+        for version in versions
+        if LooseVersion(version) >= LooseVersion(minimum_version)
+    ]
+
+    upload_dates = {
+        version: dateutil.parser.parse(data["releases"][version][-1]["upload_time"])
+        for version in selected_versions
+    }
+
+    relevant_versions = []
+
+    for index, version in enumerate(selected_versions[1:]):
+        timediff = upload_dates[version] - upload_dates[selected_versions[index]]
+
+        if timediff.days > 7:
+            relevant_versions.append(selected_versions[index])
+
+    relevant_versions.append(selected_versions[-1])
+
+    return relevant_versions
+
+
+class PythonPackage:
+    pass
+
+
+class PythonRequirementsFile(PythonPackage):
+    def __init__(self, requirements_file):
+        self.requirements_file = requirements_file
+
+    def install(self, pip):
+        pip("install", "-r", self.requirements_file).run()
+
+
+class PythonRequirements(PythonPackage):
+    def __init__(self, requirements: List[str]):
+        self.requirements = requirements
+
+    def install(self, pip):
+        for requirement in self.requirements:
+            pip("install", requirement).run()
+
+
+class PythonProjectPackage(PythonPackage):
+    def __init__(self, package_filename):
+        self.package_filename = package_filename
+
+    def install(self, pip):
+        local_install_output = pip("install", self.package_filename).output()
+
+        if "DEPRECATION" in local_install_output:
+            raise Exception("DEPRECATION ERROR:\n {}".format(local_install_output))
+
+
+class PythonProjectDirectory(PythonPackage):
+    def __init__(self, project_directory):
+        self.project_directory = project_directory
+
+    def install(self, pip):
+        pip("install", "-e", self.project_directory).run()
+
+
+class ProjectVirtualenv(hitchbuild.HitchBuild):
+    def __init__(
+        self,
+        venv_name,
+        py_version,
+        requirements=None,
+        requirements_files=None,
+        package_dir=None,
+        local_package=None,
+        packages=None,
+    ):
+        self.venv_name = venv_name
+        self.py_version = py_version
+        self.requirements_files = requirements_files
+        self.requirements = requirements
+        self.package_dir = package_dir
+        self.local_package = local_package
+        self.packages = packages
+        self.build_path = (
+            self.py_version.pyenv_build.build_path / "versions" / self.venv_name
+        )
+        self.fingerprint_path = self.build_path / "fingerprint.txt"
+
+    @property
+    def pyenv(self):
+        return self.py_version.pyenv_build.pyenv
+
+    @property
+    def python_path(self):
+        return self.build_path / "bin" / "python"
+
+    def clean(self):
+        try:
+            self.build_path.readlink().rmtree(ignore_errors=True)
+            self.build_path.remove()
+        except FileNotFoundError:
+            pass
+
+    @property
+    def pip(self):
+        return Command(self.build_path / "bin" / "pip")
+
+    def build(self):
+        if not self.build_path.exists():
+            self.py_version.ensure_built()
+            self.pyenv("virtualenv", self.py_version.version, self.venv_name).run()
+
+            if self.packages is not None:
+                for package in self.packages:
+                    package.install(self.pip)
+
+            self.refingerprint()
+
+
+class PyVersion(hitchbuild.HitchBuild):
+    def __init__(self, pyenv_build, version):
+        self.pyenv_build = pyenv_build
+        self.version = version
+        self.build_path = self.pyenv_build.build_path / "versions" / self.version
+        self.fingerprint_path = self.build_path / "fingerprint.txt"
+
+    def clean(self):
+        self.build_path.rmtree(ignore_errors=True)
+
+    def build(self):
+        if not self.build_path.exists():
+            self.pyenv_build.ensure_built()
+            self.pyenv_build.pyenv("install", self.version).run()
+            self.refingerprint()
+
+
+class Pyenv(hitchbuild.HitchBuild):
+    def __init__(self, build_path):
+        self.build_path = Path(build_path).abspath()
+        self.fingerprint_path = self.build_path / "fingerprint.txt"
+
+    @property
+    def bin(self):
+        return CommandPath(self.build_path / "bin")
+
+    def clean(self):
+        self.build_path.rmtree(ignore_errors=True)
+
+    @property
+    def pyenv(self):
+        return Command(self.bin.pyenv).with_env(PYENV_ROOT=self.build_path)
+
+    def available_versions_above_and_including(self, minimum_version):
+        return [
+            version.strip()
+            for version in self.pyenv("install", "-l").output().split("\n")
+            if version.strip().startswith("3")
+            and "dev" not in version
+            and LooseVersion(version.strip()) >= LooseVersion(minimum_version)
+        ]
+
+    def build(self):
+        if self.incomplete():
+            self.build_path.rmtree(ignore_errors=True)
+            self.build_path.mkdir()
+            Command(
+                "git", "clone", "https://github.com/pyenv/pyenv.git", self.build_path
+            ).run()
+            Command(
+                "git",
+                "clone",
+                "https://github.com/pyenv/pyenv-virtualenv.git",
+                self.build_path / "plugins" / "pyenv-virtualenv",
+            ).run()
+            self.refingerprint()
diff --git a/hitch/story/email-url.story b/hitch/story/email-url.story
index 98c63f2..b897aaa 100644
--- a/hitch/story/email-url.story
+++ b/hitch/story/email-url.story
@@ -15,10 +15,10 @@ Email and URL validators:
       given:
         yaml_snippet: |
           a: billg@microsoft.com
-          b: http://www.twitter.com/@realDonaldTrump
+          b: https://user:pass@example.com:443/path?k=v#frag
       steps:
       - Run: |
-          Ensure(load(yaml_snippet, schema)).equals({"a": "billg@microsoft.com", "b": "http://www.twitter.com/@realDonaldTrump"})
+          Ensure(load(yaml_snippet, schema)).equals({"a": "billg@microsoft.com", "b": "https://user:pass@example.com:443/path?k=v#frag"})
 
     Exception:
       given:
diff --git a/hitch/todo.yml b/hitch/todo.yml
deleted file mode 100644
index 2e0f0ee..0000000
--- a/hitch/todo.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-- Create rules such that only certain map key combinations are allowed. x and y, x alone, c and y, c alone, etc.
-
-- Using YAML objects as keys causes infinite loop.
-
-- Add why not section on jsonschema and yaml/ruamel.yaml
-
-- Add why not section on kwalify and ruamel.yaml
-
-- Add why not section on schema and yaml/ruamel.yaml
-
-- Add scalar types AbsoluteUrl(), RelativeUrl(), Domain()
-
-- Consider adding scalar types PositiveInt and Base64.
-
-- Refactor so that custom scalar validators can be written.
diff --git a/key.sh b/key.sh
new file mode 100755
index 0000000..58f2e64
--- /dev/null
+++ b/key.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+set -e
+PROJECT_NAME=strictyaml
+
+PROJECT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
+CONTAINER_NAME=${PROJECT_NAME}-hitch-container
+IMAGE_NAME=${PROJECT_NAME}-hitch
+
+hitchrun() {
+    podman run --privileged -it --rm \
+        -v $PROJECT_DIR:/src \
+        -v $CONTAINER_NAME:/gen \
+        -v ~/.ssh/id_rsa:/root/.ssh/id_rsa \
+        -v ~/.ssh/id_rsa.pub:/root/.ssh/id_rsa.pub \
+        --workdir /src \
+        $IMAGE_NAME \
+        $1
+}
+
+
+case "$1" in
+    "clean")
+        if podman image exists $IMAGE_NAME; then
+            podman image rm -f $IMAGE_NAME
+        fi
+        if podman volume exists $CONTAINER_NAME; then
+            podman volume rm $CONTAINER_NAME
+        fi
+        ;;
+    "make")
+        echo "building ci container..."
+        if ! podman volume exists $CONTAINER_NAME; then
+            podman volume create $CONTAINER_NAME
+        fi
+        podman build -f hitch/Dockerfile-hitch -t $IMAGE_NAME $PROJECT_DIR
+        hitchrun "/venv/bin/python hitch/key.py build"
+        ;;
+    "bash")
+        hitchrun "bash"
+        ;;
+    *)
+        hitchrun "/venv/bin/python hitch/key.py $1 $2 $3 $4 $5 $6 $7 $8 $9"
+        ;; 
+esac
+
+exit
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..b5c302e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,37 @@
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[tool.setuptools]
+packages = ["strictyaml", "strictyaml.ruamel"]
+
+[project]
+name = "strictyaml"
+authors = [
+    {name = "Colm O'Connor", email = "colm.oconnor.github@gmail.com"},
+]
+description = "Strict, typed YAML parser"
+license = {text = "MIT"}
+requires-python = ">=3.7.0"
+keywords = ["yaml"]
+classifiers = [
+    "Programming Language :: Python :: 3",
+    "License :: OSI Approved :: MIT License",
+    "Topic :: Text Processing :: Markup",
+    "Topic :: Software Development :: Libraries",
+    "Natural Language :: English",
+]
+dependencies = [
+    "python-dateutil>=2.6.0"
+]
+dynamic = ["version", "readme"]
+
+[project.urls]
+homepage = "https://hitchdev.com/strictyaml"
+documentation = "https://hitchdev.com/strictyaml/using"
+repository = "https://github.com/crdoconnor/strictyaml"
+changelog = "https://hitchdev.com/strictyaml/changelog"
+
+[tool.setuptools.dynamic]
+readme = {file = ["README.md",], content-type = "text/markdown"}
+version = {file = "VERSION"}
diff --git a/setup.py b/setup.py
index b71b48b..6068493 100644
--- a/setup.py
+++ b/setup.py
@@ -1,50 +1,3 @@
-# -*- coding: utf-8 -*
-from setuptools.command.install import install
-from setuptools import find_packages
 from setuptools import setup
-from sys import version_info, stderr, exit
-import codecs
-import sys
-import os
 
-
-def read(*parts):
-    # intentionally *not* adding an encoding option to open
-    # see here: https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690
-    with codecs.open(os.path.join(os.path.abspath(os.path.dirname(__file__)), *parts)) as f:
-        return f.read()
-
-
-setup(name="strictyaml",
-      version=read('VERSION').replace('\n', ''),
-      description="Strict, typed YAML parser",
-      long_description=read('README.md'),
-      long_description_content_type="text/markdown",
-      classifiers=[
-          'Development Status :: 4 - Beta',
-          'Intended Audience :: Developers',
-          'License :: OSI Approved :: MIT License',
-          'Topic :: Text Processing :: Markup',
-          'Topic :: Software Development :: Libraries',
-          'Natural Language :: English',
-          'Programming Language :: Python :: 2',
-          'Programming Language :: Python :: 2.6',
-          'Programming Language :: Python :: 2.7',
-          'Programming Language :: Python :: 3',
-          'Programming Language :: Python :: 3.1',
-          'Programming Language :: Python :: 3.2',
-          'Programming Language :: Python :: 3.3',
-          'Programming Language :: Python :: 3.4',
-          'Programming Language :: Python :: 3.5',
-      ],
-      keywords='yaml',
-      author='Colm O\'Connor',
-      author_email='colm.oconnor.github@gmail.com',
-      url='http://hitchdev.com/strictyaml',
-      license='MIT',
-      install_requires=["python-dateutil>=2.6.0", ],
-      packages=find_packages(exclude=["tests", "docs", ]),
-      package_data={},
-      zip_safe=False,
-      include_package_data=True,
-)
+setup()
diff --git a/strictyaml/__init__.py b/strictyaml/__init__.py
index 6febcb3..827fb22 100644
--- a/strictyaml/__init__.py
+++ b/strictyaml/__init__.py
@@ -53,4 +53,4 @@ from strictyaml.exceptions import AnchorTokenDisallowed
 from strictyaml.exceptions import DuplicateKeysDisallowed
 from strictyaml import exceptions
 
-__version__ = "DEVELOPMENT_VERSION"
+__version__ = "1.6.2"
diff --git a/strictyaml/compound.py b/strictyaml/compound.py
index 5de73c1..10899be 100644
--- a/strictyaml/compound.py
+++ b/strictyaml/compound.py
@@ -19,7 +19,7 @@ class Optional(object):
 
     def __repr__(self):
         # TODO: Add default
-        return u'Optional("{0}")'.format(self.key)
+        return 'Optional("{0}")'.format(self.key)
 
 
 class MapPattern(MapValidator):
@@ -52,20 +52,20 @@ class MapPattern(MapValidator):
 
         if self._maximum_keys is not None and len(items) > self._maximum_keys:
             chunk.expecting_but_found(
-                u"while parsing a mapping",
-                u"expected a maximum of {0} key{1}, found {2}.".format(
+                "while parsing a mapping",
+                "expected a maximum of {0} key{1}, found {2}.".format(
                     self._maximum_keys,
-                    u"s" if self._maximum_keys > 1 else u"",
+                    "s" if self._maximum_keys > 1 else "",
                     len(items),
                 ),
             )
 
         if self._minimum_keys is not None and len(items) < self._minimum_keys:
             chunk.expecting_but_found(
-                u"while parsing a mapping",
-                u"expected a minimum of {0} key{1}, found {2}.".format(
+                "while parsing a mapping",
+                "expected a minimum of {0} key{1}, found {2}.".format(
                     self._minimum_keys,
-                    u"s" if self._minimum_keys > 1 else u"",
+                    "s" if self._minimum_keys > 1 else "",
                     len(items),
                 ),
             )
@@ -87,7 +87,7 @@ class MapPattern(MapValidator):
         )
 
     def __repr__(self):
-        return u"MapPattern({0}, {1})".format(
+        return "MapPattern({0}, {1})".format(
             repr(self._key_validator), repr(self._value_validator)
         )
 
@@ -138,7 +138,7 @@ class Map(MapValidator):
 
     def __repr__(self):
         # TODO : repr key_validator
-        return u"Map({{{0}}})".format(
+        return "Map({{{0}}})".format(
             ", ".join(
                 [
                     "{0}: {1}".format(repr(key), repr(value))
@@ -152,8 +152,8 @@ class Map(MapValidator):
 
     def unexpected_key(self, key, yaml_key, value, chunk):
         key.expecting_but_found(
-            u"while parsing a mapping",
-            u"unexpected key not in schema '{0}'".format(unicode(yaml_key.scalar)),
+            "while parsing a mapping",
+            "unexpected key not in schema '{0}'".format(unicode(yaml_key.scalar)),
         )
 
     def validate(self, chunk):
@@ -201,8 +201,8 @@ class Map(MapValidator):
 
         if not set(self._required_keys).issubset(found_keys):
             chunk.while_parsing_found(
-                u"a mapping",
-                u"required key(s) '{0}' not found".format(
+                "a mapping",
+                "required key(s) '{0}' not found".format(
                     "', '".join(
                         sorted(list(set(self._required_keys).difference(found_keys)))
                     )
diff --git a/strictyaml/constants.py b/strictyaml/constants.py
index 89598c6..ecc3d05 100644
--- a/strictyaml/constants.py
+++ b/strictyaml/constants.py
@@ -6,6 +6,4 @@ BOOL_VALUES = TRUE_VALUES + FALSE_VALUES
 
 REGEXES = {
     "email": r".+?\@.+?",
-    # https://urlregex.com/
-    "url": r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+",
 }
diff --git a/strictyaml/exceptions.py b/strictyaml/exceptions.py
index c9fc661..4d48dcd 100644
--- a/strictyaml/exceptions.py
+++ b/strictyaml/exceptions.py
@@ -43,7 +43,7 @@ class YAMLValidationError(StrictYAMLError):
     def context_mark(self):
         context_line = self._chunk.start_line() - 1
         str_document = dump(self._chunk.whole_document, Dumper=RoundTripDumper)
-        context_index = len(u"\n".join(str_document.split(u"\n")[:context_line]))
+        context_index = len("\n".join(str_document.split("\n")[:context_line]))
         return StringMark(
             self._chunk.label,
             context_index,
@@ -57,7 +57,7 @@ class YAMLValidationError(StrictYAMLError):
     def problem_mark(self):
         problem_line = self._chunk.end_line() - 1
         str_document = dump(self._chunk.whole_document, Dumper=RoundTripDumper)
-        problem_index = len(u"\n".join(str_document.split(u"\n")[:problem_line]))
+        problem_index = len("\n".join(str_document.split("\n")[:problem_line]))
         return StringMark(
             self._chunk.label,
             problem_index,
diff --git a/strictyaml/parser.py b/strictyaml/parser.py
index 028d3b4..b19245b 100644
--- a/strictyaml/parser.py
+++ b/strictyaml/parser.py
@@ -135,51 +135,51 @@ class StrictYAMLConstructor(RoundTripConstructor):
 
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:null", RoundTripConstructor.construct_yaml_str
+    "tag:yaml.org,2002:null", RoundTripConstructor.construct_yaml_str
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:bool", RoundTripConstructor.construct_yaml_str
+    "tag:yaml.org,2002:bool", RoundTripConstructor.construct_yaml_str
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:int", RoundTripConstructor.construct_yaml_str
+    "tag:yaml.org,2002:int", RoundTripConstructor.construct_yaml_str
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:float", RoundTripConstructor.construct_yaml_str
+    "tag:yaml.org,2002:float", RoundTripConstructor.construct_yaml_str
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:binary", RoundTripConstructor.construct_yaml_str
+    "tag:yaml.org,2002:binary", RoundTripConstructor.construct_yaml_str
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:timestamp", RoundTripConstructor.construct_yaml_str
+    "tag:yaml.org,2002:timestamp", RoundTripConstructor.construct_yaml_str
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:omap", RoundTripConstructor.construct_yaml_omap
+    "tag:yaml.org,2002:omap", RoundTripConstructor.construct_yaml_omap
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:pairs", RoundTripConstructor.construct_yaml_pairs
+    "tag:yaml.org,2002:pairs", RoundTripConstructor.construct_yaml_pairs
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:set", RoundTripConstructor.construct_yaml_set
+    "tag:yaml.org,2002:set", RoundTripConstructor.construct_yaml_set
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:str", RoundTripConstructor.construct_yaml_str
+    "tag:yaml.org,2002:str", RoundTripConstructor.construct_yaml_str
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:seq", RoundTripConstructor.construct_yaml_seq
+    "tag:yaml.org,2002:seq", RoundTripConstructor.construct_yaml_seq
 )
 
 StrictYAMLConstructor.add_constructor(
-    u"tag:yaml.org,2002:map", RoundTripConstructor.construct_yaml_map
+    "tag:yaml.org,2002:map", RoundTripConstructor.construct_yaml_map
 )
 
 StrictYAMLConstructor.add_constructor(None, RoundTripConstructor.construct_undefined)
@@ -257,7 +257,7 @@ class StrictYAMLLoader(
         VersionedResolver.__init__(self, version, loader=self)
 
 
-def as_document(data, schema=None, label=u"<unicode string>"):
+def as_document(data, schema=None, label="<unicode string>"):
     """
     Translate dicts/lists and scalar (string/bool/float/int/etc.) values into a
     YAML object which can be dumped out.
@@ -269,7 +269,7 @@ def as_document(data, schema=None, label=u"<unicode string>"):
 
 
 def generic_load(
-    yaml_string, schema=None, label=u"<unicode string>", allow_flow_style=False
+    yaml_string, schema=None, label="<unicode string>", allow_flow_style=False
 ):
     if not utils.is_string(yaml_string):
         raise TypeError("StrictYAML can only read a string of valid YAML.")
@@ -302,7 +302,7 @@ def generic_load(
 
 
 def dirty_load(
-    yaml_string, schema=None, label=u"<unicode string>", allow_flow_style=False
+    yaml_string, schema=None, label="<unicode string>", allow_flow_style=False
 ):
     """
     Parse the first YAML document in a string
@@ -315,7 +315,7 @@ def dirty_load(
     )
 
 
-def load(yaml_string, schema=None, label=u"<unicode string>"):
+def load(yaml_string, schema=None, label="<unicode string>"):
     """
     Parse the first YAML document in a string
     and produce corresponding YAML object.
diff --git a/strictyaml/representation.py b/strictyaml/representation.py
index a0d8dcf..25a4342 100644
--- a/strictyaml/representation.py
+++ b/strictyaml/representation.py
@@ -22,7 +22,6 @@ if sys.version_info[:2] < (3, 7):
                 + "}"
             )
 
-
 else:
     OrderedDict = dict
 
@@ -176,7 +175,7 @@ class YAML(object):
         return float(self._value)
 
     def __repr__(self):
-        return u"YAML({0})".format(self.data)
+        return "YAML({0})".format(self.data)
 
     def __bool__(self):
         if isinstance(self._value, bool):
diff --git a/strictyaml/scalar.py b/strictyaml/scalar.py
index 8f45524..cb6e33f 100644
--- a/strictyaml/scalar.py
+++ b/strictyaml/scalar.py
@@ -10,6 +10,7 @@ import dateutil.parser
 import decimal
 import sys
 import re
+import urllib.parse
 from strictyaml.ruamel.scalarstring import PreservedScalarString
 
 
@@ -70,7 +71,7 @@ class Enum(ScalarValidator):
 
     def __repr__(self):
         # TODO : item_validator
-        return u"Enum({0})".format(repr(self._restricted_to))
+        return "Enum({0})".format(repr(self._restricted_to))
 
 
 class CommaSeparated(ScalarValidator):
@@ -144,10 +145,24 @@ class Email(Regex):
         self._matching_message = "when expecting an email address"
 
 
-class Url(Regex):
-    def __init__(self):
-        super(Url, self).__init__(constants.REGEXES["url"])
-        self._matching_message = "when expecting a url"
+class Url(ScalarValidator):
+    def __is_absolute_url(self, raw):
+        try:
+            ret = urllib.parse.urlparse(raw)
+            return ret.scheme != "" and ret.netloc != ""
+        except ValueError:
+            return False
+
+    def validate_scalar(self, chunk):
+        if not self.__is_absolute_url(chunk.contents):
+            chunk.expecting_but_found("when expecting a URL")
+        return chunk.contents
+
+    def to_yaml(self, data):
+        self.should_be_string(data, "expected a URL,")
+        if not self.__is_absolute_url(data):
+            raise YAMLSerializationError("'{}' is not a URL".format(data))
+        return data
 
 
 class Str(ScalarValidator):
@@ -216,7 +231,7 @@ class Bool(ScalarValidator):
             else:
                 raise YAMLSerializationError("Not a boolean")
         else:
-            return u"yes" if data else u"no"
+            return "yes" if data else "no"
 
 
 class Float(ScalarValidator):
@@ -292,7 +307,7 @@ class NullNone(ScalarValidator):
 
     def to_yaml(self, data):
         if data is None:
-            return u"null"
+            return "null"
         raise YAMLSerializationError("expected None, got '{}'")
 
 
@@ -309,7 +324,7 @@ class EmptyNone(ScalarValidator):
 
     def to_yaml(self, data):
         if data is None:
-            return u""
+            return ""
         raise YAMLSerializationError("expected None, got '{}'")
 
 
@@ -319,7 +334,7 @@ class EmptyDict(EmptyNone):
 
     def to_yaml(self, data):
         if data == {}:
-            return u""
+            return ""
         raise YAMLSerializationError("Not an empty dict")
 
 
@@ -329,5 +344,5 @@ class EmptyList(EmptyNone):
 
     def to_yaml(self, data):
         if data == []:
-            return u""
+            return ""
         raise YAMLSerializationError("expected empty list, got '{}'")
diff --git a/strictyaml/utils.py b/strictyaml/utils.py
index c653fe8..32ce5e0 100644
--- a/strictyaml/utils.py
+++ b/strictyaml/utils.py
@@ -240,7 +240,7 @@ def ruamel_structure(data, validator=None):
             )
         return CommentedSeq([ruamel_structure(item) for item in data])
     elif isinstance(data, bool):
-        return u"yes" if data else u"no"
+        return "yes" if data else "no"
     elif isinstance(data, (int, float)):
         return str(data)
     else:
diff --git a/strictyaml/validators.py b/strictyaml/validators.py
index 4362618..b63643b 100644
--- a/strictyaml/validators.py
+++ b/strictyaml/validators.py
@@ -18,7 +18,7 @@ class Validator(object):
         return YAML(chunk, validator=self)
 
     def __repr__(self):
-        return u"{0}()".format(self.__class__.__name__)
+        return "{0}()".format(self.__class__.__name__)
 
 
 class MapValidator(Validator):
@@ -115,4 +115,4 @@ class OrValidator(Validator):
             return result
 
     def __repr__(self):
-        return u"{0} | {1}".format(repr(self._validator_a), repr(self._validator_b))
+        return "{0} | {1}".format(repr(self._validator_a), repr(self._validator_b))
diff --git a/strictyaml/yamllocation.py b/strictyaml/yamllocation.py
index a81c9d7..cdffa1d 100644
--- a/strictyaml/yamllocation.py
+++ b/strictyaml/yamllocation.py
@@ -88,17 +88,17 @@ class YAMLChunk(object):
 
     def found(self):
         if self.is_sequence():
-            return u"a sequence"
+            return "a sequence"
         elif self.is_mapping():
-            return u"a mapping"
-        elif self.contents == u"":
-            return u"a blank string"
+            return "a mapping"
+        elif self.contents == "":
+            return "a blank string"
         elif utils.is_integer(self.contents):
-            return u"an arbitrary integer"
+            return "an arbitrary integer"
         elif utils.is_decimal(self.contents):
-            return u"an arbitrary number"
+            return "an arbitrary number"
         else:
-            return u"arbitrary text"
+            return "arbitrary text"
 
     def expect_sequence(self, expecting="when expecting a sequence"):
         if not self.is_sequence():

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/strictyaml-1.7.3.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/strictyaml-1.7.3.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/strictyaml-1.7.3.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/strictyaml-1.7.3.egg-info/top_level.txt

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/strictyaml-1.6.1.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/strictyaml-1.6.1.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/strictyaml-1.6.1.egg-info/not-zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/strictyaml-1.6.1.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/strictyaml-1.6.1.egg-info/top_level.txt

No differences were encountered in the control files

More details

Full run details