diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ca51e18665ffb1b19793a60c779086e700052561..1e94b4e92a25d9f197875bfe66707478d469ba61 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,14 +1,70 @@
+# vim:ff=unix ts=2 sw=2 ai expandtab
+---
 repos:
--   repo: https://github.com/pre-commit/pre-commit-hooks
-    sha: v1.2.3
-    hooks:
-    -   id: check-added-large-files
-    -   id: check-yaml
-    -   id: check-merge-conflict
-    -   id: flake8
-    -   id: check-symlinks
--   repo: https://github.com/ambv/black
-    sha: 18.5b0
-    hooks:
-    -   id: black
-        args: [--line-length=79]
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v2.2.3
+    hooks:
+      - id: check-added-large-files
+      - id: check-executables-have-shebangs
+      - id: check-merge-conflict
+      - id: detect-private-key
+      - id: trailing-whitespace
+  - repo: https://github.com/ambv/black
+    rev: 18.9b0
+    hooks:
+      - id: black
+        args:
+          - |
+              --line-length=79
+  - repo: https://github.com/Lucas-C/pre-commit-hooks-markup
+    rev: v1.0.0
+    hooks:
+      - id: rst-linter
+  - repo: https://github.com/PyCQA/prospector
+    rev: 1.1.6.4
+    hooks:
+      - id: prospector
+        args:
+          - |-
+              --max-line-length=79
+          - |-
+              --tool=pyroma
+          - |-
+              --tool=dodgy
+        additional_dependencies:
+          - pyroma
+          - dodgy
+  - repo: https://gitlab.com/pycqa/flake8
+    rev: 3.7.7
+    hooks:
+      - id: flake8
+        args:
+          - |-
+            --max-line-length=79
+        additional_dependencies:
+          - flake8-bugbear
+  - repo: https://github.com/pre-commit/mirrors-pylint
+    rev: v2.3.1
+    hooks:
+      - id: pylint
+        args:
+          - |-
+            --disable=R0801
+  - repo: https://github.com/adrienverge/yamllint
+    rev: v1.16.0
+    hooks:
+      - id: yamllint
+  - repo: https://github.com/PyCQA/bandit
+    rev: 1.6.1
+    hooks:
+      - id: bandit
+  - repo: https://github.com/amperser/proselint/
+    rev: 0.10.2
+    hooks:
+      - id: proselint
+        types: [plain-text]
+        exclude: LICENSE|requirements
+  - repo: https://github.com/mgedmin/check-manifest
+    rev: "0.39"
+    hooks:
+      - id: check-manifest
diff --git a/.travis.yml b/.travis.yml
index 9346f7d2e132852347d3b667316a16b6399024f4..359681d2412b49688e85065413b96b4fe6e9d6e0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,29 +1,44 @@
+# vim:ff=unix ts=2 sw=2 ai expandtab
 ---
 language: python
-python: ["2.7", "3.4", "3.5", "3.6"]
-dist: trusty
+python:
+  - "2.7"
+  - "3.4"
+  - "3.5"
+  - "3.6"
+  - "3.7"
+dist: xenial
 sudo: false
 cache:
   - pip
+  - $HOME/.pre-commit
+
 matrix:
   include:
-    - python: "3.6"
-      env: TOXENV=docs
-    - python: "3.6"
-      env: TOXENV=bandit
-    - python: "3.6"
-      env: TOXENV=pre-commit
-  allow_failures:
-    - python: "3.2"
+    - python: "3.7"
+      env:
+        PIPENV_IGNORE_VIRTUALENVS: "1"
+      install:
+        - pipenv install --dev
+      addons:
+        apt:
+          packages:
+            - libdbus-1-dev
+            - libglib2.0-dev
+      script:
+        - pipenv run lint
+        - pipenv run check
 
 install:
-  - git clone --depth 1 https://github.com/sstephenson/bats "$HOME/bats"
-  - pip install tox-travis | cat
+  - git clone --depth 1 https://github.com/bats-core/bats-core "$HOME/bats"
+  - pip install . | cat
 
 env:
-    PATH: "$PATH:$HOME/bats/bin"
+  PATH: "$PATH:$HOME/bats/bin:$HOME/.local/bin"
+
 script:
-  - tox
+  - bats -t tests/
+  - python -m doctest template/*.py
 
 notifications:
   email: false
diff --git a/MANIFEST.in b/MANIFEST.in
index 30ab8989bd7e8bf4681dd29d8f9cc36fcf59dab3..bc8756d871ba08f8804ca67ba57e4033e0df2653 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,9 @@
 recursive-include template *.py
-exclude .pre-commit-config.yaml
-exclude *.bats
 include *.rst
-include VERSION
 include *.txt
+exclude .pre-commit-config.yaml
+exclude .travis.yml
+exclude .gitignore
+exclude Pipfile*
+exclude tests
+exclude tests/*
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 0000000000000000000000000000000000000000..d9f28ee6a215e004d798ec4cb14a25b1b7b337a3
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,26 @@
+[[source]]
+name = "pypi"
+url = "https://pypi.org/simple"
+verify_ssl = true
+
+[dev-packages]
+pre-commit = "*"
+bumpversion = "*"
+twine = "*"
+dbus-python = "*"
+keyring = "*"
+template = {editable = true,path = "."}
+
+[packages]
+
+[requires]
+python_version = "3.7"
+
+[scripts]
+lint = "pre-commit run --all-files"
+build = "sh -c 'git clean -fdX && python setup.py bdist_wheel'"
+clean = "git clean -fdX"
+upload = "sh -c 'git clean -fdX && python setup.py bdist_wheel && twine upload -s dist/*'"
+bats = "bats -t tests/"
+check = "sh -c 'rm -rf dist/ && python setup.py bdist_wheel && twine check dist/*'"
+doctest = "sh -c 'python -m doctest template/*.py'"
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 0000000000000000000000000000000000000000..144a03dc509e7eb77e9cff676ae4bf4c169bf60c
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,399 @@
+{
+    "_meta": {
+        "hash": {
+            "sha256": "62827577f8781dd89462ca09eb23122a71010f2f121432f74817d72b1fad6bac"
+        },
+        "pipfile-spec": 6,
+        "requires": {
+            "python_version": "3.7"
+        },
+        "sources": [
+            {
+                "name": "pypi",
+                "url": "https://pypi.org/simple",
+                "verify_ssl": true
+            }
+        ]
+    },
+    "default": {},
+    "develop": {
+        "asn1crypto": {
+            "hashes": [
+                "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
+                "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
+            ],
+            "version": "==0.24.0"
+        },
+        "aspy.yaml": {
+            "hashes": [
+                "sha256:463372c043f70160a9ec950c3f1e4c3a82db5fca01d334b6bc89c7164d744bdc",
+                "sha256:e7c742382eff2caed61f87a39d13f99109088e5e93f04d76eb8d4b28aa143f45"
+            ],
+            "version": "==1.3.0"
+        },
+        "bleach": {
+            "hashes": [
+                "sha256:213336e49e102af26d9cde77dd2d0397afabc5a6bf2fed985dc35b5d1e285a16",
+                "sha256:3fdf7f77adcf649c9911387df51254b813185e32b2c6619f690b593a617e19fa"
+            ],
+            "version": "==3.1.0"
+        },
+        "bumpversion": {
+            "hashes": [
+                "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e",
+                "sha256:6753d9ff3552013e2130f7bc03c1007e24473b4835952679653fb132367bdd57"
+            ],
+            "index": "pypi",
+            "version": "==0.5.3"
+        },
+        "certifi": {
+            "hashes": [
+                "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
+                "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
+            ],
+            "version": "==2019.6.16"
+        },
+        "cffi": {
+            "hashes": [
+                "sha256:041c81822e9f84b1d9c401182e174996f0bae9991f33725d059b771744290774",
+                "sha256:046ef9a22f5d3eed06334d01b1e836977eeef500d9b78e9ef693f9380ad0b83d",
+                "sha256:066bc4c7895c91812eff46f4b1c285220947d4aa46fa0a2651ff85f2afae9c90",
+                "sha256:066c7ff148ae33040c01058662d6752fd73fbc8e64787229ea8498c7d7f4041b",
+                "sha256:2444d0c61f03dcd26dbf7600cf64354376ee579acad77aef459e34efcb438c63",
+                "sha256:300832850b8f7967e278870c5d51e3819b9aad8f0a2c8dbe39ab11f119237f45",
+                "sha256:34c77afe85b6b9e967bd8154e3855e847b70ca42043db6ad17f26899a3df1b25",
+                "sha256:46de5fa00f7ac09f020729148ff632819649b3e05a007d286242c4882f7b1dc3",
+                "sha256:4aa8ee7ba27c472d429b980c51e714a24f47ca296d53f4d7868075b175866f4b",
+                "sha256:4d0004eb4351e35ed950c14c11e734182591465a33e960a4ab5e8d4f04d72647",
+                "sha256:4e3d3f31a1e202b0f5a35ba3bc4eb41e2fc2b11c1eff38b362de710bcffb5016",
+                "sha256:50bec6d35e6b1aaeb17f7c4e2b9374ebf95a8975d57863546fa83e8d31bdb8c4",
+                "sha256:55cad9a6df1e2a1d62063f79d0881a414a906a6962bc160ac968cc03ed3efcfb",
+                "sha256:5662ad4e4e84f1eaa8efce5da695c5d2e229c563f9d5ce5b0113f71321bcf753",
+                "sha256:59b4dc008f98fc6ee2bb4fd7fc786a8d70000d058c2bbe2698275bc53a8d3fa7",
+                "sha256:73e1ffefe05e4ccd7bcea61af76f36077b914f92b76f95ccf00b0c1b9186f3f9",
+                "sha256:a1f0fd46eba2d71ce1589f7e50a9e2ffaeb739fb2c11e8192aa2b45d5f6cc41f",
+                "sha256:a2e85dc204556657661051ff4bab75a84e968669765c8a2cd425918699c3d0e8",
+                "sha256:a5457d47dfff24882a21492e5815f891c0ca35fefae8aa742c6c263dac16ef1f",
+                "sha256:a8dccd61d52a8dae4a825cdbb7735da530179fea472903eb871a5513b5abbfdc",
+                "sha256:ae61af521ed676cf16ae94f30fe202781a38d7178b6b4ab622e4eec8cefaff42",
+                "sha256:b012a5edb48288f77a63dba0840c92d0504aa215612da4541b7b42d849bc83a3",
+                "sha256:d2c5cfa536227f57f97c92ac30c8109688ace8fa4ac086d19d0af47d134e2909",
+                "sha256:d42b5796e20aacc9d15e66befb7a345454eef794fdb0737d1af593447c6c8f45",
+                "sha256:dee54f5d30d775f525894d67b1495625dd9322945e7fee00731952e0368ff42d",
+                "sha256:e070535507bd6aa07124258171be2ee8dfc19119c28ca94c9dfb7efd23564512",
+                "sha256:e1ff2748c84d97b065cc95429814cdba39bcbd77c9c85c89344b317dc0d9cbff",
+                "sha256:ed851c75d1e0e043cbf5ca9a8e1b13c4c90f3fbd863dacb01c0808e2b5204201"
+            ],
+            "version": "==1.12.3"
+        },
+        "cfgv": {
+            "hashes": [
+                "sha256:32edbe09de6f4521224b87822103a8c16a614d31a894735f7a5b3bcf0eb3c37e",
+                "sha256:3bd31385cd2bebddbba8012200aaf15aa208539f1b33973759b4d02fc2148da5"
+            ],
+            "version": "==2.0.0"
+        },
+        "chardet": {
+            "hashes": [
+                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
+                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
+            ],
+            "version": "==3.0.4"
+        },
+        "cryptography": {
+            "hashes": [
+                "sha256:24b61e5fcb506424d3ec4e18bca995833839bf13c59fc43e530e488f28d46b8c",
+                "sha256:25dd1581a183e9e7a806fe0543f485103232f940fcfc301db65e630512cce643",
+                "sha256:3452bba7c21c69f2df772762be0066c7ed5dc65df494a1d53a58b683a83e1216",
+                "sha256:41a0be220dd1ed9e998f5891948306eb8c812b512dc398e5a01846d855050799",
+                "sha256:5751d8a11b956fbfa314f6553d186b94aa70fdb03d8a4d4f1c82dcacf0cbe28a",
+                "sha256:5f61c7d749048fa6e3322258b4263463bfccefecb0dd731b6561cb617a1d9bb9",
+                "sha256:72e24c521fa2106f19623a3851e9f89ddfdeb9ac63871c7643790f872a305dfc",
+                "sha256:7b97ae6ef5cba2e3bb14256625423413d5ce8d1abb91d4f29b6d1a081da765f8",
+                "sha256:961e886d8a3590fd2c723cf07be14e2a91cf53c25f02435c04d39e90780e3b53",
+                "sha256:96d8473848e984184b6728e2c9d391482008646276c3ff084a1bd89e15ff53a1",
+                "sha256:ae536da50c7ad1e002c3eee101871d93abdc90d9c5f651818450a0d3af718609",
+                "sha256:b0db0cecf396033abb4a93c95d1602f268b3a68bb0a9cc06a7cff587bb9a7292",
+                "sha256:cfee9164954c186b191b91d4193989ca994703b2fff406f71cf454a2d3c7327e",
+                "sha256:e6347742ac8f35ded4a46ff835c60e68c22a536a8ae5c4422966d06946b6d4c6",
+                "sha256:f27d93f0139a3c056172ebb5d4f9056e770fdf0206c2f422ff2ebbad142e09ed",
+                "sha256:f57b76e46a58b63d1c6375017f4564a28f19a5ca912691fd2e4261b3414b618d"
+            ],
+            "version": "==2.7"
+        },
+        "dbus-python": {
+            "hashes": [
+                "sha256:abf12bbb765e300bf8e2a1b2f32f85949eab06998dbda127952c31cb63957b6f"
+            ],
+            "index": "pypi",
+            "version": "==1.2.8"
+        },
+        "docutils": {
+            "hashes": [
+                "sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6",
+                "sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274",
+                "sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6"
+            ],
+            "version": "==0.14"
+        },
+        "entrypoints": {
+            "hashes": [
+                "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19",
+                "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"
+            ],
+            "version": "==0.3"
+        },
+        "hashin": {
+            "hashes": [
+                "sha256:dbace6900d8de44f3106a64496803e45843cf4974755613db811a487fadbf4c6",
+                "sha256:fe764df71cabbbddfa72aa4d6685581c932bb5cf9100ddee6b2b04f3446ae2f7"
+            ],
+            "index": "pypi",
+            "version": "==0.14.5"
+        },
+        "identify": {
+            "hashes": [
+                "sha256:0a11379b46d06529795442742a043dc2fa14cd8c995ae81d1febbc5f1c014c87",
+                "sha256:43a5d24ffdb07bc7e21faf68b08e9f526a1f41f0056073f480291539ef961dfd"
+            ],
+            "version": "==1.4.5"
+        },
+        "idna": {
+            "hashes": [
+                "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
+                "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
+            ],
+            "version": "==2.8"
+        },
+        "importlib-metadata": {
+            "hashes": [
+                "sha256:6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7",
+                "sha256:cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db"
+            ],
+            "version": "==0.18"
+        },
+        "jeepney": {
+            "hashes": [
+                "sha256:6089412a5de162c04747f0220f6b2223b8ba660acd041e52a76426ca550e3c70",
+                "sha256:f6f8b1428403b4afad04b6b82f9ab9fc426c253d7504c9031c41712a2c01dc74"
+            ],
+            "version": "==0.4"
+        },
+        "jinja2": {
+            "hashes": [
+                "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013",
+                "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"
+            ],
+            "version": "==2.10.1"
+        },
+        "jmespath": {
+            "hashes": [
+                "sha256:3720a4b1bd659dd2eecad0666459b9788813e032b83e7ba58578e48254e0a0e6",
+                "sha256:bde2aef6f44302dfb30320115b17d030798de8c4110e28d5cf6cf91a7a31074c"
+            ],
+            "version": "==0.9.4"
+        },
+        "keyring": {
+            "hashes": [
+                "sha256:1b74595f7439e4581a11d4f9a12790ac34addce64ca389c86272ff465f5e0b90",
+                "sha256:afbfe7bc9bdba69d25c551b0c738adde533d87e0b51ad6bbe332cbea19ad8476"
+            ],
+            "index": "pypi",
+            "version": "==19.0.2"
+        },
+        "markupsafe": {
+            "hashes": [
+                "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
+                "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
+                "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
+                "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
+                "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
+                "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
+                "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
+                "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
+                "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
+                "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
+                "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
+                "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
+                "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
+                "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
+                "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
+                "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
+                "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
+                "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
+                "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
+                "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
+                "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
+                "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
+                "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
+                "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
+                "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
+                "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
+                "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
+                "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"
+            ],
+            "version": "==1.1.1"
+        },
+        "nodeenv": {
+            "hashes": [
+                "sha256:ad8259494cf1c9034539f6cced78a1da4840a4b157e23640bc4a0c0546b0cb7a"
+            ],
+            "version": "==1.3.3"
+        },
+        "packaging": {
+            "hashes": [
+                "sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af",
+                "sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3"
+            ],
+            "version": "==19.0"
+        },
+        "pip-api": {
+            "hashes": [
+                "sha256:742b3edb5b077853ffcfec8a849f6ff1a622c6ecf648adb5fa0e4594998fcd46",
+                "sha256:a685eb315b9f10f6df4ceb66f3710148c8ad7de2d4ff29ee98fed0e4d949ec81"
+            ],
+            "version": "==0.0.10"
+        },
+        "pkginfo": {
+            "hashes": [
+                "sha256:7424f2c8511c186cd5424bbf31045b77435b37a8d604990b79d4e70d741148bb",
+                "sha256:a6d9e40ca61ad3ebd0b72fbadd4fba16e4c0e4df0428c041e01e06eb6ee71f32"
+            ],
+            "version": "==1.5.0.1"
+        },
+        "pre-commit": {
+            "hashes": [
+                "sha256:92e406d556190503630fd801958379861c94884693a032ba66629d0351fdccd4",
+                "sha256:cccc39051bc2457b0c0f7152a411f8e05e3ba2fe1a5613e4ee0833c1c1985ce3"
+            ],
+            "index": "pypi",
+            "version": "==1.17.0"
+        },
+        "pycparser": {
+            "hashes": [
+                "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
+            ],
+            "version": "==2.19"
+        },
+        "pygments": {
+            "hashes": [
+                "sha256:71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127",
+                "sha256:881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"
+            ],
+            "version": "==2.4.2"
+        },
+        "pyparsing": {
+            "hashes": [
+                "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a",
+                "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03"
+            ],
+            "version": "==2.4.0"
+        },
+        "pyyaml": {
+            "hashes": [
+                "sha256:57acc1d8533cbe51f6662a55434f0dbecfa2b9eaf115bede8f6fd00115a0c0d3",
+                "sha256:588c94b3d16b76cfed8e0be54932e5729cc185caffaa5a451e7ad2f7ed8b4043",
+                "sha256:68c8dd247f29f9a0d09375c9c6b8fdc64b60810ebf07ba4cdd64ceee3a58c7b7",
+                "sha256:70d9818f1c9cd5c48bb87804f2efc8692f1023dac7f1a1a5c61d454043c1d265",
+                "sha256:86a93cccd50f8c125286e637328ff4eef108400dd7089b46a7be3445eecfa391",
+                "sha256:a0f329125a926876f647c9fa0ef32801587a12328b4a3c741270464e3e4fa778",
+                "sha256:a3c252ab0fa1bb0d5a3f6449a4826732f3eb6c0270925548cac342bc9b22c225",
+                "sha256:b4bb4d3f5e232425e25dda21c070ce05168a786ac9eda43768ab7f3ac2770955",
+                "sha256:cd0618c5ba5bda5f4039b9398bb7fb6a317bb8298218c3de25c47c4740e4b95e",
+                "sha256:ceacb9e5f8474dcf45b940578591c7f3d960e82f926c707788a570b51ba59190",
+                "sha256:fe6a88094b64132c4bb3b631412e90032e8cfe9745a58370462240b8cb7553cd"
+            ],
+            "version": "==5.1.1"
+        },
+        "readme-renderer": {
+            "hashes": [
+                "sha256:bb16f55b259f27f75f640acf5e00cf897845a8b3e4731b5c1a436e4b8529202f",
+                "sha256:c8532b79afc0375a85f10433eca157d6b50f7d6990f337fa498c96cd4bfc203d"
+            ],
+            "version": "==24.0"
+        },
+        "requests": {
+            "hashes": [
+                "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
+                "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
+            ],
+            "version": "==2.22.0"
+        },
+        "requests-toolbelt": {
+            "hashes": [
+                "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f",
+                "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
+            ],
+            "version": "==0.9.1"
+        },
+        "secretstorage": {
+            "hashes": [
+                "sha256:20c797ae48a4419f66f8d28fc221623f11fc45b6828f96bdb1ad9990acb59f92",
+                "sha256:7a119fb52a88e398dbb22a4b3eb39b779bfbace7e4153b7bc6e5954d86282a8a"
+            ],
+            "markers": "sys_platform == 'linux'",
+            "version": "==3.1.1"
+        },
+        "six": {
+            "hashes": [
+                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
+                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
+            ],
+            "version": "==1.12.0"
+        },
+        "template": {
+            "editable": true,
+            "path": "."
+        },
+        "toml": {
+            "hashes": [
+                "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
+                "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
+            ],
+            "version": "==0.10.0"
+        },
+        "tqdm": {
+            "hashes": [
+                "sha256:14a285392c32b6f8222ecfbcd217838f88e11630affe9006cd0e94c7eff3cb61",
+                "sha256:25d4c0ea02a305a688e7e9c2cdc8f862f989ef2a4701ab28ee963295f5b109ab"
+            ],
+            "version": "==4.32.2"
+        },
+        "twine": {
+            "hashes": [
+                "sha256:0fb0bfa3df4f62076cab5def36b1a71a2e4acb4d1fa5c97475b048117b1a6446",
+                "sha256:d6c29c933ecfc74e9b1d9fa13aa1f87c5d5770e119f5a4ce032092f0ff5b14dc"
+            ],
+            "index": "pypi",
+            "version": "==1.13.0"
+        },
+        "urllib3": {
+            "hashes": [
+                "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
+                "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
+            ],
+            "version": "==1.25.3"
+        },
+        "virtualenv": {
+            "hashes": [
+                "sha256:b7335cddd9260a3dd214b73a2521ffc09647bde3e9457fcca31dc3be3999d04a",
+                "sha256:d28ca64c0f3f125f59cabf13e0a150e1c68e5eea60983cc4395d88c584495783"
+            ],
+            "version": "==16.6.1"
+        },
+        "webencodings": {
+            "hashes": [
+                "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78",
+                "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"
+            ],
+            "version": "==0.5.1"
+        },
+        "zipp": {
+            "hashes": [
+                "sha256:8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d",
+                "sha256:ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3"
+            ],
+            "version": "==0.5.1"
+        }
+    }
+}
diff --git a/README.rst b/README.rst
index 3b9173fd9083d12fcda97f503527a932e9afbeb7..c69b3074796dc7b2ed198c960c3d96483f9d6239 100644
--- a/README.rst
+++ b/README.rst
@@ -5,7 +5,7 @@ Template
     :target: https://travis-ci.org/adarnimrod/template
 
 A CLI tool for generating files from Jinja2 templates and environment
-variables.
+variables. Tested on Python versions 2.7, 3.4 and later.
 
 Examples
 --------
@@ -54,16 +54,27 @@ docstrings in :code:`template/filters.py`.
 Testing
 -------
 
-Tests require Python 2.7, Python 3.3 or later, Tox and Bats and are run by
-running :code:`tox`. Also, Travis CI is used to test on multiple Python
-versions for every push.
+Tests require Python 3.7, `pipenv <https://docs.pipenv.org>` and
+`Bats <https://github.com/bats-core/bats-core>`. Run the tests with the
+following commands:
+
+.. code:: shell
+
+   pipenv run lint  # Pre-commit hooks.
+   pipenv run check  # Twine check.
+   pipenv run doctest  # Doc tests.
+   pipenv run bats  # Bats tests.
+
+Also, Travis CI is setup for this project so every push to this repository is
+checked with all supported Python versions.
 
 Release
 -------
 
-Releases require Python 2.7 or Python 3.3 or later and Tox. To release a new
-version bump the version in the :code:`VERSION` file and run :code:`tox -e
-release`.
+Release requires Python 3.7 and `pipenv <https://docs.pipenv.org>`. To bump the
+version run :code:`pipenv run bumpversion major|minor|patch` to update the
+version and git commit and tag. Then run :code:`pipenv run upload` to upload the
+new version to PyPI.
 
 License
 -------
@@ -79,8 +90,8 @@ Nimrod Adar, `contact me <nimrod@shore.co.il>`_ or visit my `website
 <http://git-scm.com/book/en/v2/Git-Commands-Email>`_. The repository is located
 at: https://www.shore.co.il/git/.
 
-TODO
-----
+Pending tasks
+-------------
 
 - Release on tagged commits to PyPI in Travis CI
   (https://docs.travis-ci.com/user/deployment/pypi/ and
diff --git a/VERSION b/VERSION
deleted file mode 100644
index c0a1ac19918dd682d4bee32525557a25d7444c94..0000000000000000000000000000000000000000
--- a/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-0.4.6
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index 3c6e79cf31da1c0433d2fa666bf50b53f6359f26..a9aaff61d80dbaa29150feed87b05a05ba5124de 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,12 @@
+[bumpversion]
+current_version = 0.4.6
+commit = True
+tag = True
+
 [bdist_wheel]
-universal=1
+universal = 1
+
+[bumpversion:file:setup.py]
+
+[bumpversion:file:template/__init__.py]
+
diff --git a/setup.py b/setup.py
index 1532a73c5aec8d86d4a95565733113a39df67255..fade778517b122f456fccf99a05c733ad6bd47a0 100644
--- a/setup.py
+++ b/setup.py
@@ -1,27 +1,35 @@
 #!/usr/bin/env python
+# pylint: disable=missing-docstring
 from setuptools import setup, find_packages
 
 setup(
     name="template",
-    version=open("VERSION", "r").read(),
+    version="0.4.6",
     description="""A CLI tool for generating files from Jinja2 templates and
     environment variables.""",
     long_description=open("README.rst", "r").read(),
+    long_description_content_type="text/x-rst",
     url="https://www.shore.co.il/git/template",
     author="Nimrod Adar",
     author_email="nimrod@shore.co.il",
-    license="MIT",
+    license="AGPLv3+",
     classifiers=[
         "Development Status :: 4 - Beta",
         "Intended Audience :: Developers",
         "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.4",
+        "Programming Language :: Python :: 3.5",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 2.7",
         "Intended Audience :: System Administrators",
         "Topic :: Utilities",
+        "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",  # noqa: E501 pylint: disable=line-too-long
     ],
     keywords="config configuration jinja template environment",
     packages=find_packages(),
     install_requires=["Jinja2", "PyYAML", "jmespath", "toml"],
-    extras_require={"dev": ["tox"]},
+    extras_require={"dev": ["pipenv"]},
     entry_points={"console_scripts": ["template=template:main"]},
 )
diff --git a/template/__init__.py b/template/__init__.py
index 95394146a4f9d0d8c53066f4acaff22c1bee0fd3..0718328dc4d8084bbf4314bdf48637a3dc3c7d6b 100755
--- a/template/__init__.py
+++ b/template/__init__.py
@@ -1,4 +1,5 @@
 #!/usr/bin/env python
+# pylint: disable=import-error
 """Generate files from Jinja2 templates and environment variables."""
 
 from __future__ import (
@@ -6,26 +7,31 @@ from __future__ import (
     division,
     print_function,
     unicode_literals,
-)
-from jinja2 import Environment
+)  # pylint: disable=duplicate-code
 from os import environ
 from sys import stdin, stdout
 import argparse
 from argparse import ArgumentParser
 import template.filters
+from jinja2 import Environment
+
+
+__version__ = "0.4.6"
 
 
 def render(template_string):
     """Render the template."""
     env = Environment(autoescape=True)
     # Add all functions in template.filters as Jinja filters.
+    # pylint: disable=invalid-name
     for tf in filter(lambda x: not x.startswith("_"), dir(template.filters)):
-        env.filters[tf] = template.filters.__getattribute__(tf)
+        env.filters[tf] = getattr(template.filters, tf)
     t = env.from_string(template_string)
     return t.render(environ)
 
 
 def main():
+    """Main entrypoint."""
     parser = ArgumentParser(
         description="""A CLI tool for generating files from Jinja2 templates
         and environment variables."""
diff --git a/template/filters.py b/template/filters.py
index 7ad2b6bb6b9560a1741bec72b9bd39a687a46931..f17598f4ed12bf51b8a78db4f4f6f86372142564 100644
--- a/template/filters.py
+++ b/template/filters.py
@@ -1,25 +1,29 @@
 #!/usr/bin/env python
+"""Filters for the template CLI."""
+# pylint: disable=import-error
+
+
 from __future__ import (
     absolute_import,
     division,
     print_function,
     unicode_literals,
-)
+)  # pylint: disable=duplicate-code
 
 
 def to_yaml(value):
-    """
+    r"""
     Converts given data structure to YAML form.
     Examples:
 
     >>> to_yaml([1,2,3])
-    '[1, 2, 3]\\n'
+    '- 1\n- 2\n- 3\n'
     >>> to_yaml({'a': 1, 'b': 2})
-    '{a: 1, b: 2}\\n'
+    'a: 1\nb: 2\n'
     >>> to_yaml({1: {'a': [1,2,3]}})
-    '1:\\n  a: [1, 2, 3]\\n'
+    '1:\n  a:\n  - 1\n  - 2\n  - 3\n'
     >>> to_yaml("abc")
-    'abc\\n...\\n'
+    'abc\n...\n'
     """
     from yaml import safe_dump
 
@@ -136,6 +140,6 @@ def jmespath(value, query):
     ...
     True
     """
-    import jmespath
+    import jmespath as jp
 
-    return jmespath.search(query, value)
+    return jp.search(query, value)
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index 557e4fcd37f3d1eac2a2dc2001566e5ac141652b..0000000000000000000000000000000000000000
--- a/tox.ini
+++ /dev/null
@@ -1,63 +0,0 @@
-[tox]
-envlist = py{2,3},docs,bandit,pre-commit
-
-[travis]
-python =
-    2.7: py2
-    3.4: py3
-    3.5: py3
-    3.6: py3
-
-[testenv]
-basepython =
-    py2: python2
-    py3: python3
-deps =
-    check-manifest
-    readme_renderer
-    flake8
-    six
-whitelist_externals =
-    bats
-passenv = HOME TERM
-commands =
-    check-manifest --ignore tox.ini,tests*
-    python setup.py check --metadata --strict
-    python -m doctest template/filters.py template/__init__.py
-    bats -t tests
-
-[testenv:docs]
-basepython = python
-deps = readme_renderer
-commands = python setup.py check --restructuredtext --strict
-passenv = HOME TERM
-
-[testenv:release]
-basepython = python
-passenv = HOME TERM
-whitelist_externals =
-    sh
-deps =
-    twine
-    wheel
-commands =
-    sh -c 'git tag -a "v$(cat VERSION)" && git push --tags'
-    sh -c 'rm -rf dist/'
-    python setup.py bdist_wheel
-    twine upload --skip-existing dist/*.whl
-
-[testenv:bandit]
-basepython = python
-passenv = HOME TERM
-deps = bandit
-commands = bandit --recursive ./ --exclude .tox/,build/,dist/,template.egg-info
-
-[testenv:pre-commit]
-basepython = python
-passenv = HOME TERM
-deps = pre-commit
-commands = pre-commit run --all-files
-
-[flake8]
-exclude = .tox,*.egg,build,data
-select = E,W,F