diff --git a/.appveyor.yml b/.appveyor.yml
index 4b72bce610d..10039084d88 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -7,6 +7,8 @@ environment:
- PYTHON: "C:/Python27-x64"
- PYTHON: "C:/Python35-x64"
- PYTHON: "C:/Python36-x64"
+ - PYTHON: "C:/Python37-x64"
+
install:
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
@@ -15,8 +17,8 @@ install:
# about it being out of date.
- "python -m pip install --disable-pip-version-check --user --upgrade pip"
- # Create poetry virtualenv
- - "python -m pip install poetry --pre"
+ # Installing Poetry
+ - "python -m pip install poetry --pre -U"
# Install dependencies
- "poetry install -v"
diff --git a/.gitignore b/.gitignore
index cdfa7189fc7..e5dd3e40d51 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,9 +29,9 @@ setup.cfg
MANIFEST.in
/setup.py
/docs/site/*
-pyproject.lock
/tests/fixtures/simple_project/setup.py
/tests/fixtures/project_with_extras/setup.py
.mypy_cache
.venv
+/releases/*
diff --git a/.travis.yml b/.travis.yml
index f84063b3e56..1ec9607321e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,26 +7,25 @@ stages:
cache:
pip: true
directories:
- - $HOME/.cache/pypoetry
- - $HOME/.cache/pre-commit
+ - "$HOME/.cache/pypoetry"
+ - "$HOME/.cache/pre-commit"
install:
- - pip install poetry --pre
+ - pip install pip -U
+ - pip install poetry --pre -U
- poetry install -v
script: pytest -q tests/
jobs:
include:
- - python: "2.7"
- - python: "3.4"
- - python: "3.5"
- - python: "3.6"
-
- - stage: linting
- python: "3.6"
- install:
- - pip install pre-commit
- - pre-commit install-hooks
- script:
- - pre-commit run --all-files
+ - python: '2.7'
+ - python: '3.5'
+ - python: '3.6'
+ - stage: linting
+ python: '3.6'
+ install:
+ - pip install pre-commit
+ - pre-commit install-hooks
+ script:
+ - pre-commit run --all-files
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000000..b4f016e45ca
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,44 @@
+# This file is part of Poetry
+# https://github.com/sdispater/poetry
+
+# Licensed under the MIT license:
+# http://www.opensource.org/licenses/MIT-license
+# Copyright (c) 2018 Sébastien Eustace
+
+POETRY_RELEASE := $$(sed -n -E "s/__version__ = '(.+)'/\1/p" poetry/__version__.py)
+
+# lists all available targets
+list:
+ @sh -c "$(MAKE) -p no_targets__ | \
+ awk -F':' '/^[a-zA-Z0-9][^\$$#\/\\t=]*:([^=]|$$)/ {\
+ split(\$$1,A,/ /);for(i in A)print A[i]\
+ }' | grep -v '__\$$' | grep -v 'make\[1\]' | grep -v 'Makefile' | sort"
+# required for list
+no_targets__:
+
+# install all dependencies
+setup: setup-python
+
+# test your application (tests in the tests/ directory)
+test:
+ @py.test --cov=poetry --cov-config .coveragerc tests/ -sq
+
+release: build linux_release osx_release
+
+build:
+ @poetry build
+ @python sonnet make:release
+
+publish:
+ @poetry publish
+
+wheel:
+ @poetry build -v
+
+linux_release:
+ docker pull quay.io/pypa/manylinux1_x86_64
+ docker run --rm -v `pwd`:/io quay.io/pypa/manylinux1_x86_64 /io/make-linux-release.sh
+
+# run tests against all supported python versions
+tox:
+ @tox
diff --git a/docs/docs/index.md b/docs/docs/index.md
index ed17f4373ed..a5f28cce992 100644
--- a/docs/docs/index.md
+++ b/docs/docs/index.md
@@ -20,30 +20,70 @@ recommended way of installing `poetry`.
curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python
```
-Alternatively, you can download the `get-poetry.py` file and execute it separately.
+!!! note
-If you want to install prerelease versions, you can do so by passing `--preview` to `get-poetry.py`:
+ You only need to install Poetry once. It will automatically pick up the current
+ Python version and use it to [create virtualenvs](/docs/basic-usage/#poetry-and-virtualenvs) accordingly.
-```bash
-python get-poetry.py --preview
-```
+The installer installs the `poetry` tool to Poetry's `bin` directory.
+On Unix it is located at `$HOME/.poetry/bin` and on Windows at `%USERPROFILE%\.poetry\bin`.
-Similarly, if you want to install a specific version, you can use `--version`:
+This directory will be in your `$PATH` environment variable,
+which means you can run them from the shell without further configuration.
+Open a new shell and type the following:
```bash
-python get-poetry.py --version 0.7.0
+poetry --version
```
+If you see something like `Poetry 0.11.4` then you are ready to use Poetry.
+If you decide Poetry isn't your thing, you can completely remove it from your system
+by running the installer again with the `--uninstall` option.
+
!!!note
- Using `pip` to install `poetry` is also possible.
-
+ Alternatively, you can download the `get-poetry.py` file and execute it separately.
+
+ If you want to install prerelease versions, you can do so by passing `--preview` to `get-poetry.py`:
+
+ ```bash
+ python get-poetry.py --preview
+ ```
+
+ Similarly, if you want to install a specific version, you can use `--version`:
+
```bash
- pip install --user poetry
- ```
-
- Be aware, however, that it will also install poetry's dependencies
- which might cause conflicts.
+ python get-poetry.py --version 0.7.0
+ ```
+
+ Note that the installer does not support Poetry releases < 12.0.
+
+### Alternative installation methods (not recommended)
+
+#### Installing with `pip`
+
+Using `pip` to install Poetry is possible.
+
+```bash
+pip install --user poetry
+```
+
+!!!warning
+
+ Be aware that it will also install Poetry's dependencies
+ which might cause conflicts with other packages.
+
+#### Installing with `pipsi`
+
+Using [`pipsi`](https://github.com/mitsuhiko/pipsi) to install Poetry is also possible.
+
+```bash
+pipsi install poetry
+```
+
+Make sure your installed version of `pipsi` is at least version `0.10`,
+otherwise Poetry will not function properly. You can get it from its
+[Github repository](https://github.com/mitsuhiko/pipsi).
## Updating `poetry`
@@ -67,6 +107,11 @@ to `self:update`.
poetry self:update 0.8.0
```
+!!!note
+
+ The `self:update` command will only work if you used the recommended
+ installer to install Poetry.
+
## Enable tab completion for Bash, Fish, or Zsh
@@ -91,7 +136,7 @@ poetry completions zsh > ~/.zfunc/_poetry
!!! note
You may need to restart your shell in order for the changes to take effect.
-
+
For `zsh`, you must then add the following line in your `~/.zshrc` before `compinit`:
```bash
diff --git a/get-poetry.py b/get-poetry.py
index 0281cf0c967..cf66c1631ac 100644
--- a/get-poetry.py
+++ b/get-poetry.py
@@ -4,38 +4,60 @@
It does, in order:
- - Downloads the latest stable version of poetry.
- - Checks if the _vendor directory is empty.
- - If the _vendor directory is not empty, empties it.
- - Installs all dependencies in the _vendor directory
+ - Downloads the latest stable (or pre-release) version of poetry.
+ - Downloads all its dependencies in the poetry/_vendor directory.
+ - Copies it and all extra files in $POETRY_HOME.
+ - Updates the PATH in a system-specific way.
-This ensure that poetry will look for its dependencies inside
-the _vendor directory without tampering with the base system.
+There will be a `poetry` script that will be installed in $POETRY_HOME/bin
+which will act as the poetry command but is slightly different in the sense
+that it will use the current Python installation.
-Note, however, that installing poetry via pip will still work,
-since if poetry does not find the dependencies in the _vendor
-directory, it will look for them in the base system.
+What this means is that one Poetry installation can serve for multiple
+Python versions.
"""
import argparse
+import hashlib
import json
import os
import platform
import re
import shutil
+import stat
import subprocess
import sys
+import tarfile
import tempfile
from contextlib import contextmanager
-from email.parser import Parser
from functools import cmp_to_key
-from glob import glob
+from gzip import GzipFile
+from io import UnsupportedOperation
try:
+ from urllib.error import HTTPError
from urllib.request import urlopen
except ImportError:
+ from urllib2 import HTTPError
from urllib2 import urlopen
+try:
+ input = raw_input
+except NameError:
+ pass
+
+
+try:
+ try:
+ import winreg
+ except ImportError:
+ import _winreg as winreg
+except ImportError:
+ winreg = None
+
+
+WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
+
FOREGROUND_COLORS = {
"black": 30,
@@ -85,6 +107,7 @@ def style(fg, bg, options):
"info": style("green", None, None),
"comment": style("yellow", None, None),
"error": style("red", None, None),
+ "warning": style("yellow", None, None),
}
@@ -127,9 +150,120 @@ def temporary_directory(*args, **kwargs):
shutil.rmtree(name)
+def expanduser(path):
+ """
+ Expand ~ and ~user constructions.
+
+ Includes a workaround for http://bugs.python.org/issue14768
+ """
+ expanded = os.path.expanduser(path)
+ if path.startswith("~/") and expanded.startswith("//"):
+ expanded = expanded[1:]
+
+ return expanded
+
+
+HOME = expanduser("~")
+POETRY_HOME = os.path.join(HOME, ".poetry")
+POETRY_BIN = os.path.join(POETRY_HOME, "bin")
+POETRY_ENV = os.path.join(POETRY_HOME, "env")
+POETRY_LIB = os.path.join(POETRY_HOME, "lib")
+POETRY_LIB_BACKUP = os.path.join(POETRY_HOME, "lib-backup")
+
+
+BIN = """#!/usr/bin/env python
+import glob
+import sys
+import os
+
+lib = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "lib"))
+
+sys.path.insert(0, lib)
+
+if __name__ == "__main__":
+ from poetry.console import main
+
+ main()
+"""
+
+BAT = "@echo off\r\npython %USERPROFILE%/.poetry/bin/poetry %*\r\n"
+
+
+PRE_MESSAGE = """# Welcome to {poetry}!
+
+This will download and install the latest version of {poetry},
+a dependency and package manager for Python.
+
+It will add the `poetry` command to {poetry}'s bin directory, located at:
+
+{poetry_home_bin}
+
+{platform_msg}
+
+You can uninstall at any time with `poetry self:uninstall`,
+or by executing this script with the --uninstall option,
+and these changes will be reverted.
+"""
+
+PRE_UNINSTALL_MESSAGE = """# We are sorry to see you go!
+
+This will uninstall {poetry}.
+
+It will remove the `poetry` command from {poetry}'s bin directory, located at:
+
+{poetry_home_bin}
+
+This will also remove {poetry} from your system's PATH.
+"""
+
+
+PRE_MESSAGE_UNIX = """This path will then be added to your `PATH` environment variable by
+modifying the profile file{plural} located at:
+
+{rcfiles}"""
+
+
+PRE_MESSAGE_WINDOWS = """This path will then be added to your `PATH` environment variable by
+modifying the `HKEY_CURRENT_USER/Environment/PATH` registry key."""
+
+PRE_MESSAGE_NO_MODIFY_PATH = """This path needs to be in your `PATH` environment variable,
+but will not be added automatically."""
+
+POST_MESSAGE_UNIX = """{poetry} ({version}) is installed now. Great!
+
+To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH`
+environment variable. Next time you log in this will be done
+automatically.
+
+To configure your current shell run `source {poetry_home_env}`
+"""
+
+POST_MESSAGE_WINDOWS = """{poetry} ({version}) is installed now. Great!
+
+To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH`
+environment variable. Future applications will automatically have the
+correct environment, but you may need to restart your current shell.
+"""
+
+POST_MESSAGE_UNIX_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great!
+
+To get started you need {poetry}'s bin directory ({poetry_home_bin}) in your `PATH`
+environment variable.
+
+To configure your current shell run `source {poetry_home_env}`
+"""
+
+POST_MESSAGE_WINDOWS_NO_MODIFY_PATH = """{poetry} ({version}) is installed now. Great!
+
+To get started you need Poetry's bin directory ({poetry_home_bin}) in your `PATH`
+environment variable. This has not been done automatically.
+"""
+
+
class Installer:
CURRENT_PYTHON = sys.executable
+ CURRENT_PYTHON_VERSION = sys.version_info[:2]
METADATA_URL = "https://pypi.org/pypi/poetry/json"
VERSION_REGEX = re.compile(
"v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?"
@@ -141,15 +275,59 @@ class Installer:
"(?:\+[^\s]+)?"
)
- def __init__(self, version=None, preview=False):
+ BASE_URL = "https://github.com/sdispater/poetry/releases/download/"
+
+ def __init__(
+ self,
+ version=None,
+ preview=False,
+ force=False,
+ accept_all=False,
+ base_url=BASE_URL,
+ ):
self._version = version
self._preview = preview
+ self._force = force
+ self._modify_path = True
+ self._accept_all = accept_all
+ self._base_url = base_url
def allows_prereleases(self):
return self._preview
def run(self):
- print(colorize("info", "Retrieving metadata"))
+ version, current_version = self.get_version()
+
+ if version is None:
+ return 0
+
+ self.customize_install()
+ self.display_pre_message()
+ self.ensure_home()
+
+ try:
+ self.install(version, upgrade=current_version is not None)
+ except subprocess.CalledProcessError as e:
+ print(colorize("error", "An error has occured: {}".format(str(e))))
+ print(e.output.decode())
+
+ return e.returncode
+
+ self.display_post_message(version)
+
+ return 0
+
+ def uninstall(self):
+ self.display_pre_uninstall_message()
+
+ if not self.customize_uninstall():
+ return
+
+ self.remove_home()
+ self.remove_from_path()
+
+ def get_version(self):
+ print(colorize("info", "Retrieving Poetry metadata"))
r = urlopen(self.METADATA_URL)
metadata = json.loads(r.read().decode())
@@ -175,9 +353,9 @@ def _compare_versions(x, y):
)
if self._version and self._version not in releases:
- print(colorize("error", "Version {} does not exist".format(self._version)))
+ print(colorize("error", "Version {} does not exist.".format(self._version)))
- return 1
+ return None, None
version = self._version
if not version:
@@ -190,132 +368,409 @@ def _compare_versions(x, y):
break
- try:
- import poetry
+ current_version = None
+ if os.path.exists(POETRY_LIB):
+ with open(os.path.join(POETRY_LIB, "poetry", "__version__.py")) as f:
+ version_content = f.read()
- poetry_version = poetry.__version__
- except ImportError:
- poetry_version = None
+ current_version_re = re.match(
+ '(?ms).*__version__ = "(.+)".*', version_content
+ )
+ if not current_version_re:
+ print(
+ colorize(
+ "warning",
+ "Unable to get the current Poetry version. Assuming None",
+ )
+ )
+ else:
+ current_version = current_version_re.group(1)
- if poetry_version == version:
+ if current_version == version and not self._force:
print("Latest version already installed.")
- return 0
+ return None, current_version
+
+ return version, current_version
+
+ def customize_install(self):
+ if not self._accept_all:
+ print("Before we start, please answer the following questions.")
+ print("You may simple press the Enter key to keave unchanged.")
+
+ modify_path = input("Modify PATH variable? ([y]/n) ") or "y"
+ if modify_path.lower() in {"n", "no"}:
+ self._modify_path = False
+
+ print("")
+ def customize_uninstall(self):
+ if not self._accept_all:
+ print()
+
+ uninstall = (
+ input("Are you sure you want to uninstall Poetry? (y/[n]) ") or "n"
+ )
+ if uninstall.lower() not in {"y", "yes"}:
+ return False
+
+ print("")
+
+ return True
+
+ def customize_install(self):
+ if not self._accept_all:
+ print("Before we start, please answer the following questions.")
+ print("You may simple press the Enter key to keave unchanged.")
+
+ modify_path = input("Modify PATH variable? ([y]/n) ") or "y"
+ if modify_path.lower() in {"n", "no"}:
+ self._modify_path = False
+
+ print("")
+
+ def ensure_home(self):
+ """
+ Ensures that $POETRY_HOME exists or create it.
+ """
+ if not os.path.exists(POETRY_HOME):
+ os.mkdir(POETRY_HOME, 0o755)
+
+ def remove_home(self):
+ """
+ Removes $POETRY_HOME.
+ """
+ if not os.path.exists(POETRY_HOME):
+ return
+
+ shutil.rmtree(POETRY_HOME)
+
+ def install(self, version, upgrade=False):
+ """
+ Installs Poetry in $POETRY_HOME.
+ """
print("Installing version: " + colorize("info", version))
+ self.make_lib(version)
+ self.make_bin()
+ self.make_env()
+ self.update_path()
+
+ return 0
+
+ def make_lib(self, version):
+ """
+ Packs everything into a single lib/ directory.
+ """
+ if os.path.exists(POETRY_LIB_BACKUP):
+ shutil.rmtree(POETRY_LIB_BACKUP)
+
+ # Backup the current installation
+ if os.path.exists(POETRY_LIB):
+ shutil.copytree(POETRY_LIB, POETRY_LIB_BACKUP)
+ shutil.rmtree(POETRY_LIB)
+
try:
- return self.install(version)
- except subprocess.CalledProcessError as e:
- print(colorize("error", "An error has occured: {}".format(str(e))))
- print(e.output.decode())
+ self._make_lib(version)
+ except Exception:
+ if not os.path.exists(POETRY_LIB_BACKUP):
+ raise
- return e.returncode
+ shutil.copytree(POETRY_LIB_BACKUP, POETRY_LIB)
+ shutil.rmtree(POETRY_LIB_BACKUP)
- def install(self, version):
- # Most of the work will be delegated to pip
- with temporary_directory(prefix="poetry-installer-") as dir:
- dist = os.path.join(dir, "dist")
- print(" - Getting dependencies")
- try:
- self.call(
- self.CURRENT_PYTHON,
- "-m",
- "pip",
- "install",
- "poetry=={}".format(version),
- "--target",
- dist,
- )
- except subprocess.CalledProcessError as e:
- if "must supply either home or prefix/exec-prefix" in e.output.decode():
- # Homebrew Python and possible other installations
- # We workaround this issue by temporarily changing
- # the --user directory
- original_user = os.getenv("PYTHONUSERBASE")
- os.environ["PYTHONUSERBASE"] = dir
- self.call(
- self.CURRENT_PYTHON,
- "-m",
- "pip",
- "install",
- "poetry=={}".format(version),
- "--user",
- "--ignore-installed",
- )
+ raise
+ finally:
+ if os.path.exists(POETRY_LIB_BACKUP):
+ shutil.rmtree(POETRY_LIB_BACKUP)
- if original_user is not None:
- os.environ["PYTHONUSERBASE"] = original_user
- else:
- del os.environ["PYTHONUSERBASE"]
-
- # Finding site-package directory
- lib = os.path.join(dir, "lib")
- lib_python = list(glob(os.path.join(lib, "python*")))[0]
- site_packages = os.path.join(lib_python, "site-packages")
- shutil.copytree(site_packages, dist)
- else:
- raise
-
- print(" - Vendorizing dependencies")
-
- poetry_dir = os.path.join(dist, "poetry")
- vendor_dir = os.path.join(poetry_dir, "_vendor")
-
- # Everything, except poetry itself, should
- # be put in the _vendor directory
- for file in glob(os.path.join(dist, "*")):
- if (
- os.path.basename(file).startswith("poetry")
- or os.path.basename(file) == "__pycache__"
- ):
- continue
+ def _make_lib(self, version):
+ # We get the payload from the remote host
+ url = self._base_url + "{}/".format(version)
+ name = "poetry-{}-{}.tar.gz".format(version, sys.platform)
+ checksum = "poetry-{}-{}.sha256sum".format(version, sys.platform)
- dest = os.path.join(vendor_dir, os.path.basename(file))
- if os.path.isdir(file):
- shutil.copytree(file, dest)
- shutil.rmtree(file)
- else:
- shutil.copy(file, dest)
- os.unlink(file)
+ try:
+ r = urlopen(url + "{}".format(checksum))
+ except HTTPError as e:
+ if e.code == 404:
+ raise RuntimeError("Could not find {} file".format(checksum))
- wheel_data = os.path.join(
- dist, "poetry-{}.dist-info".format(version), "WHEEL"
- )
- with open(wheel_data) as f:
- wheel_data = Parser().parsestr(f.read())
+ raise
- tag = wheel_data["Tag"]
+ checksum = r.read().decode()
- # Repack everything and install
- print(" - Installing {}".format(colorize("info", "poetry")))
+ try:
+ r = urlopen(_url + "{}".format(name))
+ except HTTPError as e:
+ if e.code == 404:
+ raise RuntimeError("Could not find {} file".format(name))
- shutil.make_archive(
- os.path.join(dir, "poetry-{}-{}".format(version, tag)),
- format="zip",
- root_dir=str(dist),
- )
+ raise
+
+ meta = r.info()
+ size = int(meta["Content-Length"])
+ current = 0
+ block_size = 8192
- os.rename(
- os.path.join(dir, "poetry-{}-{}.zip".format(version, tag)),
- os.path.join(dir, "poetry-{}-{}.whl".format(version, tag)),
+ print(
+ " - Downloading {} ({:.2f}MB)".format(
+ colorize("comment", name), size / 1024 / 1024
)
+ )
+
+ sha = hashlib.sha256()
+ with temporary_directory(prefix="poetry-installer-") as dir_:
+ tar = os.path.join(dir_, name)
+ with open(tar, "wb") as f:
+ while True:
+ buffer = r.read(block_size)
+ if not buffer:
+ break
+
+ current += len(buffer)
+ f.write(buffer)
+ sha.update(buffer)
+
+ # Checking hashes
+ if checksum != sha.hexdigest():
+ raise RuntimeError(
+ "Hashes for {} do not match: {} != {}".format(
+ name, checksum, sha.hexdigest()
+ )
+ )
+
+ gz = GzipFile(tar, mode="rb")
+ try:
+ with tarfile.TarFile(tar, fileobj=gz, format=tarfile.PAX_FORMAT) as f:
+ f.extractall(POETRY_LIB)
+ finally:
+ gz.close()
+
+ def make_bin(self):
+ if not os.path.exists(POETRY_BIN):
+ os.mkdir(POETRY_BIN, 0o755)
+
+ ext = ""
+ if WINDOWS:
+ with open(os.path.join(POETRY_BIN, "poetry.bat"), "w") as f:
+ f.write(BAT)
+
+ with open(os.path.join(POETRY_BIN, "poetry"), "w") as f:
+ f.write(BIN)
+
+ if not WINDOWS:
+ # Making the file executable
+ st = os.stat(os.path.join(POETRY_BIN, "poetry"))
+ os.chmod(os.path.join(POETRY_BIN, "poetry"), st.st_mode | stat.S_IEXEC)
+
+ def make_env(self):
+ if WINDOWS:
+ return
+
+ with open(os.path.join(POETRY_HOME, "env"), "w") as f:
+ f.write(self.get_export_string())
+
+ def update_path(self):
+ """
+ Tries to update the $PATH automatically.
+ """
+ if WINDOWS:
+ return self.add_to_windows_path()
+
+ # Updating any profile we can on UNIX systems
+ export_string = self.get_export_string()
- self.call(
- self.CURRENT_PYTHON,
- "-m",
- "pip",
- "install",
- "--upgrade",
- "--no-deps",
- os.path.join(dir, "poetry-{}-{}.whl".format(version, tag)),
+ addition = "\n{}".format(export_string)
+
+ updated = []
+ profiles = self.get_unix_profiles()
+ for profile in profiles:
+ if not os.path.exists(profile):
+ continue
+
+ with open(profile, "r") as f:
+ content = f.read()
+
+ if addition not in content:
+ with open(profile, "a") as f:
+ f.write(addition)
+
+ updated.append(os.path.relpath(profile, HOME))
+
+ def add_to_windows_path(self):
+ try:
+ old_path = self.get_windows_path_var()
+ except WindowsError:
+ old_path = None
+
+ if old_path is None:
+ print(
+ colorize(
+ "warning",
+ "Unable to get the PATH value. It will not be updated automatically",
+ )
)
+ self._modify_path = False
+
+ return
+
+ new_path = POETRY_BIN
+ if POETRY_BIN in old_path:
+ old_path = old_path.replace(POETRY_BIN + ";", "")
+
+ if old_path:
+ new_path += ";"
+ new_path += old_path
+
+ self.set_windows_path_var(new_path)
+
+ def get_windows_path_var(self):
+ with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root:
+ with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key:
+ path, _ = winreg.QueryValueEx(key, "PATH")
+
+ return path
+
+ def set_windows_path_var(self, value):
+ import ctypes
+ from ctypes.wintypes import HWND
+ from ctypes.wintypes import LPARAM
+ from ctypes.wintypes import LPVOID
+ from ctypes.wintypes import UINT
+ from ctypes.wintypes import WPARAM
+
+ with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as root:
+ with winreg.OpenKey(root, "Environment", 0, winreg.KEY_ALL_ACCESS) as key:
+ winreg.SetValueEx(key, "PATH", 0, winreg.REG_EXPAND_SZ, value)
+
+ # Tell other processes to update their environment
+ HWND_BROADCAST = 0xFFFF
+ WM_SETTINGCHANGE = 0x1A
+
+ SMTO_ABORTIFHUNG = 0x0002
+
+ result = ctypes.c_long()
+ SendMessageTimeoutW = ctypes.windll.user32.SendMessageTimeoutW
+ SendMessageTimeoutW(
+ HWND_BROADCAST,
+ WM_SETTINGCHANGE,
+ 0,
+ u"Environment",
+ SMTO_ABORTIFHUNG,
+ 5000,
+ ctypes.byref(result),
+ )
+
+ def remove_from_path(self):
+ if WINDOWS:
+ return self.remove_from_windows_path()
+
+ return self.remove_from_unix_path()
+
+ def remove_from_windows_path(self):
+ path = self.get_windows_path_var()
+
+ poetry_path = POETRY_BIN
+ if poetry_path in path:
+ path = path.replace(POETRY_BIN + ";", "")
+
+ if poetry_path in path:
+ path = path.replace(POETRY_BIN, "")
+
+ self.set_windows_path_var(path)
+
+ def remove_from_unix_path(self):
+ pass
+
+ def get_export_string(self):
+ path = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")
+ export_string = 'export PATH="{}:$PATH"'.format(path)
+
+ return export_string
+
+ def get_unix_profiles(self):
+ profiles = [os.path.join(HOME, ".profile")]
+
+ shell = os.getenv("SHELL")
+ if "zsh" in shell:
+ zdotdir = os.getenv("ZDOTDIR", HOME)
+ profiles.append(os.path.join(zdotdir, ".zprofile"))
+
+ bash_profile = os.path.join(HOME, ".bash_profile")
+ if os.path.exists(bash_profile):
+ profiles.append(bash_profile)
+
+ return profiles
+
+ def display_pre_message(self):
+ if WINDOWS:
+ home = POETRY_BIN.replace(os.getenv("USERPROFILE", ""), "%USERPROFILE%")
+ else:
+ home = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")
+ kwargs = {
+ "poetry": colorize("info", "Poetry"),
+ "poetry_home_bin": colorize("comment", home),
+ }
+
+ if not self._modify_path:
+ kwargs["platform_msg"] = PRE_MESSAGE_NO_MODIFY_PATH
+ else:
+ if WINDOWS:
+ kwargs["platform_msg"] = PRE_MESSAGE_WINDOWS
+ else:
+ profiles = [
+ colorize("comment", p.replace(os.getenv("HOME", ""), "$HOME"))
+ for p in self.get_unix_profiles()
+ ]
+ kwargs["platform_msg"] = PRE_MESSAGE_UNIX.format(
+ rcfiles="\n".join(profiles), plural="s" if len(profiles) > 1 else ""
+ )
+
+ print(PRE_MESSAGE.format(**kwargs))
+
+ def display_pre_uninstall_message(self):
+ kwargs = {
+ "poetry": colorize("info", "Poetry"),
+ "poetry_home_bin": colorize(
+ "comment",
+ POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME").replace(
+ os.getenv("USERPROFILE", ""), "%USERPROFILE%"
+ ),
+ ),
+ }
+
+ print(PRE_UNINSTALL_MESSAGE.format(**kwargs))
+
+ def display_post_message(self, version):
print("")
- print(
- "{} ({}) successfully installed!".format(
- colorize("info", "poetry"), colorize("comment", version)
+
+ kwargs = {
+ "poetry": colorize("info", "Poetry"),
+ "version": colorize("comment", version),
+ }
+
+ if WINDOWS:
+ message = POST_MESSAGE_WINDOWS
+ if not self._modify_path:
+ message = POST_MESSAGE_WINDOWS_NO_MODIFY_PATH
+
+ poetry_home_bin = POETRY_BIN.replace(
+ os.getenv("USERPROFILE", ""), "%USERPROFILE%"
)
- )
+ else:
+ message = POST_MESSAGE_UNIX
+ if not self._modify_path:
+ message = POST_MESSAGE_UNIX_NO_MODIFY_PATH
+
+ poetry_home_bin = POETRY_BIN.replace(os.getenv("HOME", ""), "$HOME")
+ kwargs["poetry_home_env"] = colorize(
+ "comment", POETRY_ENV.replace(os.getenv("HOME", ""), "$HOME")
+ )
+
+ kwargs["poetry_home_bin"] = colorize("comment", poetry_home_bin)
+
+ print(message.format(**kwargs))
def call(self, *args):
return subprocess.check_output(args, stderr=subprocess.STDOUT)
@@ -329,14 +784,28 @@ def main():
"-p", "--preview", dest="preview", action="store_true", default=False
)
parser.add_argument("--version", dest="version")
+ parser.add_argument(
+ "-f", "--force", dest="force", action="store_true", default=False
+ )
+ parser.add_argument(
+ "-y", "--yes", dest="accept_all", action="store_true", default=False
+ )
+ parser.add_argument(
+ "--uninstall", dest="uninstall", action="store_true", default=False
+ )
args = parser.parse_args()
installer = Installer(
version=args.version or os.getenv("POETRY_VERSION"),
preview=args.preview or os.getenv("POETRY_PREVIEW"),
+ force=args.force,
+ accept_all=args.accept_all,
)
+ if args.uninstall:
+ return installer.uninstall()
+
return installer.run()
diff --git a/make-linux-release.sh b/make-linux-release.sh
new file mode 100755
index 00000000000..5bd8a98a457
--- /dev/null
+++ b/make-linux-release.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+PYTHON_VERSIONS="cp27-cp27m cp34-cp34m cp35-cp35m cp36-cp36m cp37-cp37m"
+
+cd /io
+/opt/python/cp37-cp37m/bin/pip install poetry --pre -U
+/opt/python/cp37-cp37m/bin/poetry config settings.virtualenvs.create false
+/opt/python/cp37-cp37m/bin/poetry install --no-dev
+/opt/python/cp37-cp37m/bin/python sonnet make:release \
+ -P "2.7:/opt/python/cp27-cp27m/bin/python" \
+ -P "3.4:/opt/python/cp34-cp34m/bin/python" \
+ -P "3.5:/opt/python/cp35-cp35m/bin/python" \
+ -P "3.6:/opt/python/cp36-cp36m/bin/python" \
+ -P "3.7:/opt/python/cp37-cp37m/bin/python"
+cd -
diff --git a/poetry/__init__.py b/poetry/__init__.py
index 5a0e6815d07..ae18b1eb4d6 100644
--- a/poetry/__init__.py
+++ b/poetry/__init__.py
@@ -3,8 +3,11 @@
_ROOT = os.path.dirname(os.path.realpath(__file__))
_VENDOR = os.path.join(_ROOT, "_vendor")
+_CURRENT_VENDOR = os.path.join(
+ _VENDOR, "py{}".format(".".join(str(v) for v in sys.version_info[:2]))
+)
# Add vendored dependencies to path.
-sys.path.insert(0, _VENDOR)
+sys.path.insert(0, _CURRENT_VENDOR)
from .__version__ import __version__ # noqa
diff --git a/poetry/console/commands/self/update.py b/poetry/console/commands/self/update.py
index 7f492d619d9..55f599bfbbb 100644
--- a/poetry/console/commands/self/update.py
+++ b/poetry/console/commands/self/update.py
@@ -1,11 +1,19 @@
+import hashlib
import os
import shutil
import subprocess
import sys
-
-from email.parser import Parser
+import tarfile
from functools import cmp_to_key
+from gzip import GzipFile
+
+try:
+ from urllib.error import HTTPError
+ from urllib.request import urlopen
+except ImportError:
+ from urllib2 import HTTPError
+ from urllib2 import urlopen
from ..command import Command
@@ -19,12 +27,41 @@ class SelfUpdateCommand(Command):
{ --preview : Install prereleases. }
"""
+ BASE_URL = "https://poetry.eustace.io"
+
+ @property
+ def home(self):
+ from poetry.utils._compat import Path
+ from poetry.utils.appdirs import expanduser
+
+ home = Path(expanduser("~"))
+
+ return home / ".poetry"
+
+ @property
+ def lib(self):
+ return self.home / "lib"
+
+ @property
+ def lib_backup(self):
+ return self.home / "lib-backup"
+
def handle(self):
from poetry.__version__ import __version__
from poetry.repositories.pypi_repository import PyPiRepository
from poetry.semver import Version
+ from poetry.utils._compat import Path
from poetry.utils._compat import decode
+ current = Path(__file__)
+ try:
+ current.relative_to(self.home)
+ except ValueError:
+ raise RuntimeError(
+ "Poetry was not installed with the recommended installer. "
+ "Cannot update automatically."
+ )
+
version = self.argument("version")
if not version:
version = ">=" + __version__
@@ -83,98 +120,103 @@ def handle(self):
return e.returncode
def update(self, release):
- from poetry.utils._compat import Path
- from poetry.utils.helpers import temporary_directory
-
version = release.version
self.line("Updating to {}".format(version))
- prefix = sys.prefix
- base_prefix = getattr(sys, "base_prefix", None)
- real_prefix = getattr(sys, "real_prefix", None)
-
- prefix_poetry = self._bin_path(Path(prefix), "poetry")
- if prefix_poetry.exists():
- pip = self._bin_path(prefix_poetry.parent.parent, "pip").resolve()
- elif (
- base_prefix
- and base_prefix != prefix
- and self._bin_path(Path(base_prefix), "poetry").exists()
- ):
- pip = self._bin_path(Path(base_prefix), "pip")
- elif real_prefix:
- pip = self._bin_path(Path(real_prefix), "pip")
- else:
- pip = self._bin_path(Path(prefix), "pip")
-
- if not pip.exists():
- raise RuntimeError("Unable to determine poetry's path")
-
- with temporary_directory(prefix="poetry-update-") as temp_dir:
- temp_dir = Path(temp_dir)
- dist = temp_dir / "dist"
- self.line(" - Getting dependencies")
- self.process(
- str(pip),
- "install",
- "-U",
- "poetry=={}".format(release.version),
- "--target",
- str(dist),
- )
-
- self.line(" - Vendorizing dependencies")
-
- poetry_dir = dist / "poetry"
- vendor_dir = poetry_dir / "_vendor"
-
- # Everything, except poetry itself, should
- # be put in the _vendor directory
- for file in dist.glob("*"):
- if file.name.startswith("poetry"):
- continue
+ if self.lib_backup.exists():
+ shutil.rmtree(str(self.lib_backup))
- dest = vendor_dir / file.name
- if file.is_dir():
- shutil.copytree(str(file), str(dest))
- shutil.rmtree(str(file))
- else:
- shutil.copy(str(file), str(dest))
- os.unlink(str(file))
+ # Backup the current installation
+ if self.lib.exists():
+ shutil.copytree(str(self.lib), str(self.lib_backup))
+ shutil.rmtree(str(self.lib))
- wheel_data = dist / "poetry-{}.dist-info".format(version) / "WHEEL"
- with wheel_data.open() as f:
- wheel_data = Parser().parsestr(f.read())
+ try:
+ self._update(version)
+ except Exception:
+ if not self.lib_backup.exists():
+ raise
+
+ shutil.copytree(str(self.lib_backup), str(self.lib))
+ shutil.rmtree(str(self.lib_backup))
+
+ raise
+ finally:
+ if self.lib_backup.exists():
+ shutil.rmtree(str(self.lib_backup))
+
+ self.line("")
+ self.line(
+ "Poetry ({}) is installed now. Great!".format(
+ version
+ )
+ )
- tag = wheel_data["Tag"]
+ def _update(self, version):
+ from poetry.utils.helpers import temporary_directory
- # Repack everything and install
- self.line(" - Updating poetry")
+ checksum = "poetry-{}-{}.sha256sum".format(version, sys.platform)
- shutil.make_archive(
- str(temp_dir / "poetry-{}-{}".format(version, tag)),
- format="zip",
- root_dir=str(dist),
- )
+ try:
+ r = urlopen(self.BASE_URL + "/releases/{}".format(checksum))
+ except HTTPError as e:
+ if e.code == 404:
+ raise RuntimeError("Could not find {} file".format(checksum))
- os.rename(
- str(temp_dir / "poetry-{}-{}.zip".format(version, tag)),
- str(temp_dir / "poetry-{}-{}.whl".format(version, tag)),
- )
+ raise
- self.process(
- str(pip),
- "install",
- "--upgrade",
- "--no-deps",
- str(temp_dir / "poetry-{}-{}.whl".format(version, tag)),
- )
+ checksum = r.read().decode()
- self.line("")
- self.line(
- "poetry> ({}>) "
- "successfully installed!".format(version)
- )
+ # We get the payload from the remote host
+ name = "poetry-{}-{}.tar.gz".format(version, sys.platform)
+ try:
+ r = urlopen(self.BASE_URL + "/releases/{}".format(name))
+ except HTTPError as e:
+ if e.code == 404:
+ raise RuntimeError("Could not find {} file".format(name))
+
+ raise
+
+ meta = r.info()
+ size = int(meta["Content-Length"])
+ current = 0
+ block_size = 8192
+
+ bar = self.progress_bar(max=size)
+ bar.set_format(" - Downloading {}> %percent%%>".format(name))
+ bar.start()
+
+ sha = hashlib.sha256()
+ with temporary_directory(prefix="poetry-updater-") as dir_:
+ tar = os.path.join(dir_, name)
+ with open(tar, "wb") as f:
+ while True:
+ buffer = r.read(block_size)
+ if not buffer:
+ break
+
+ current += len(buffer)
+ f.write(buffer)
+ sha.update(buffer)
+
+ bar.set_progress(current)
+
+ bar.finish()
+
+ # Checking hashes
+ if checksum != sha.hexdigest():
+ raise RuntimeError(
+ "Hashes for {} do not match: {} != {}".format(
+ name, checksum, sha.hexdigest()
+ )
+ )
+
+ gz = GzipFile(tar, mode="rb")
+ try:
+ with tarfile.TarFile(tar, fileobj=gz, format=tarfile.PAX_FORMAT) as f:
+ f.extractall(str(self.lib))
+ finally:
+ gz.close()
def process(self, *args):
return subprocess.check_output(list(args), stderr=subprocess.STDOUT)
diff --git a/pyproject.lock b/pyproject.lock
new file mode 100644
index 00000000000..a9b9caee58f
--- /dev/null
+++ b/pyproject.lock
@@ -0,0 +1,799 @@
+[[package]]
+category = "dev"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+name = "appdirs"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "1.4.3"
+
+[package.requirements]
+python = ">=3.6,<4.0"
+
+[[package]]
+category = "dev"
+description = "A few extensions to pyyaml."
+name = "aspy.yaml"
+optional = false
+platform = "all"
+python-versions = "*"
+version = "1.1.1"
+
+[package.dependencies]
+pyyaml = "*"
+
+[[package]]
+category = "dev"
+description = "Atomic file writes."
+name = "atomicwrites"
+optional = false
+platform = "*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.2.1"
+
+[[package]]
+category = "dev"
+description = "Classes Without Boilerplate"
+name = "attrs"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "18.2.0"
+
+[[package]]
+category = "dev"
+description = "The uncompromising code formatter."
+name = "black"
+optional = false
+platform = "*"
+python-versions = ">=3.6"
+version = "18.6b4"
+
+[package.dependencies]
+appdirs = "*"
+attrs = ">=17.4.0"
+click = ">=6.5"
+toml = ">=0.9.4"
+
+[package.requirements]
+python = ">=3.6,<4.0"
+
+[[package]]
+category = "main"
+description = "httplib2 caching for requests"
+name = "cachecontrol"
+optional = false
+platform = "*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.12.5"
+
+[package.dependencies]
+lockfile = ">=0.9"
+msgpack = "*"
+requests = "*"
+
+[[package]]
+category = "dev"
+description = "A decorator for caching properties in classes."
+name = "cached-property"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "1.4.3"
+
+[[package]]
+category = "main"
+description = "Cachy provides a simple yet effective caching library."
+name = "cachy"
+optional = false
+platform = "*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.2.0"
+
+[[package]]
+category = "main"
+description = "Python package for providing Mozilla's CA Bundle."
+name = "certifi"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "2018.8.24"
+
+[[package]]
+category = "dev"
+description = "Validate configuration and produce human readable error messages."
+name = "cfgv"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "1.1.0"
+
+[package.dependencies]
+six = "*"
+
+[[package]]
+category = "main"
+description = "Universal encoding detector for Python 2 and 3"
+name = "chardet"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "3.0.4"
+
+[[package]]
+category = "main"
+description = "Cleo allows you to create beautiful and testable command-line interfaces."
+name = "cleo"
+optional = false
+platform = "*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.6.8"
+
+[package.dependencies]
+pastel = ">=0.1.0,<0.2.0"
+pylev = ">=1.3,<2.0"
+
+[[package]]
+category = "dev"
+description = "A simple wrapper around optparse for powerful command line utilities."
+name = "click"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "6.7"
+
+[[package]]
+category = "dev"
+description = "Cross-platform colored terminal text."
+name = "colorama"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "0.3.9"
+
+[package.requirements]
+platform = "win32"
+
+[[package]]
+category = "dev"
+description = "Code coverage measurement for Python"
+name = "coverage"
+optional = false
+platform = "*"
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
+version = "4.5.1"
+
+[[package]]
+category = "main"
+description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4"
+name = "enum34"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "1.1.6"
+
+[package.requirements]
+python = ">=2.7,<2.8"
+
+[[package]]
+category = "dev"
+description = "Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+"
+name = "funcsigs"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "1.0.2"
+
+[package.requirements]
+python = "<3.0"
+
+[[package]]
+category = "main"
+description = "Backport of the functools module from Python 3.2.3 for use on 2.7 and PyPy."
+name = "functools32"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "3.2.3-2"
+
+[package.requirements]
+python = ">=2.7,<2.8"
+
+[[package]]
+category = "main"
+description = "HTML parser based on the WHATWG HTML specification"
+name = "html5lib"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "1.0.1"
+
+[package.dependencies]
+six = ">=1.9"
+webencodings = "*"
+
+[[package]]
+category = "dev"
+description = "File identification library for Python"
+name = "identify"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "1.1.4"
+
+[[package]]
+category = "main"
+description = "Internationalized Domain Names in Applications (IDNA)"
+name = "idna"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "2.7"
+
+[[package]]
+category = "dev"
+description = "A small but fast and easy to use stand-alone template engine written in pure python."
+name = "jinja2"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "2.10"
+
+[package.dependencies]
+MarkupSafe = ">=0.23"
+
+[[package]]
+category = "main"
+description = "An implementation of JSON Schema validation for Python"
+name = "jsonschema"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "2.6.0"
+
+[package.dependencies]
+[package.dependencies.functools32]
+python = ">=2.7,<2.8"
+version = "*"
+
+[[package]]
+category = "dev"
+description = "Python LiveReload is an awesome tool for web developers"
+name = "livereload"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "2.5.2"
+
+[package.dependencies]
+six = "*"
+tornado = "*"
+
+[[package]]
+category = "main"
+description = "Platform-independent file locking module"
+name = "lockfile"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "0.12.2"
+
+[[package]]
+category = "dev"
+description = "Python implementation of Markdown."
+name = "markdown"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "2.6.11"
+
+[[package]]
+category = "dev"
+description = "Implements a XML/HTML/XHTML Markup safe string for Python"
+name = "markupsafe"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "1.0"
+
+[[package]]
+category = "dev"
+description = "Project documentation with Markdown."
+name = "mkdocs"
+optional = false
+platform = "*"
+python-versions = ">=2.7.9,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
+version = "1.0.3"
+
+[package.dependencies]
+Jinja2 = ">=2.7.1"
+Markdown = ">=2.3.1"
+PyYAML = ">=3.10"
+click = ">=3.3"
+livereload = ">=2.5.1"
+tornado = ">=5.0"
+
+[[package]]
+category = "dev"
+description = "Rolling backport of unittest.mock for all Pythons"
+name = "mock"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "2.0.0"
+
+[package.dependencies]
+pbr = ">=0.11"
+six = ">=1.9"
+
+[package.dependencies.funcsigs]
+python = "<3.3"
+version = ">=1"
+
+[package.requirements]
+python = "<3.0"
+
+[[package]]
+category = "dev"
+description = "More routines for operating on iterables, beyond itertools"
+name = "more-itertools"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "4.3.0"
+
+[package.dependencies]
+six = ">=1.0.0,<2.0.0"
+
+[[package]]
+category = "main"
+description = "MessagePack (de)serializer."
+name = "msgpack"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "0.5.6"
+
+[[package]]
+category = "dev"
+description = "Node.js virtual environment builder"
+name = "nodeenv"
+optional = false
+platform = "any"
+python-versions = "*"
+version = "1.3.2"
+
+[[package]]
+category = "main"
+description = "Bring colors to your terminal."
+name = "pastel"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "0.1.0"
+
+[[package]]
+category = "main"
+description = "Object-oriented filesystem paths"
+name = "pathlib2"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "2.3.2"
+
+[package.dependencies]
+six = "*"
+
+[package.dependencies.scandir]
+python = "<3.5"
+version = "*"
+
+[package.requirements]
+python = "<3.6"
+
+[[package]]
+category = "dev"
+description = "Python Build Reasonableness"
+name = "pbr"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "4.2.0"
+
+[package.requirements]
+python = "<3.0"
+
+[[package]]
+category = "main"
+description = "Query metadatdata from sdists / bdists / installed packages."
+name = "pkginfo"
+optional = false
+platform = "Unix"
+python-versions = "*"
+version = "1.4.2"
+
+[[package]]
+category = "dev"
+description = "plugin and hook calling mechanisms for python"
+name = "pluggy"
+optional = false
+platform = "unix"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.7.1"
+
+[[package]]
+category = "dev"
+description = "A framework for managing and maintaining multi-language pre-commit hooks."
+name = "pre-commit"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "1.11.0"
+
+[package.dependencies]
+"aspy.yaml" = "*"
+cached-property = "*"
+cfgv = ">=1.0.0"
+identify = ">=1.0.0"
+nodeenv = ">=0.11.1"
+pyyaml = "*"
+six = "*"
+toml = "*"
+virtualenv = "*"
+
+[[package]]
+category = "dev"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+name = "py"
+optional = false
+platform = "unix"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.6.0"
+
+[[package]]
+category = "dev"
+description = "Pygments is a syntax highlighting package written in Python."
+name = "pygments"
+optional = false
+platform = "any"
+python-versions = "*"
+version = "2.2.0"
+
+[[package]]
+category = "dev"
+description = "Pygments Github custom lexers."
+name = "pygments-github-lexers"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "0.0.5"
+
+[package.dependencies]
+pygments = ">=2.0.2"
+
+[[package]]
+category = "main"
+description = "A pure Python Levenshtein implementation that's not freaking GPL'd."
+name = "pylev"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "1.3.0"
+
+[[package]]
+category = "dev"
+description = "Extension pack for Python Markdown."
+name = "pymdown-extensions"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "4.12"
+
+[package.dependencies]
+Markdown = ">=2.6.10"
+
+[[package]]
+category = "main"
+description = "Python parsing module"
+name = "pyparsing"
+optional = false
+platform = "UNKNOWN"
+python-versions = "*"
+version = "2.2.0"
+
+[[package]]
+category = "main"
+description = "Persistent/Functional/Immutable data structures"
+name = "pyrsistent"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "0.14.4"
+
+[package.dependencies]
+six = "*"
+
+[[package]]
+category = "dev"
+description = "pytest: simple powerful testing with Python"
+name = "pytest"
+optional = false
+platform = "unix"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "3.7.4"
+
+[package.dependencies]
+atomicwrites = ">=1.0"
+attrs = ">=17.4.0"
+more-itertools = ">=4.0.0"
+pluggy = ">=0.7"
+py = ">=1.5.0"
+setuptools = "*"
+six = ">=1.10.0"
+
+[package.dependencies.colorama]
+platform = "win32"
+version = "*"
+
+[package.dependencies.funcsigs]
+python = "<3.0"
+version = "*"
+
+[package.dependencies.pathlib2]
+python = "<3.6"
+version = ">=2.2.0"
+
+[[package]]
+category = "dev"
+description = "Pytest plugin for measuring coverage."
+name = "pytest-cov"
+optional = false
+platform = "*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.6.0"
+
+[package.dependencies]
+coverage = ">=4.4"
+pytest = ">=2.9"
+
+[[package]]
+category = "dev"
+description = "Thin-wrapper around the mock package for easier use with py.test"
+name = "pytest-mock"
+optional = false
+platform = "any"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.10.0"
+
+[package.dependencies]
+pytest = ">=2.7"
+
+[package.dependencies.mock]
+python = "<3.0"
+version = "*"
+
+[[package]]
+category = "dev"
+description = "YAML parser and emitter for Python"
+name = "pyyaml"
+optional = false
+platform = "Any"
+python-versions = "*"
+version = "3.13"
+
+[[package]]
+category = "main"
+description = "Python HTTP for Humans."
+name = "requests"
+optional = false
+platform = "*"
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.19.1"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+chardet = ">=3.0.2,<3.1.0"
+idna = ">=2.5,<2.8"
+urllib3 = ">=1.21.1,<1.24"
+
+[[package]]
+category = "main"
+description = "A utility belt for advanced users of python-requests"
+name = "requests-toolbelt"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "0.8.0"
+
+[package.dependencies]
+requests = ">=2.0.1,<3.0.0"
+
+[[package]]
+category = "main"
+description = "scandir, a better directory iterator and faster os.walk()"
+name = "scandir"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "1.9.0"
+
+[package.requirements]
+python = "<3.5"
+
+[[package]]
+category = "main"
+description = "Tool to Detect Surrounding Shell"
+name = "shellingham"
+optional = false
+platform = "*"
+python-versions = ">=2.6,!=3.0,!=3.1,!=3.2,!=3.3"
+version = "1.2.4"
+
+[[package]]
+category = "main"
+description = "Python 2 and 3 compatibility utilities"
+name = "six"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "1.11.0"
+
+[[package]]
+category = "dev"
+description = "Python Library for Tom's Obvious, Minimal Language"
+name = "toml"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "0.9.4"
+
+[[package]]
+category = "main"
+description = "Style preserving TOML library"
+name = "tomlkit"
+optional = false
+platform = "*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.4.4"
+
+[package.dependencies]
+[package.dependencies.enum34]
+python = ">=2.7,<2.8"
+version = ">=1.1,<2.0"
+
+[package.dependencies.typing]
+python = ">=2.7,<2.8 || >=3.4,<3.5"
+version = ">=3.6,<4.0"
+
+[[package]]
+category = "dev"
+description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
+name = "tornado"
+optional = false
+platform = "*"
+python-versions = ">= 2.7, !=3.0.*, !=3.1.*, !=3.2.*, != 3.3.*"
+version = "5.1"
+
+[[package]]
+category = "dev"
+description = "virtualenv-based automation of test activities"
+name = "tox"
+optional = false
+platform = "unix"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "3.2.1"
+
+[package.dependencies]
+pluggy = ">=0.3.0,<1"
+py = ">=1.4.17,<2"
+setuptools = ">=30.0.0"
+six = ">=1.0.0,<2"
+virtualenv = ">=1.11.2"
+
+[[package]]
+category = "main"
+description = "Type Hints for Python"
+name = "typing"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "3.6.6"
+
+[package.requirements]
+python = ">=2.7,<2.8 || >=3.4,<3.5"
+
+[[package]]
+category = "main"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+name = "urllib3"
+optional = false
+platform = "*"
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4"
+version = "1.23"
+
+[[package]]
+category = "main"
+description = "Virtual Python Environment builder"
+name = "virtualenv"
+optional = false
+platform = "*"
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*"
+version = "16.0.0"
+
+[[package]]
+category = "main"
+description = "Character encoding aliases for legacy web content"
+name = "webencodings"
+optional = false
+platform = "*"
+python-versions = "*"
+version = "0.5.1"
+
+[metadata]
+content-hash = "5c5e11cb29f8ef5ddf0c3bf98c849597799c71d99cd78157807f439a788036e7"
+platform = "*"
+python-versions = "~2.7 || ^3.4"
+
+[metadata.hashes]
+appdirs = ["9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", "d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"]
+"aspy.yaml" = ["04d26279513618f1024e1aba46471db870b3b33aef204c2d09bcf93bea9ba13f", "0a77e23fafe7b242068ffc0252cee130d3e509040908fc678d9d1060e7494baa"]
+atomicwrites = ["0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0", "ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"]
+attrs = ["10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69", "ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"]
+black = ["22158b89c1a6b4eb333a1e65e791a3f8b998cf3b11ae094adb2570f31f769a44", "4b475bbd528acce094c503a3d2dbc2d05a4075f6d0ef7d9e7514518e14cc5191"]
+cachecontrol = ["cef77effdf51b43178f6a2d3b787e3734f98ade253fa3187f3bb7315aaa42ff7"]
+cached-property = ["630fdbf0f4ac7d371aa866016eba1c3ac43e9032246748d4994e67cb05f99bc4", "f1f9028757dc40b4cb0fd2234bd7b61a302d7b84c683cb8c2c529238a24b8938"]
+cachy = ["b71513e5a38ce90c1280c02b7d8d6bb3fdf64666c9cc0584f2479afea097d56c", "b71e8e7ddb5b386e23e81befdfac8a93885406139b8681bedc17b3444fcb8fca"]
+certifi = ["376690d6f16d32f9d1fe8932551d80b23e9d393a8578c5633a2ed39a64861638", "456048c7e371c089d0a77a5212fb37a2c2dce1e24146e3b7e0261736aaeaa22a"]
+cfgv = ["73f48a752bd7aab103c4b882d6596c6360b7aa63b34073dd2c35c7b4b8f93010", "d1791caa9ff5c0c7bce80e7ecc1921752a2eb7c2463a08ed9b6c96b85a2f75aa"]
+chardet = ["84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", "fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"]
+cleo = ["85a63076b72ca376fb06668be1fc7758dc16740b394783d5cc65200c4b32f71b", "9b7f79f1aa470a025c0d28c76aa225ee9e65028d32f80032e871aa3500df61b8"]
+click = ["29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", "f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"]
+colorama = ["463f8483208e921368c9f306094eb6f725c6ca42b0f97e313cb5d5512459feda", "48eb22f4f8461b1df5734a074b57042430fb06e1d61bd1e11b078c0fe6d7a1f1"]
+coverage = ["03481e81d558d30d230bc12999e3edffe392d244349a90f4ef9b88425fac74ba", "0b136648de27201056c1869a6c0d4e23f464750fd9a9ba9750b8336a244429ed", "104ab3934abaf5be871a583541e8829d6c19ce7bde2923b2751e0d3ca44db60a", "10a46017fef60e16694a30627319f38a2b9b52e90182dddb6e37dcdab0f4bf95", "15b111b6a0f46ee1a485414a52a7ad1d703bdf984e9ed3c288a4414d3871dcbd", "198626739a79b09fa0a2f06e083ffd12eb55449b5f8bfdbeed1df4910b2ca640", "1c383d2ef13ade2acc636556fd544dba6e14fa30755f26812f54300e401f98f2", "23d341cdd4a0371820eb2b0bd6b88f5003a7438bbedb33688cd33b8eae59affd", "28b2191e7283f4f3568962e373b47ef7f0392993bb6660d079c62bd50fe9d162", "2a5b73210bad5279ddb558d9a2bfedc7f4bf6ad7f3c988641d83c40293deaec1", "2eb564bbf7816a9d68dd3369a510be3327f1c618d2357fa6b1216994c2e3d508", "337ded681dd2ef9ca04ef5d93cfc87e52e09db2594c296b4a0a3662cb1b41249", "3a2184c6d797a125dca8367878d3b9a178b6fdd05fdc2d35d758c3006a1cd694", "3c79a6f7b95751cdebcd9037e4d06f8d5a9b60e4ed0cd231342aa8ad7124882a", "3d72c20bd105022d29b14a7d628462ebdc61de2f303322c0212a054352f3b287", "3eb42bf89a6be7deb64116dd1cc4b08171734d721e7a7e57ad64cc4ef29ed2f1", "4635a184d0bbe537aa185a34193898eee409332a8ccb27eea36f262566585000", "56e448f051a201c5ebbaa86a5efd0ca90d327204d8b059ab25ad0f35fbfd79f1", "5a13ea7911ff5e1796b6d5e4fbbf6952381a611209b736d48e675c2756f3f74e", "69bf008a06b76619d3c3f3b1983f5145c75a305a0fea513aca094cae5c40a8f5", "6bc583dc18d5979dc0f6cec26a8603129de0304d5ae1f17e57a12834e7235062", "701cd6093d63e6b8ad7009d8a92425428bc4d6e7ab8d75efbb665c806c1d79ba", "7608a3dd5d73cb06c531b8925e0ef8d3de31fed2544a7de6c63960a1e73ea4bc", "76ecd006d1d8f739430ec50cc872889af1f9c1b6b8f48e29941814b09b0fd3cc", "7aa36d2b844a3e4a4b356708d79fd2c260281a7390d678a10b91ca595ddc9e99", "7d3f553904b0c5c016d1dad058a7554c7ac4c91a789fca496e7d8347ad040653", "7e1fe19bd6dce69d9fd159d8e4a80a8f52101380d5d3a4d374b6d3eae0e5de9c", "8c3cb8c35ec4d9506979b4cf90ee9918bc2e49f84189d9bf5c36c0c1119c6558", "9d6dd10d49e01571bf6e147d3b505141ffc093a06756c60b053a859cb2128b1f", "9e112fcbe0148a6fa4f0a02e8d58e94470fc6cb82a5481618fea901699bf34c4", "ac4fef68da01116a5c117eba4dd46f2e06847a497de5ed1d64bb99a5fda1ef91", "b8815995e050764c8610dbc82641807d196927c3dbed207f0a079833ffcf588d", "be6cfcd8053d13f5f5eeb284aa8a814220c3da1b0078fa859011c7fffd86dab9", "c1bb572fab8208c400adaf06a8133ac0712179a334c09224fb11393e920abcdd", "de4418dadaa1c01d497e539210cb6baa015965526ff5afc078c57ca69160108d", "e05cb4d9aad6233d67e0541caa7e511fa4047ed7750ec2510d466e806e0255d6", "e4d96c07229f58cb686120f168276e434660e4358cc9cf3b0464210b04913e77", "f3f501f345f24383c0000395b26b726e46758b71393267aeae0bd36f8b3ade80", "f8a923a85cb099422ad5a2e345fe877bbc89a8a8b23235824a93488150e45f6e"]
+enum34 = ["2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", "644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", "6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", "8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"]
+funcsigs = ["330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca", "a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50"]
+functools32 = ["89d824aa6c358c421a234d7f9ee0bd75933a67c29588ce50aaa3acdf4d403fa0", "f6253dfbe0538ad2e387bd8fdfd9293c925d63553f5813c4e587745416501e6d"]
+html5lib = ["20b159aa3badc9d5ee8f5c647e5efd02ed2a66ab8d354930bd9ff139fc1dc0a3", "66cb0dcfdbbc4f9c3ba1a63fdb511ffdbd4f513b2b6d81b80cd26ce6b3fb3736"]
+identify = ["49845e70fc6b1ec3694ab930a2c558912d7de24548eebcd448f65567dc757c43", "68daab16a3db364fa204591f97dc40bfffd1a7739f27788a4895b4d8fd3516e5"]
+idna = ["156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e", "684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"]
+jinja2 = ["74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", "f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4"]
+jsonschema = ["000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", "6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02"]
+livereload = ["583179dc8d49b040a9da79bd33de59e160d2a8802b939e304eb359a4419f6498", "dd4469a8f5a6833576e9f5433f1439c306de15dbbfeceabd32479b1123380fa5"]
+lockfile = ["6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", "6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"]
+markdown = ["9ba587db9daee7ec761cfc656272be6aabe2ed300fece21208e4aab2e457bc8f", "a856869c7ff079ad84a3e19cd87a64998350c2b94e9e08e44270faef33400f81"]
+markupsafe = ["a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"]
+mkdocs = ["50bf14ba9fdef391ae3e416ce66f1c598949d6e9cf2e9d7ed69abe3cf368539a", "ce8c238231d8df02e9f66390b5ff153f52682326d83cac412d7f44957b704fbb"]
+mock = ["5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1", "b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"]
+more-itertools = ["c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", "c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", "fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"]
+msgpack = ["0b3b1773d2693c70598585a34ca2715873ba899565f0a7c9a1545baef7e7fbdc", "0bae5d1538c5c6a75642f75a1781f3ac2275d744a92af1a453c150da3446138b", "0ee8c8c85aa651be3aa0cd005b5931769eaa658c948ce79428766f1bd46ae2c3", "1369f9edba9500c7a6489b70fdfac773e925342f4531f1e3d4c20ac3173b1ae0", "22d9c929d1d539f37da3d1b0e16270fa9d46107beab8c0d4d2bddffffe895cee", "2ff43e3247a1e11d544017bb26f580a68306cec7a6257d8818893c1fda665f42", "31a98047355d34d047fcdb55b09cb19f633cf214c705a765bd745456c142130c", "8767eb0032732c3a0da92cbec5ac186ef89a3258c6edca09161472ca0206c45f", "8acc8910218555044e23826980b950e96685dc48124a290c86f6f41a296ea172", "ab189a6365be1860a5ecf8159c248f12d33f79ea799ae9695fa6a29896dcf1d4", "cfd6535feb0f1cf1c7cdb25773e965cc9f92928244a8c3ef6f8f8a8e1f7ae5c4", "e274cd4480d8c76ec467a85a9c6635bbf2258f0649040560382ab58cabb44bcf", "f86642d60dca13e93260187d56c2bef2487aa4d574a669e8ceefcf9f4c26fd00", "f8a57cbda46a94ed0db55b73e6ab0c15e78b4ede8690fa491a0e55128d552bb0", "fcea97a352416afcbccd7af9625159d80704a25c519c251c734527329bb20d0e"]
+nodeenv = ["aa040ab5189bae17d272175609010be6c5b589ec4b8dbd832cc50c9e9cb7496f"]
+pastel = ["3108af417ec0fa6d0a620e676ec4f02c839ca13e10611586e5d2174b46aa0bc3", "d1fee8079534f99f1805a044fef946d23eee6d6a7cd34292c30e6c16be9a80b9"]
+pathlib2 = ["8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83", "d1aa2a11ba7b8f7b21ab852b1fb5afb277e1bb99d5dfc663380b5015c0d80c5a"]
+pbr = ["1b8be50d938c9bb75d0eaf7eda111eec1bf6dc88a62a6412e33bf077457e0f45", "b486975c0cafb6beeb50ca0e17ba047647f229087bd74e37f4a7e2cac17d2caa"]
+pkginfo = ["5878d542a4b3f237e359926384f1dde4e099c9f5525d236b1840cf704fa8d474", "a39076cb3eb34c333a0dd390b568e9e1e881c7bf2cc0aee12120636816f55aee"]
+pluggy = ["6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", "95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1"]
+pre-commit = ["18ef2892ff6b6518945bc7dcf423e3c969033a4ca132b6a1ae0d52eb2e51ea27", "88d59872610a7069d937b6868632ba534187bda58c4665de12b25c8c549ddd0e"]
+py = ["06a30435d058473046be836d3fc4f27167fd84c45b99704f2fb5509ef61f9af1", "50402e9d1c9005d759426988a492e0edaadb7f4e68bcddfea586bc7432d009c6"]
+pygments = ["78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", "dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc"]
+pygments-github-lexers = ["0f9e9fb607d351c127a1e55e82a6eb491ed1fc11b2d6a0444ba217dc6d1f82c1", "aaca57e77cd6fcfce8d6ee97a998962eebf7fbb810519a8ebde427c62823e133"]
+pylev = ["063910098161199b81e453025653ec53556c1be7165a9b7c50be2f4d57eae1c3", "1d29a87beb45ebe1e821e7a3b10da2b6b2f4c79b43f482c2df1a1f748a6e114e"]
+pymdown-extensions = ["20f2ae1067ab850cab92fcf57487267a7fd1365a7b1e7c5394e1e0778455eec1", "7d3fcbb4c5d70a78d1f4c2c7eef02dbe7e1ba08b06cb72e08b3d1027eb77458b"]
+pyparsing = ["0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04", "281683241b25fe9b80ec9d66017485f6deff1af5cde372469134b56ca8447a07", "8f1e18d3fd36c6795bb7e02a39fd05c611ffc2596c1e0d995d34d67630426c18", "9e8143a3e15c13713506886badd96ca4b579a87fbdf49e550dbfc057d6cb218e", "b8b3117ed9bdf45e14dcc89345ce638ec7e0e29b2b579fa1ecf32ce45ebac8a5", "e4d45427c6e20a59bf4f88c639dcc03ce30d193112047f94012102f235853a58", "fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"]
+pyrsistent = ["4024f838472cba9ea1ccbc638e0bcafec2efda28594a9905177ec365f1a95fea"]
+pytest = ["2d7c49e931316cc7d1638a3e5f54f5d7b4e5225972b3c9838f3584788d27f349", "ad0c7db7b5d4081631e0155f5c61b80ad76ce148551aaafe3a718d65a7508b18"]
+pytest-cov = ["513c425e931a0344944f84ea47f3956be0e416d95acbd897a44970c8d926d5d7", "e360f048b7dae3f2f2a9a4d067b2dd6b6a015d384d1577c994a43f3f7cbad762"]
+pytest-mock = ["53801e621223d34724926a5c98bd90e8e417ce35264365d39d6c896388dcc928", "d89a8209d722b8307b5e351496830d5cc5e192336003a485443ae9adeb7dd4c0"]
+pyyaml = ["3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b", "3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf", "40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a", "558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3", "a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1", "aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1", "bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613", "d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04", "d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f", "e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537", "e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"]
+requests = ["63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1", "ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"]
+requests-toolbelt = ["42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237", "f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5"]
+scandir = ["04b8adb105f2ed313a7c2ef0f1cf7aff4871aa7a1883fa4d8c44b5551ab052d6", "1444134990356c81d12f30e4b311379acfbbcd03e0bab591de2696a3b126d58e", "1b5c314e39f596875e5a95dd81af03730b338c277c54a454226978d5ba95dbb6", "346619f72eb0ddc4cf355ceffd225fa52506c92a2ff05318cfabd02a144e7c4e", "44975e209c4827fc18a3486f257154d34ec6eaec0f90fef0cca1caa482db7064", "61859fd7e40b8c71e609c202db5b0c1dbec0d5c7f1449dec2245575bdc866792", "a5e232a0bf188362fa00123cc0bb842d363a292de7126126df5527b6a369586a", "c14701409f311e7a9b7ec8e337f0815baf7ac95776cc78b419a1e6d49889a383", "c7708f29d843fc2764310732e41f0ce27feadde453261859ec0fca7865dfc41b", "c9009c527929f6e25604aec39b0a43c3f831d2947d89d6caaab22f057b7055c8", "f5c71e29b4e2af7ccdc03a020c626ede51da471173b4a6ad1e904f2b2e04b4bd"]
+shellingham = ["869c760320b6d4cd88ac5bdc2a6a40c042fb388c5fcd056f8a07a82d9859cc40", "c09c563a2e185ec3d64e43c286dbba3150fc182d96cd29ff5b002f3d3c3f5076"]
+six = ["70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"]
+toml = ["8e86bd6ce8cc11b9620cb637466453d94f5d57ad86f17e98a98d1f73e3baab2d"]
+tomlkit = ["8ab16e93162fc44d3ad83d2aa29a7140b8f7d996ae1790a73b9a7aed6fb504ac", "ca181cee7aee805d455628f7c94eb8ae814763769a93e69157f250fe4ebe1926"]
+tornado = ["1c0816fc32b7d31b98781bd8ebc7a9726d7dce67407dc353a2e66e697e138448", "4f66a2172cb947387193ca4c2c3e19131f1c70fa8be470ddbbd9317fd0801582", "5327ba1a6c694e0149e7d9126426b3704b1d9d520852a3e4aa9fc8fe989e4046", "6a7e8657618268bb007646b9eae7661d0b57f13efc94faa33cd2588eae5912c9", "a9b14804783a1d77c0bd6c66f7a9b1196cbddfbdf8bceb64683c5ae60bd1ec6f", "c58757e37c4a3172949c99099d4d5106e4d7b63aa0617f9bb24bfbff712c7866", "d8984742ce86c0855cccecd5c6f54a9f7532c983947cff06f3a0e2115b47f85c"]
+tox = ["37cf240781b662fb790710c6998527e65ca6851eace84d1595ee71f7af4e85f7", "eb61aa5bcce65325538686f09848f04ef679b5cd9b83cc491272099b28739600"]
+typing = ["4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d", "57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4", "a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a"]
+urllib3 = ["a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf", "b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"]
+virtualenv = ["2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669", "ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"]
+webencodings = ["a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", "b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"]
diff --git a/sonnet b/sonnet
new file mode 100755
index 00000000000..a550bde6c1c
--- /dev/null
+++ b/sonnet
@@ -0,0 +1,190 @@
+#!/usr/bin/env python
+import hashlib
+import os
+import shutil
+import subprocess
+import sys
+import tarfile
+
+from gzip import GzipFile
+
+from cleo import Application
+from cleo import Command
+
+WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")
+
+
+class MakeReleaseCommand(Command):
+ """
+ Makes a self-contained package of Poetry.
+
+ make:release
+ {--P|python=?* : Python version to use}
+ """
+
+ PYTHON = {
+ "2.7": "python2.7",
+ "3.4": "python3.4",
+ "3.5": "python3.5",
+ "3.6": "python3.6",
+ "3.7": "python3.7",
+ }
+
+ def handle(self):
+ pythons = self.PYTHON
+ if self.option("python"):
+ pythons = {}
+ for python in self.option("python"):
+ parts = python.split(":", 1)
+ if len(parts) == 1:
+ pythons[parts[0]] = self.PYTHON[parts[0]]
+
+ version, python = parts
+ pythons[version] = python
+
+ self.check_system(pythons)
+
+ from poetry import __version__
+ from poetry.poetry import Poetry
+ from poetry.puzzle import Solver
+ from poetry.repositories.pool import Pool
+ from poetry.repositories.repository import Repository
+ from poetry.utils._compat import Path
+ from poetry.utils.helpers import temporary_directory
+
+ project = Poetry.create(Path.cwd())
+ package = project.package
+ del package.dev_requires[:]
+
+ # We only use the lock file to resolve the dependencies
+ pool = Pool()
+ pool.add_repository(project.locker.locked_repository(with_dev_reqs=True))
+
+ with temporary_directory() as tmp_dir:
+ # Copy poetry to tmp dir
+ poetry_dir = os.path.join(tmp_dir, "poetry")
+ shutil.copytree(
+ os.path.join(os.path.dirname(__file__), "poetry"), poetry_dir
+ )
+ for version, python in sorted(pythons.items()):
+ self.line(
+ "Preparing files for Python {}".format(
+ version
+ )
+ )
+ with package.with_python_versions("^{}".format(version)):
+ solver = Solver(
+ package, pool, Repository(), Repository(), self.output
+ )
+ ops = solver.solve()
+
+ self.vendorize_for_python(
+ python, [op.package for op in ops], poetry_dir, version
+ )
+
+ self.line("")
+
+ self.line("Packaging files")
+ with temporary_directory() as tmp_dir2:
+ base_name = "poetry-{}-{}".format(__version__, sys.platform)
+ name = "{}.tar.gz".format(base_name)
+ gz = GzipFile(os.path.join(tmp_dir2, name), mode="wb")
+ try:
+ with tarfile.TarFile(
+ os.path.join(tmp_dir2, name),
+ mode="w",
+ fileobj=gz,
+ format=tarfile.PAX_FORMAT,
+ ) as tar:
+ for root, dirs, files in os.walk(tmp_dir):
+ for f in files:
+ if f.endswith(".pyc"):
+ continue
+
+ path = os.path.join(os.path.realpath(root), f)
+ relpath = os.path.relpath(
+ path, os.path.realpath(tmp_dir)
+ )
+ tar_info = tar.gettarinfo(str(path), arcname=relpath)
+
+ if tar_info.isreg():
+ with open(path, "rb") as f:
+ tar.addfile(tar_info, f)
+ else:
+ tar.addfile(tar_info) # Symlinks & ?
+ finally:
+ gz.close()
+
+ releases_dir = os.path.join(os.path.dirname(__file__), "releases")
+ if not os.path.exists(releases_dir):
+ os.mkdir(releases_dir)
+
+ shutil.copyfile(
+ os.path.join(tmp_dir2, name), os.path.join(releases_dir, name)
+ )
+
+ # Compute hash
+ sha = hashlib.sha256()
+ with open(os.path.join(releases_dir, name), "rb") as f:
+ while True:
+ buffer = f.read(8192)
+ if not buffer:
+ break
+
+ sha.update(buffer)
+
+ with open(
+ os.path.join(releases_dir, "{}.sha256sum".format(base_name)), "w"
+ ) as f:
+ f.write(sha.hexdigest())
+
+ self.line("Built {}".format(name))
+
+ def check_system(self, pythons):
+ for version, python in sorted(pythons.items()):
+ try:
+ subprocess.check_output(
+ [python, "-V"], stderr=subprocess.STDOUT, shell=WINDOWS
+ )
+ except subprocess.CalledProcessError:
+ raise RuntimeError("Python {} is not available".format(version))
+
+ def vendorize_for_python(self, python, packages, dest, python_version):
+ vendor_dir = os.path.join(dest, "_vendor", "py{}".format(python_version))
+
+ bar = self.progress_bar(max=len(packages))
+ bar.set_format("%message% %current%/%max%")
+ bar.set_message(
+ "Vendorizing dependencies for Python {}".format(
+ python_version
+ )
+ )
+ bar.start()
+ for package in packages:
+ subprocess.check_output(
+ [
+ python,
+ "-m",
+ "pip",
+ "install",
+ package.name,
+ "--no-deps",
+ "--target",
+ vendor_dir,
+ ],
+ stderr=subprocess.STDOUT,
+ shell=WINDOWS,
+ )
+ bar.advance()
+
+ bar.finish()
+
+ self.line("")
+
+
+app = Application("Sonnet")
+
+app.add(MakeReleaseCommand())
+
+if __name__ == "__main__":
+ app.run()