diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..324395c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+*.cache
+*.log
+*.log.*
+*.pid
+*.wpr
+*.wpu
+*.a
+*.o
+*.py[co]
+*.so
+*.sw[nop]
+__pycache__
+*~
+.#*
+[#]*#
+dropin.cache
+RE:\.?[^.]+
+*.e4p
+.pytest_cache
+environment.yml
+dist
+*.egg-info
+.coverage
+htmlcov
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..4b7e0bd
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,78 @@
+# Info:
+# -----
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+
+fail_fast: True
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v2.2.3
+ hooks:
+ #- id: no-commit-to-branch
+ #- args: [--branch, master]
+ - id: check-docstring-first
+ - id: trailing-whitespace
+ args: [--markdown-linebreak-ext=md]
+ - id: end-of-file-fixer
+ - id: check-merge-conflict
+ - id: double-quote-string-fixer
+ - id: mixed-line-ending
+ args: [--fix=lf]
+ - id: check-executables-have-shebangs
+ - id: fix-encoding-pragma
+ - id: check-case-conflict
+ - id: name-tests-test
+ - repo: https://github.com/pre-commit/pygrep-hooks
+ rev: v1.4.1
+ hooks:
+ - id: python-use-type-annotations
+ - id: python-no-log-warn
+ - repo: https://github.com/asottile/reorder_python_imports
+ rev: v1.6.0
+ hooks:
+ - id: reorder-python-imports
+ - repo: https://github.com/asottile/add-trailing-comma
+ rev: v1.4.1
+ hooks:
+ - id: add-trailing-comma
+ - repo: https://github.com/pre-commit/mirrors-yapf
+ rev: v0.28.0
+ hooks:
+ - id: yapf
+ types: [python]
+ always_run: true
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v2.2.3
+ hooks:
+ - id: check-ast
+ - repo: https://gitlab.com/pycqa/flake8
+ rev: ace069c9c3585b4c74348f94da313bbc020dfc73
+ hooks:
+ - id: flake8
+ - repo: https://github.com/codespell-project/codespell
+ rev: 70196e35a7baa730e26ab6a0da77b5e68d980611
+ hooks:
+ - id: codespell
+ - repo: local
+ hooks:
+ - id: pytest
+ name: pytest
+ description: running pytest
+ entry: bash -c "$VIRTUAL_ENV/bin/pytest"
+ language: system
+ types: [python]
+ pass_filenames: false
+ - id: coverage-badge
+ name: coverage-badge
+ description: create the coverage badge
+ entry: bash -c "$VIRTUAL_ENV/bin/coverage-badge -f -o docs/img/coverage.svg"
+ language: system
+ types: [python]
+ pass_filenames: false
+ - id: build-package
+ name: build the package for the repo
+ description: Generate the dist packages for this repo
+ language: system
+ entry: dephell project build
+ files: "^pyproject.toml$"
+ pass_filenames: false
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000..c1e43e6
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.7.3
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..268f2a9
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Simon Kallfass
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c2d0cb2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,200 @@
+# conda-env-manager: cenv
+
+![coverage](docs/img/coverage.svg)
+[![PyPI version fury.io](https://badge.fury.io/py/ansicolortags.svg)](https://pypi.python.org/pypi/cenv_tool/)
+[![PyPI pyversions](https://img.shields.io/pypi/pyversions/ansicolortags.svg)](https://pypi.python.org/pypi/cenv_tool/)
+[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/)
+
+![logo](docs/img/logo.png)
+
+Due to the redundant dependency information inside the `meta.yaml` (required
+to create the conda-package) and the `environment.yml` (as definition file
+for the conda-environment during development and for production), `cenv`
+(short form for `conda-env-manager`) was created to make the `meta.yaml`
+the only relevant file and to create and update conda-environment from the
+definition inside this `meta.yaml`.
+The name of the conda-environment to create / update is defined in the section
+`extra` and the variable `env_name` inside the `meta.yaml`.
+
+The steps run by cenv:
+
+* creation of a backup if the environment already exists followed by the
+ removal of the previous environment.
+* creation of the environment as defined in the `meta.yaml`.
+ If any failures occurred during creation and the backup was created, the
+ command to reset the backup-version can be used.
+* if enabled in the config file the environment.yml is exported after creation
+ / update of the environment.
+
+
+The usage of cenv reduces the conda commands to use to the following:
+
+* `conda activate ...` to activate the environment
+* `conda deactivate` to deactivate an environment
+* `conda info` to show information about the currently activated environment
+* `conda search ...` to search for availability of a package in the conda
+ channels.
+* `conda remove -n ... --all` to remove an environment
+* `cenv` to create / update an environment
+
+
+## Installation
+
+Install `cenv` using pip:
+```bash
+pip3 install cenv_tool
+```
+
+Now run `init_cenv` to create the relevant config-files and add the
+autoactivate- and autoupdate-shell-function to your `.bashrc` / `.zshrc`.
+
+
+### autoactivate and autoupdate
+
+Per default these features are deactivated, even if added to your shell by
+running `init_cenv`.
+
+
+#### autoactivate-feature
+
+The autoactivate-feature activates the conda-environment as named
+`extra`-section in the meta.yaml located at `conda-build/meta.yaml`, if the
+environment exists.
+To activate the autoactivate-features run:
+```bash
+autoactivate_toggle
+```
+
+#### autoupdate-feature
+
+The autoupdate checks if the content of the meta.yaml changed.
+The current state is stored as a md5sum in `conda-build/meta.md5`.
+If it changed the cenv-process is called.
+
+For the autoupdate-feature run:
+```bash
+autoupdate_toggle
+```
+
+
+## Usage
+
+All steps required to create or update the projects conda environment are
+run automatically running:
+```bash
+cenv
+```
+
+**ATTENTION**:
+> If you use cenv, each environment should only be created, updated and
+> modified using `cenv`!
+> This means the commands `conda install`, `conda remove` are not used
+> anymore.
+> Changes of the dependencies of the environment are defined inside the
+> `meta.yaml` and are applied by using `cenv`.
+>
+> This means:
+>
+> * new dependency required => add it in `meta.yaml` and run `cenv`.
+> * dependency not needed anymore => remove it from `meta.yaml` and run
+> `cenv`.
+> * need of another version of dependency => change the version of dependency
+> in `meta.yaml` and run `cenv`.
+
+The required information about the projects conda environment are extracted
+from the meta.yaml.
+This meta.yaml should be located inside the project folder at
+`./conda-build/meta.yaml`.
+The project-configuration is defined in the `extra` section of the `meta.yaml`.
+There you can define the name of the projects conda-environment at
+`env_name`.
+Also you can define requirements only needed during development but not to be
+included into the resulting conda package.
+These requirements have to be defined in the `dev_requirements`-section.
+
+All other parts of the `meta.yaml` have to be defined as default.
+
+A meta.yaml valid for cenv should look like the following:
+```yaml
+ {% set data = load_setup_py_data() %}
+
+ package:
+ name: "example_package"
+ version: {{ data.get("version") }}
+
+ source:
+ path: ..
+
+ build:
+ build: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }}
+ preserve_egg_dir: True
+ script: python -m pip install --no-deps --ignore-installed .
+
+ requirements:
+ build:
+ - python 3.6.8
+ - pip
+ - setuptools
+ run:
+ - python 3.6.8
+ - attrs >=18.2
+ - jinja2 >=2.10
+ - ruamel.yaml >=0.15.23
+ - six >=1.12.0
+ - yaml >=0.1.7
+ - marshmallow >=3.0.0rc1*
+
+ test:
+ imports:
+ - example_package
+
+ extra:
+ env_name: example
+ dev_requirements:
+ - ipython >=7.2.0
+```
+
+**ATTENTION**:
+> In the `requirements-run-section` the minimal version of each package
+> has to be defined!
+> The same is required for the `dev_requirements`-section.
+> Not defining a version will not create or update a conda-environment,
+> because this is not the purpose of the conda-usage.
+> The validity of the `meta.yaml` is checked in `cenv` using the
+> `marshmallow` package.
+> You can additionally add upper limits for the version like the following:
+> `- package >=0.1,<0.3`
+
+If cenv is run the environment is created / updated from the definition inside
+this `meta.yaml`.
+The creation of the backup of the previous environment ensures to undo changes
+if any error occurs during recreation of the environment.
+
+
+**ATTENTION**:
+> `cenv` can only update the environment if it is not activated.
+> So ensure the environment to be deactivated before running `cenv`.
+
+Per default exporting the conda environment definition into an environment.yml
+is turned off.
+If you want to turn this functionality on you need to modify your
+`~/.config/cenv.yml` as described in the configuration-part.
+
+Example for the output of the `cenv` command:
+
+```bash
+ ┣━━ Cloning existing env as backup ...
+ ┣━━ Removing existing env ...
+ ┣━━ Creating env ...
+ ┣━━ Removing backup ...
+ ┗━━ Exporting env to environment.yml ...
+```
+
+# Development of cenv
+
+To create the environment to develop cenv run the pre-commit hooks manually:
+```bash
+pyenv local 3.7.3
+pre-commit run --all-files
+poetry shell
+```
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..48a5079
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,243 @@
+
+conda-env-manager: cenv
+=======================
+
+
+.. image:: coverage.svg
+ :target: coverage.svg
+ :alt: coverage
+
+
+.. image:: https://badge.fury.io/py/ansicolortags.svg
+ :target: https://pypi.python.org/pypi/cenv_tool/
+ :alt: PyPI version fury.io
+
+
+.. image:: https://img.shields.io/pypi/pyversions/ansicolortags.svg
+ :target: https://pypi.python.org/pypi/cenv_tool/
+ :alt: PyPI pyversions
+
+
+.. image:: https://img.shields.io/badge/License-MIT-blue.svg
+ :target: https://lbesson.mit-license.org/
+ :alt: MIT license
+
+
+
+.. image:: docs/img/logo.png
+ :target: docs/img/logo.png
+ :alt: logo
+
+
+Due to the redundant dependency information inside the ``meta.yaml`` (required
+to create the conda-package) and the ``environment.yml`` (as definition file
+for the conda-environment during development and for production), ``cenv``
+(short form for ``conda-env-manager``\ ) was created to make the ``meta.yaml``
+the only relevant file and to create and update conda-environment from the
+definition inside this ``meta.yaml``.
+The name of the conda-environment to create / update is defined in the section
+``extra`` and the variable ``env_name`` inside the ``meta.yaml``.
+
+The steps run by cenv:
+
+
+* creation of a backup if the environment already exists followed by the
+ removal of the previous environment.
+* creation of the environment as defined in the ``meta.yaml``.
+ If any failures occurred during creation and the backup was created, the
+ command to reset the backup-version can be used.
+* if enabled in the config file the environment.yml is exported after creation
+ / update of the environment.
+
+The usage of cenv reduces the conda commands to use to the following:
+
+
+* ``conda activate ...`` to activate the environment
+* ``conda deactivate`` to deactivate an environment
+* ``conda info`` to show information about the currently activated environment
+* ``conda search ...`` to search for availability of a package in the conda
+ channels.
+* ``conda remove -n ... --all`` to remove an environment
+* ``cenv`` to create / update an environment
+
+Installation
+------------
+
+Install ``cenv`` using pip:
+
+.. code-block:: bash
+
+ pip3 install cenv_tool
+
+Now run ``init_cenv`` to create the relevant config-files and add the
+autoactivate- and autoupdate-shell-function to your ``.bashrc`` / ``.zshrc``.
+
+autoactivate and autoupdate
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Per default these features are deactivated, even if added to your shell by
+running ``init_cenv``.
+
+autoactivate-feature
+~~~~~~~~~~~~~~~~~~~~
+
+The autoactivate-feature activates the conda-environment as named
+``extra``\ -section in the meta.yaml located at ``conda-build/meta.yaml``\ , if the
+environment exists.
+To activate the autoactivate-features run:
+
+.. code-block:: bash
+
+ autoactivate_toggle
+
+autoupdate-feature
+~~~~~~~~~~~~~~~~~~
+
+The autoupdate checks if the content of the meta.yaml changed.
+The current state is stored as a md5sum in ``conda-build/meta.md5``.
+If it changed the cenv-process is called.
+
+For the autoupdate-feature run:
+
+.. code-block:: bash
+
+ autoupdate_toggle
+
+Usage
+-----
+
+All steps required to create or update the projects conda environment are
+run automatically running:
+
+.. code-block:: bash
+
+ cenv
+
+**ATTENTION**\ :
+
+..
+
+ If you use cenv, each environment should only be created, updated and
+ modified using ``cenv``\ !
+ This means the commands ``conda install``\ , ``conda remove`` are not used
+ anymore.
+ Changes of the dependencies of the environment are defined inside the
+ ``meta.yaml`` and are applied by using ``cenv``.
+
+ This means:
+
+
+ * new dependency required => add it in ``meta.yaml`` and run ``cenv``.
+ * dependency not needed anymore => remove it from ``meta.yaml`` and run
+ ``cenv``.
+ * need of another version of dependency => change the version of dependency
+ in ``meta.yaml`` and run ``cenv``.
+
+
+The required information about the projects conda environment are extracted
+from the meta.yaml.
+This meta.yaml should be located inside the project folder at
+``./conda-build/meta.yaml``.
+The project-configuration is defined in the ``extra`` section of the ``meta.yaml``.
+There you can define the name of the projects conda-environment at
+``env_name``.
+Also you can define requirements only needed during development but not to be
+included into the resulting conda package.
+These requirements have to be defined in the ``dev_requirements``\ -section.
+
+All other parts of the ``meta.yaml`` have to be defined as default.
+
+A meta.yaml valid for cenv should look like the following:
+
+.. code-block:: yaml
+
+ {% set data = load_setup_py_data() %}
+
+ package:
+ name: "example_package"
+ version: {{ data.get("version") }}
+
+ source:
+ path: ..
+
+ build:
+ build: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }}
+ preserve_egg_dir: True
+ script: python -m pip install --no-deps --ignore-installed .
+
+ requirements:
+ build:
+ - python 3.6.8
+ - pip
+ - setuptools
+ run:
+ - python 3.6.8
+ - attrs >=18.2
+ - jinja2 >=2.10
+ - ruamel.yaml >=0.15.23
+ - six >=1.12.0
+ - yaml >=0.1.7
+ - marshmallow >=3.0.0rc1*
+
+ test:
+ imports:
+ - example_package
+
+ extra:
+ env_name: example
+ dev_requirements:
+ - ipython >=7.2.0
+
+**ATTENTION**\ :
+
+..
+
+ In the ``requirements-run-section`` the minimal version of each package
+ has to be defined!
+ The same is required for the ``dev_requirements``\ -section.
+ Not defining a version will not create or update a conda-environment,
+ because this is not the purpose of the conda-usage.
+ The validity of the ``meta.yaml`` is checked in ``cenv`` using the
+ ``marshmallow`` package.
+ You can additionally add upper limits for the version like the following:
+ ``- package >=0.1,<0.3``
+
+
+If cenv is run the environment is created / updated from the definition inside
+this ``meta.yaml``.
+The creation of the backup of the previous environment ensures to undo changes
+if any error occurs during recreation of the environment.
+
+**ATTENTION**\ :
+
+..
+
+ ``cenv`` can only update the environment if it is not activated.
+ So ensure the environment to be deactivated before running ``cenv``.
+
+
+Per default exporting the conda environment definition into an environment.yml
+is turned off.
+If you want to turn this functionality on you need to modify your
+``~/.config/cenv.yml`` as described in the configuration-part.
+
+Example for the output of the ``cenv`` command:
+
+.. code-block:: bash
+
+ ┣━━ Cloning existing env as backup ...
+ ┣━━ Removing existing env ...
+ ┣━━ Creating env ...
+ ┣━━ Removing backup ...
+ ┗━━ Exporting env to environment.yml ...
+
+Development of cenv
+===================
+
+To create the environment to develop cenv run the pre-commit hooks manually:
+
+.. code-block:: bash
+
+ pyenv local 3.7.3
+ pre-commit run --all-files
+ poetry shell
diff --git a/cenv_tool/__init__.py b/cenv_tool/__init__.py
new file mode 100644
index 0000000..c8c3ccc
--- /dev/null
+++ b/cenv_tool/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+"""Conda environment creation and update from meta.yaml."""
+from pkg_resources import get_distribution
+
+try:
+ __version__ = get_distribution('cenv_tool').version
+except AttributeError:
+ __version__ = ''
diff --git a/cenv_tool/cenv.sh b/cenv_tool/cenv.sh
new file mode 100644
index 0000000..ffc2558
--- /dev/null
+++ b/cenv_tool/cenv.sh
@@ -0,0 +1,75 @@
+source /opt/conda/etc/profile.d/conda.sh
+export AUTOACTIVATE=1
+export AUTOUPDATE=1
+export meta_yaml_path=conda-build/meta.yaml
+export meta_yaml_md5_path=conda-build/meta.md5
+CYAN='\033[0;94m'
+GREEN='\033[1;32m'
+RED='\033[1;91m'
+COLOR_END='\033[0m'
+AS_BOLD='\033[1;37m'
+
+
+
+function autoactivate_toggle(){
+ if (( $AUTOACTIVATE == 1 )); then
+ export AUTOACTIVATE=0
+ else
+ export AUTOACTIVATE=1
+ fi
+}
+
+
+function autoupdate_toggle(){
+ if (( $AUTOUPDATE == 1 )); then
+ export AUTOUPDATE=0
+ else
+ export AUTOUPDATE=1
+ fi
+}
+
+
+function get_envname_from_meta(){
+ if [ -e $meta_yaml_path ]; then
+ while IFS='' read -r line || [[ -n "$line" ]]; do
+ if [[ $line == *"env_name"* ]]; then
+ echo $( echo "$line" | cut -d' ' -f 6-)
+ fi
+ done < $meta_yaml_path;
+ fi
+}
+
+
+function autoactivate_env() {
+ if (( $AUTOUPDATE == 1 )); then
+ if [ -e $PWD/$meta_yaml_md5_path ]; then
+ local new_md5=$(echo "$(md5sum $PWD/$meta_yaml_path)" | cut -d' ' -f1)
+ local current_md5="$(head -n 1 $PWD/$meta_yaml_md5_path)"
+ if ! [ $new_md5 = $current_md5 ]; then
+ conda deactivate
+ cenv
+ fi
+ fi
+ fi
+
+ if (( $AUTOACTIVATE == 1 )); then
+ if [ -e $PWD/$meta_yaml_path ]; then
+ local env="$(get_envname_from_meta)"
+ local env="${env%\"}"
+ local env="${env#\"}"
+ if [[ $PATH != *$env* ]]; then
+ if conda activate $env 2>/dev/null && [[ $? -eq 0 ]]; then
+ CONDA_ENV_ROOT="$(pwd)"
+ PYTHONPATH=.:$PYTHONPATH
+ echo -e "${GREEN}"'\u2714'" activated${COLOR_END} $env"
+ fi
+ fi
+ elif [[ $PATH = */envs/* ]] && [[ $(pwd) != $CONDA_ENV_ROOT ]] \
+ && [[ $(pwd) != $CONDA_ENV_ROOT/* ]]; then
+ CONDA_ENV_ROOT=""
+ conda deactivate
+ unset PYTHONPATH
+ echo -e "${RED}"'\u2718'" deactivated${COLOR_END} env"
+ fi
+ fi
+}
diff --git a/cenv_tool/cenv.yml b/cenv_tool/cenv.yml
new file mode 100644
index 0000000..5339b4a
--- /dev/null
+++ b/cenv_tool/cenv.yml
@@ -0,0 +1,3 @@
+conda_folder: /opt/conda
+env_folder: /shared/conda/envs
+export_environment_yml: false
diff --git a/cenv_tool/init_cenv.py b/cenv_tool/init_cenv.py
new file mode 100644
index 0000000..ed57115
--- /dev/null
+++ b/cenv_tool/init_cenv.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+"""Install config and cenv.sh."""
+from pathlib import Path
+from typing import NoReturn
+
+from cenv_tool.utils import message
+
+CONFIG_PATH = Path.home() / '.config/cenv'
+AUTOENV_SCRIPT_PATH = CONFIG_PATH / 'cenv.sh'
+AUTOENV_SCRIPT_SOURCE_PATH = Path(__file__).with_name('cenv.sh')
+CONFIG_FILE = CONFIG_PATH / 'cenv.yml'
+CONFIG_FILE_SOURCE = Path(__file__).with_name('cenv.yml')
+ZSHRC = Path.home() / '.zshrc'
+BASHRC = Path.home() / '.bashrc'
+
+RC_CONTENT = """
+# ======================================================================>>>>>>>
+# AUTOMATICALLY ADDED BY CENV TO ENABLE AUTOUPDATE AND AUTOACTIVATE
+
+# load the cenv shell functions
+source $HOME/.config/cenv/cenv.sh
+
+# 0 means deactivated, 1 activated
+# disable autoactivate
+export AUTOACTIVATE=0
+# disable autoupdate
+export AUTOUPDATE=0
+
+# enable the autoactivation of conda-environments
+precmd() { autoactivate_env; }
+# <-----
+# <<<<<<<======================================================================
+"""
+
+
+def initialize_cenv(
+ config_path: Path,
+ autoenv_script_path: Path,
+ autoenv_script_source_path: Path,
+ config_file: Path,
+ config_file_source: Path,
+ zshrc: Path,
+ bashrc: Path,
+) -> NoReturn:
+ """Install user-config and cenv.sh for autoactivate and autoupdate.
+
+ Parameters:
+ config_path: the path for cenv config-stuff.
+ autoenv_script_path: the path to install the cenv.sh script to.
+ autoenv_script_source_path: the path where to get the cenv.sh script
+ from
+ config_file: the path to install the user-config into.
+ config_file_source: the path where to get the config file from.
+ zshrc: the path to the users .zshrc
+ bashrc: the path to the users .bashrc
+
+ """
+ if not config_path.exists():
+ message(text='creating cenv-config folder', color='cyan')
+ config_path.mkdir(parents=True)
+ message(text='created cenv-config folder', color='green')
+ else:
+ message(text='cenv-config folder already exists', color='green')
+
+ if not autoenv_script_path.exists():
+ message(
+ text=(
+ 'copying script for autoactivation and autoupdate to '
+ 'config-folder'
+ ),
+ color='cyan',
+ )
+ autoenv_script_path.write_text(autoenv_script_source_path.read_text())
+ message(
+ text='copied script for autoactivateion and autoupdate',
+ color='green',
+ )
+ else:
+ message(
+ text='script for autoactivation and autupdate already copied',
+ color='green',
+ )
+
+ if not config_file.exists():
+ message(
+ text='copying config-file to cenv-config-folder ...',
+ color='cyan',
+ )
+ config_file.write_text(config_file_source.read_text())
+ message(text='copied config-file', color='green')
+ else:
+ message(text='config-file already exists', color='green')
+
+ for shell_config in (zshrc, bashrc):
+ if shell_config.exists():
+ if RC_CONTENT not in shell_config.read_text():
+ message(
+ text=f'initilized cenv in {shell_config}',
+ color='green',
+ )
+ with open(shell_config, 'a') as opened_shell_config:
+ opened_shell_config.write(RC_CONTENT)
+ else:
+ message(
+ text=f'cenv already initialized in {shell_config}',
+ color='green',
+ )
+
+
+def main():
+ """Call the initialization function to install config and cenv.sh."""
+ initialize_cenv(
+ config_path=CONFIG_PATH,
+ autoenv_script_path=AUTOENV_SCRIPT_PATH,
+ autoenv_script_source_path=AUTOENV_SCRIPT_SOURCE_PATH,
+ config_file=CONFIG_FILE,
+ config_file_source=CONFIG_FILE_SOURCE,
+ zshrc=ZSHRC,
+ bashrc=BASHRC,
+ )
+
+
+if __name__ == '__main__':
+ main()
diff --git a/cenv_tool/project.py b/cenv_tool/project.py
new file mode 100644
index 0000000..af75354
--- /dev/null
+++ b/cenv_tool/project.py
@@ -0,0 +1,277 @@
+# -*- coding: utf-8 -*-
+"""Contain the processing for creating conda environments from the meta.yaml.
+
+cenv is a tool to handle conda-environment creation and update from the
+dependency-definition inside the meta.yaml file.
+
+As default conda has two files for dependency management:
+* the environment.yml
+* and the meta.yaml
+
+In the environment.yml the environment-definition is stored.
+In the meta.yaml the required information to build a conda-package are
+stored. This means redundant information.
+
+cenv collects the dependency-information and all project-specific settings
+from the meta.yaml-file.
+
+The collected information is used to create / update the projects conda
+environment.
+"""
+from argparse import ArgumentParser
+from argparse import Namespace
+from argparse import RawTextHelpFormatter
+from pathlib import Path
+from typing import List
+from typing import NoReturn
+
+import attr
+
+from cenv_tool import __version__
+from cenv_tool.rules import ARGPARSE_DESCRIPTION
+from cenv_tool.rules import CondaCmdFormats
+from cenv_tool.rules import RULES
+from cenv_tool.rules import Rules
+from cenv_tool.utils import CenvProcessError
+from cenv_tool.utils import message
+from cenv_tool.utils import read_config
+from cenv_tool.utils import read_meta_yaml
+from cenv_tool.utils import run_in_bash
+
+
+@attr.s(slots=True, auto_attribs=True)
+class Project:
+ """Contain a python-project using conda-environments.
+
+ Containing methods to display information to current project and methods
+ to update the projects conda-environment from the settings defined in the
+ projects meta.yaml file.
+ """
+
+ rules: Rules
+ conda_folder: Path = attr.ib(default=None)
+ env_folder: Path = attr.ib(default=None)
+ env_name: str = attr.ib(default=None)
+ dependencies: dict = attr.ib(default=None)
+ is_env: bool = attr.ib(default=None)
+ export_environment_yml: bool = attr.ib(None)
+ cmds: CondaCmdFormats = attr.ib(default=None)
+ cmd_kwargs: dict = attr.ib(default=None)
+ is_git: bool = attr.ib(default=None)
+
+ def __attrs_post_init__(self):
+ """Set the more complex attributes of the project class."""
+ try:
+ meta_yaml, dependencies = read_meta_yaml(Path.cwd())
+ settings = meta_yaml['extra']
+ except FileNotFoundError:
+ message(text='project has no meta.yaml!', color='red')
+ exit(1)
+
+ config = read_config()
+ self.is_git = (Path.cwd() / self.rules.git_folder).exists()
+ self.export_environment_yml = config['export_environment_yml']
+ self.conda_folder = Path(config['conda_folder'])
+ self.env_folder = Path(config['env_folder'])
+ self.env_name = settings['env_name']
+ self.dependencies = dependencies
+ self.is_env = self.env_name in self.collect_available_envs()
+ conda_bin = self.rules.conda_cmds.conda_bin(self.conda_folder)
+ self.cmds = self.rules.conda_cmds
+ self.cmd_kwargs = {
+ 'conda': conda_bin,
+ 'name': self.env_name,
+ 'pkgs': ' '.join([f'"{_}"' for _ in self.dependencies]),
+ }
+
+ def collect_available_envs(self) -> List[str]:
+ """Collect the names of the conda-environments currently installed.
+
+ Parameters:
+ conda_folder: the path where conda is installed.
+
+ Returns:
+ List of currently installed conda-environments
+
+ """
+ return run_in_bash(
+ str(self.conda_folder.absolute()) +
+ '/bin/conda env list | awk \'{ if( !($1=="#") ) print $1 }\'',
+ ).split('\n')
+
+ def write_new_md5sum(self):
+ """Write the new md5sum of the meta.yaml to conda-build/meta.md5."""
+ message(text='write md5sum of meta.yaml', color='bold', special='row')
+ command = (
+ 'echo "$(md5sum $PWD/conda-build/meta.yaml)" | '
+ 'cut -d\' \' -f1 > $PWD/conda-build/meta.md5'
+ )
+ run_in_bash(cmd=command)
+ message(text='updated', color='green', special='end', indent=2)
+
+ def export_environment_definition(self) -> NoReturn:
+ """Export the projects environment definition to an environment.yml."""
+ message(text='Export environment.yml ...', color='bold', special='row')
+ run_in_bash(cmd=self.cmds.export.format(**self.cmd_kwargs))
+ message(text='Exported', color='green', special='end', indent=2)
+
+ def remove_backup_environment(self) -> NoReturn:
+ """Remove backup cloned from original environment."""
+ run_in_bash(cmd=self.cmds.clean.format(**self.cmd_kwargs))
+
+ def restore_environment_from_backup(self, cloned: bool) -> NoReturn:
+ """Restore the environment from the cloned backup environment.
+
+ After restore the backup environment is removed.
+
+ Parameters:
+ cloned: indicates if the environment already existed and a backup
+ was created.
+
+ """
+ message(text='Error during creation!', color='red', special='row')
+ if self.is_env and cloned:
+ message(text='Recreating backup', color='bold', special='row')
+ run_in_bash(cmd=self.cmds.restore.format(**self.cmd_kwargs))
+ self.remove_backup_environment()
+ message(text='Recreated', color='green', special='end', indent=2)
+ message(text='Exit', color='red', special='end')
+
+ def remove_previous_environment(self) -> NoReturn:
+ """Remove old version of project environment.
+
+ If the old environment can't be removed, the backup made is removed.
+ """
+ try:
+ message(text='Remove existing env', color='bold', special='row')
+ run_in_bash(cmd=self.cmds.remove.format(**self.cmd_kwargs))
+ message(text='Removed', color='green', special='end', indent=2)
+ except CenvProcessError:
+ self.remove_backup_environment()
+ message(
+ text=(
+ 'Could not remove environment because it is '
+ 'activated! Please deactivate it first.'
+ ),
+ color='red',
+ )
+ exit(1)
+
+ def clone_environment_as_backup(self) -> NoReturn:
+ """Clone the existing environment as a backup.
+
+ If the backup already exists, the previous backup is removed, then
+ the new one is created by cloning the current project environment.
+ """
+ backup_name = f'{self.env_name}_backup'
+ if backup_name in self.collect_available_envs():
+ message(text='Clear old backup', color='bold', special='row')
+ self.remove_backup_environment()
+ message(text='Cleared', color='green', special='end', indent=2)
+ message(text='Create backup', color='bold', special='row')
+ run_in_bash(cmd=self.cmds.clone.format(**self.cmd_kwargs))
+ message(text='Created', color='green', special='end', indent=2)
+
+ def handle_existing_environment(self) -> bool:
+ """Check if environment already exists and create a backup of it."""
+ if self.is_env:
+ self.clone_environment_as_backup()
+ self.remove_previous_environment()
+ return True
+
+ return False
+
+ def create_environment(self, cloned: bool) -> NoReturn:
+ """Create the environment for the project.
+
+ Try to create the environment for the project. If the environment
+ already existed and a backup was made and any error occurs, the backup
+ environment is restored.
+ If everything worked correctly the backup (if made) is finally
+ removed.
+
+ Parameters:
+ cloned: indicates if the environment already existed and a backup
+ was created.
+
+ """
+ message(text='Create environment', color='bold', special='row')
+
+ try:
+ run_in_bash(cmd=self.cmds.create.format(**self.cmd_kwargs))
+ except CenvProcessError:
+ self.restore_environment_from_backup(cloned=cloned)
+ raise
+
+ if cloned:
+ message(text='Clear backup', color='bold', special='row', indent=2)
+ run_in_bash(cmd=self.cmds.clean.format(**self.cmd_kwargs))
+ message(text='Cleared', color='green', special='end', indent=3)
+
+ message(text='Created', color='green', special='end', indent=2)
+
+ def update(self) -> NoReturn:
+ """Create / recreate the conda-environment of the current project.
+
+ If the conda-environment already exists the user is aked for
+ confirmation to continue. Then the environment will be cloned as a
+ backup and the original environment will be removed. Now the new
+ conda-environment will be created. If a backup was created it is
+ removed afterwards. If any errors occurs during creation of the new
+ environment the old environment will be recreated from backup and
+ then the backup will be removed. If activated in the config-file, the
+ environment-definition of the created environment is exported to an
+ environment.yml file. Finally the md5sum of the meta.yaml is stored
+ for the autoupdate feature.
+ """
+ if self.is_env:
+ message(text=f'Updating {self.env_name}', color='cyan')
+ else:
+ message(text=f'Creating {self.env_name}', color='cyan')
+
+ cloned = self.handle_existing_environment()
+
+ self.create_environment(cloned=cloned)
+
+ if self.export_environment_yml:
+ self.export_environment_definition()
+
+ self.write_new_md5sum()
+
+ message(text='Done', color='green', special='end')
+
+
+def build_arguments() -> Namespace:
+ """Create arguments for the cenv-tool.
+
+ Returns:
+ The parsed arguments
+
+ """
+ parser = ArgumentParser(
+ description=ARGPARSE_DESCRIPTION,
+ epilog='For additional information see http://www.cenv.ouroboros.info',
+ formatter_class=RawTextHelpFormatter,
+ )
+ parser.add_argument(
+ '-v',
+ '--version',
+ action='store_true',
+ default=False,
+ help='Show current version of cenv and exit.',
+ )
+ args = parser.parse_args()
+ return args
+
+
+def main() -> NoReturn:
+ """Collect the required args, initializing and running the Project."""
+ args = build_arguments()
+ if args.version:
+ print(__version__)
+ else:
+ Project(rules=RULES).update()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/cenv_tool/rules.py b/cenv_tool/rules.py
new file mode 100644
index 0000000..2f430f3
--- /dev/null
+++ b/cenv_tool/rules.py
@@ -0,0 +1,106 @@
+# -*- coding: utf-8 -*-
+"""Rules-definitions required by cenv."""
+import attr
+
+EXAMPLE_META_YAML = """
+ {% set data = load_setup_py_data() %}
+
+ package:
+ name: "example_package"
+ version: {{ data.get("version") }}
+
+ source:
+ path: ..
+
+ build:
+ build: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }}
+ preserve_egg_dir: True
+ script: python -m pip install --no-deps --ignore-installed .
+
+ requirements:
+ build:
+ - python 3.6.8
+ - pip
+ - setuptools
+ run:
+ - python 3.6.8
+ - attrs >=18.2
+ - marshmallow >=3.0.0rc1*
+
+ test:
+ imports:
+ - example_package
+
+ extra:
+ env_name: example
+ dev_requirements:
+ - ipython >=7.2.0
+ - pylint >=2.3.1
+"""
+
+ARGPARSE_DESCRIPTION = (
+ """
+ Create / update conda environments from meta.yaml definition.
+ Due to the redundant dependency information inside the
+ meta.yaml (required to create conda-package)
+ and the environment.yml (definition for conda environment),
+ cenv (short for `conda-env-manager`) was created to make the
+ meta.yaml the only relevant file for creation and update of
+ conda environments.
+ The name of the conda-environment to create / update is defined
+ in the section "extra" and the variable "env_name" inside the
+ meta.yaml.
+ Dependencies and their versions are extracted from the
+ "requirements-run"-section of the meta.yaml.
+ Dependencies required during development are defined in the
+ "dev_requirements"-section.
+
+ Steps run by cenv:
+ * Cloning existing environment as backup if already exists
+ * Removing existing environment if already exists
+ * Creating environment from definition in meta.yaml
+ * Removing backup environment if everything worked as expected
+ * Exporting env to environment.yml (only if activated in config)'
+
+
+ IMPORTANT:
+ if you do not use the functionalities "autoactivate" and
+ "autoupdate" from cenv.sh you have to deactivate the
+ environment before running cenv.
+
+
+ An example for a valid meta.yaml:
+ """ + EXAMPLE_META_YAML
+)
+
+
+@attr.s(slots=True)
+class CondaCmdFormats:
+ """Contain the formats for the conda commands to use inside cenv."""
+
+ remove = '{conda} remove -n {name} --all -y'
+ export = '{conda} env export -n {name} > conda-build/environment.yml'
+ create = '{conda} create -n {name} {pkgs} -y'
+ clone = '{conda} create -n {name}_backup --clone {name} -y'
+ restore = '{conda} create -n {name} --clone {name}_backup -y'
+ clean = '{conda} remove -n {name}_backup --all -y'
+
+ def conda_bin(self, conda_folder):
+ """Combine the path of conda-folder with subpath of conda-bin.
+
+ Returns:
+ the path to the conda-executable
+
+ """
+ return (conda_folder / 'bin/conda').absolute()
+
+
+@attr.s(slots=True)
+class Rules:
+ """Contain the rules required by cenv-tool."""
+
+ conda_cmds = CondaCmdFormats()
+ git_folder = '.git'
+
+
+RULES = Rules()
diff --git a/cenv_tool/schemata.py b/cenv_tool/schemata.py
new file mode 100644
index 0000000..31d46a3
--- /dev/null
+++ b/cenv_tool/schemata.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+"""Contain schemata required by cenv-tool."""
+from marshmallow import fields
+from marshmallow import Schema
+from marshmallow import validate
+
+
+class SNPackage(Schema):
+ """Contain the package-section inside a meta.yaml."""
+
+ name = fields.String(
+ strict=True,
+ required=True,
+ )
+ version = fields.String(
+ strict=True,
+ required=True,
+ )
+
+
+class SNSource(Schema):
+ """Contain the source-section inside a meta.yaml."""
+
+ path = fields.String(
+ strict=True,
+ required=True,
+ )
+
+
+class SNBuild(Schema):
+ """Contain the build-section inside a meta.yaml.
+
+ Schema for the build-section inside a meta.yaml file, to check this section
+ of a given meta.yaml file in cenv, to be valid.
+ The build-section requires to define the build-number, if the egg-dir
+ should be preserved, the script to run on installation and if any
+ entrypoints are defined for the package.
+ """
+
+ build = fields.String(
+ strict=True,
+ required=True,
+ )
+ preserve_egg_dir = fields.String(
+ strict=True,
+ required=True,
+ validate=validate.OneOf(['True', 'False']),
+ )
+ script = fields.String(
+ strict=True,
+ required=True,
+ )
+ entry_points = fields.List(
+ fields.String(strict=True, required=False),
+ strict=True,
+ required=False,
+ )
+
+
+class SNRequirements(Schema):
+ """Contain requirements-section inside a meta.yaml.
+
+ Schema for the requirements-section inside a meta.yaml file, to check this
+ section of a given meta.yaml file in cenv, to be valid.
+ The underlying build- and run-sections have to be valid!
+ """
+
+ build = fields.List(
+ fields.String(
+ strict=True,
+ required=True,
+ ),
+ strict=True,
+ required=True,
+ )
+ run = fields.List(
+ fields.String(
+ strict=True,
+ required=True,
+ validate=lambda x: '=' in x if 'python ' not in x else True,
+ error_messages=dict(validator_failed='Version must be specified'),
+ ),
+ strict=True,
+ required=True,
+ )
+
+
+class SNTest(Schema):
+ """Contain tests-section inside a meta.yaml.
+
+ Schema for the test-section inside a meta.yaml file, to check this section
+ of a given meta.yaml file in cenv, to be valid.
+ """
+
+ imports = fields.List(
+ fields.String(
+ strict=True,
+ required=False,
+ ),
+ strict=True,
+ required=False,
+ )
+ commands = fields.List(
+ fields.String(
+ strict=True,
+ required=False,
+ ),
+ strict=True,
+ required=False,
+ )
+
+
+class SNExtra(Schema):
+ """Contain the extra-section inside a meta.yaml.
+
+ Schema for the extra-section inside a meta.yaml file, to check this
+ section of a given meta.yaml file in cenv, to be valid.
+ The extra-section has to contains the information where to find the
+ conda-folder, the name of the conda-environment to use for the current
+ project and the cenv-version used when the meta.yaml file was created.
+ """
+
+ env_name = fields.String(
+ strict=True,
+ required=True,
+ validate=lambda x: ' ' not in x,
+ )
+
+ dev_requirements = fields.List(
+ fields.String(
+ strict=True,
+ required=True,
+ validate=lambda x: '=' in x,
+ error_messages=dict(validator_failed='Version must be specified'),
+ ),
+ strict=True,
+ required=False,
+ )
+
+
+class SMetaYaml(Schema):
+ """Contain the representable of a complete meta.yaml file.
+
+ Schema for a meta.yaml file to be used for cenv.
+ Ensures the meta.yaml to load contains the relevant information about
+ the package, source, build, requirements and extra.
+ The test-section is optional.
+ """
+
+ package = fields.Nested(
+ SNPackage,
+ strict=True,
+ required=True,
+ )
+ source = fields.Nested(
+ SNSource,
+ strict=True,
+ required=True,
+ )
+ build = fields.Nested(
+ SNBuild,
+ strict=True,
+ required=True,
+ )
+ requirements = fields.Nested(
+ SNRequirements,
+ strict=True,
+ required=True,
+ )
+ test = fields.Nested(
+ SNTest,
+ strict=True,
+ required=False,
+ )
+ extra = fields.Nested(
+ SNExtra,
+ strict=True,
+ required=True,
+ )
diff --git a/cenv_tool/utils.py b/cenv_tool/utils.py
new file mode 100644
index 0000000..e10cf7c
--- /dev/null
+++ b/cenv_tool/utils.py
@@ -0,0 +1,167 @@
+# -*- coding: utf-8 -*-
+"""Contain utils required by cenv-tool."""
+import os
+from pathlib import Path
+from subprocess import CalledProcessError
+from subprocess import check_output
+from subprocess import STDOUT
+from typing import NoReturn
+
+import jinja2
+import six
+from marshmallow import ValidationError
+from ruamel.yaml import YAML
+
+from cenv_tool.schemata import SMetaYaml
+
+CYAN = '\033[1;36m'
+GREEN = '\033[1;32m'
+RED = '\033[1;91m'
+NCOLOR = '\033[0m'
+BOLD = '\033[1;37m'
+
+
+class CenvProcessError(Exception):
+ """Represent a process error during cenv execution."""
+
+
+def message(
+ *, text: str, color: str, special: str = None, indent: int = 1
+) -> NoReturn:
+ """Print the passed text in the passed color on terminal.
+
+ Parameters:
+ text: The text to print colored on terminal
+
+ """
+ color_mapping = {
+ 'red': RED,
+ 'green': GREEN,
+ 'cyan': CYAN,
+ 'bold': BOLD,
+ }
+ if indent == 1:
+ indent_prefix = ' ' * indent
+ else:
+ indent_prefix = ' ' + '│ ' * (indent - 1)
+ special_mapping = {
+ 'row': f'{indent_prefix}├── ',
+ 'end': f'{indent_prefix}└── ',
+ }
+
+ if special:
+ prefix = special_mapping[special]
+ else:
+ prefix = ''
+ print(f'{prefix}{color_mapping[color]}{text}{NCOLOR}')
+
+
+def run_in_bash(cmd: str) -> str:
+ """Run passed cmd inside bash using the subprocess.check_output-function.
+
+ Parameters:
+ cmd: the command to execute.
+
+ Returns:
+ the output of the ran command.
+
+ """
+ try:
+ result = check_output([cmd], shell=True, stderr=STDOUT)
+ except CalledProcessError:
+ raise CenvProcessError()
+ return result.strip().decode('ascii')
+
+
+class NullUndefined(jinja2.Undefined):
+ """Handle jinja2-variables with undefined content inside the meta.yaml."""
+
+ def __unicode__(self):
+ """Replace uncode dunder of this class."""
+ return six.text_type(self._undefined_name)
+
+ def __getattr__(self, attribute_name: str):
+ """Replace getattr dunder of this class."""
+ return six.text_type(f'{self}.{attribute_name}')
+
+ def __getitem__(self, attribute_name: str):
+ """Replace getitem dunder of this class."""
+ return f'{self}["{attribute_name}"]'
+
+
+class StrDict(dict):
+ """Handle dictionaries for jinja2-variables inside the meta.yaml."""
+
+ def __getitem__(self, key: str, default: str = '') -> str:
+ """Replace getitem dunder of this class."""
+ return self[key] if key in self else default
+
+
+def read_meta_yaml(path: Path) -> dict:
+ """Read the meta.yaml file.
+
+ The file is read from relative path conda-build/meta.yaml inside
+ the current path, validates the meta.yaml using the marshmallow-schema,
+ extracts the dependency-information and the project-settings and returns
+ these information.
+
+ Parameters:
+ path: The current working directory
+
+ Returns:
+ List containing the project-settings as a dict and the dependencies
+ also as a dict
+
+ """
+ # load the meta.yaml-content
+ myaml_content = (path / 'conda-build/meta.yaml').open().read()
+ jinja2_env = jinja2.Environment(undefined=NullUndefined)
+ jinja2_loaded_myaml = jinja2_env.from_string(myaml_content)
+ render_kwargs = {
+ 'os': os,
+ 'environ': StrDict(),
+ 'load_setup_py_data': StrDict,
+ }
+ rendered_myaml = jinja2_loaded_myaml.render(**render_kwargs)
+ loaded_myaml = YAML(typ='base').load(rendered_myaml)
+
+ # validate the content of loaded meta.yaml
+ try:
+ dumped = SMetaYaml(strict=True).dumps(loaded_myaml).data
+ meta_yaml_content = SMetaYaml(strict=True).loads(dumped).data
+ except ValidationError as err:
+ message(text='meta.yaml file is not valid!', color='red')
+ message(text=f'ValidationError in {err.args[0]}', color='red')
+ raise
+
+ # extract the dependencies defined the the requirements-run-section
+ dependencies = meta_yaml_content['requirements']['run']
+ if meta_yaml_content['extra'].get('dev_requirements'):
+ dependencies.extend(meta_yaml_content['extra']['dev_requirements'])
+
+ # combine the collected project-settings and the collected dependencies
+ # to one output of this function
+ return meta_yaml_content, dependencies
+
+
+def read_config():
+ """Read the config file for cenv from the users-home path if it exists.
+
+ If there is no user-config-file the default one is used.
+
+ Returns:
+ the content of the read config file.
+
+ """
+ user_config_path = Path.home() / '.config/cenv/cenv.yml'
+ default_config_path = Path(__file__).parent / 'cenv.yml'
+
+ # Collect settings from config file .cenv.yml
+ main_config = YAML(typ='safe').load(default_config_path.open().read())
+
+ # if a user-config-file exists, read the content and update the main-config
+ if user_config_path.exists():
+ user_config = YAML(typ='safe').load(user_config_path.open().read())
+ main_config.update(user_config)
+
+ return main_config
diff --git a/docs/about.md b/docs/about.md
new file mode 100644
index 0000000..f69e513
--- /dev/null
+++ b/docs/about.md
@@ -0,0 +1,3 @@
+* **Author**: Simon Kallfass
+* **Homepage**: https://www.ouroboros.info
+* **Email**: skallfass@ouroboros.info
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 0000000..7dc99ed
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,19 @@
+`cenv` uses the path `/opt/conda` as default conda-installation-folder
+and `/shared/conda/envs` as default conda-environments-folder.
+
+You can overwrite these settings with a `cenv.yml` at
+`~/.config/cenv/cenv.yml` with the following content:
+
+```yaml
+conda_folder: /opt/conda
+env_folder: /shared/conda/envs
+export_environment_yml: false
+```
+
+There you can define your own conda-installation-path and the
+conda-environments-folder.
+The functionality to export the created / updated environment into
+a `environment.yml` can be activated / deactivated here, too.
+Per default it is deactivated.
+If this is activated, the environment.yml will be placed at
+`conda-build/environment.yml`.
diff --git a/docs/img/coverage.svg b/docs/img/coverage.svg
new file mode 100644
index 0000000..1c7007c
--- /dev/null
+++ b/docs/img/coverage.svg
@@ -0,0 +1,21 @@
+
+
diff --git a/docs/img/logo.png b/docs/img/logo.png
new file mode 100644
index 0000000..9d4447f
Binary files /dev/null and b/docs/img/logo.png differ
diff --git a/docs/impressum.md b/docs/impressum.md
new file mode 100644
index 0000000..ea00793
--- /dev/null
+++ b/docs/impressum.md
@@ -0,0 +1,86 @@
+## Legal Disclosure
+
+Information in accordance with section 5 TMG
+
+* Simon Kallfass
+* Hofäckerstr. 46
+* 76139 Karlsruhe
+
+
+## Contact
+
+* Telephone: +49 177 176 7126
+* E-Mail: skallfass@ouroboros.info
+* Homepage: https://www.ouroboros.info
+
+
+## Disclaimer
+
+Accountability for content
+The contents of our pages have been created with the utmost care. However, we
+cannot guarantee the contents' accuracy, completeness or topicality.
+According to statutory provisions, we are furthermore responsible for our own
+content on these web pages. In this context, please note that we are
+accordingly not obliged to monitor merely the transmitted or saved
+information of third parties, or investigate circumstances pointing to
+illegal activity. Our obligations to remove or block the use of information
+under generally applicable laws remain unaffected by this as per §§ 8 to 10
+of the Telemedia Act (TMG).
+
+
+## Accountability for links
+
+Responsibility for the content of external links (to web pages of third
+parties) lies solely with the operators of the linked pages. No violations
+were evident to us at the time of linking. Should any legal infringement
+become known to us, we will remove the respective link immediately.
+
+
+## Copyright
+
+Our web pages and their contents are subject to German copyright law.
+Unless expressly permitted by law (§ 44a et seq. of the copyright law),
+every form of utilizing, reproducing or processing works subject to copyright
+protection on our web pages requires the prior consent of the respective owner
+of the rights. Individual reproductions of a work are allowed only for
+private use, so must not serve either directly or indirectly for earnings.
+Unauthorized utilization of copyrighted works is punishable
+(§ 106 of the copyright law).
+
+
+# Privacy Statement
+
+
+## General
+
+Your personal data (e.g. title, name, house address, e-mail address,
+phone number, bank details, credit card number) are processed by us only in
+accordance with the provisions of German data privacy laws. The following
+provisions describe the type, scope and purpose of collecting, processing
+and utilizing personal data. This data privacy policy applies only to our
+web pages. If links on our pages route you to other pages, please inquire
+there about how your data are handled in such cases.
+
+
+## Inventory data
+
+1. Your personal data, insofar as these are necessary for this contractual
+ relationship (inventory data) in terms of its establishment, organization
+ of content and modifications, are used exclusively for fulfilling the
+ contract. For goods to be delivered, for instance, your name and address
+ must be relayed to the supplier of the goods.
+2. Without your explicit consent or a legal basis, your personal data are not
+ passed on to third parties outside the scope of fulfilling this contract.
+ After completion of the contract, your data are blocked against further
+ use. After expiry of deadlines as per tax-related and commercial
+ regulations, these data are deleted unless you have expressly consented to
+ their further use.
+
+
+## Disclosure
+
+According to the Federal Data Protection Act, you have a right to
+free-of-charge information about your stored data, and possibly entitlement
+to correction, blocking or deletion of such data. Inquiries can be directed to
+the following e-mail addresses:
+skallfass@ouroboros.info
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..c46975e
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,39 @@
+# conda-env-manager: cenv
+
+![coverage](img/coverage.svg)
+[![PyPI version fury.io](https://badge.fury.io/py/ansicolortags.svg)](https://pypi.python.org/pypi/cenv_tool/)
+[![PyPI pyversions](https://img.shields.io/pypi/pyversions/ansicolortags.svg)](https://pypi.python.org/pypi/cenv_tool/)
+[![MIT license](https://img.shields.io/badge/License-MIT-blue.svg)](https://lbesson.mit-license.org/)
+
+![logo](img/logo.png)
+
+Due to the redundant dependency information inside the `meta.yaml` (required
+to create the conda-package) and the `environment.yml` (as definition file
+for the conda-environment during development and for production), `cenv`
+(short form for `conda-env-manager`) was created to make the `meta.yaml`
+the only relevant file and to create and update conda-environment from the
+definition inside this `meta.yaml`.
+The name of the conda-environment to create / update is defined in the section
+`extra` and the variable `env_name` inside the `meta.yaml` (at
+`conda-build/meta.yaml`).
+
+The steps run by cenv:
+
+* creation of a backup if the environment already exists followed by the
+ removal of the previous environment.
+* creation of the environment as defined in the `meta.yaml`.
+ If any failures occurred during creation and the backup was created, the
+ command to reset the backup-version can be used.
+* if enabled in the config file the environment.yml is exported after creation
+ / update of the environment.
+
+
+The usage of cenv reduces the conda commands to use to the following:
+
+* `conda activate ...` to activate the environment
+* `conda deactivate` to deactivate an environment
+* `conda info` to show information about the currently activated environment
+* `conda search ...` to search for availability of a package in the conda
+ channels.
+* `conda remove -n ... --all` to remove an environment
+* `cenv` to create / update an environment
diff --git a/docs/installation.md b/docs/installation.md
new file mode 100644
index 0000000..09f5747
--- /dev/null
+++ b/docs/installation.md
@@ -0,0 +1,35 @@
+To install cenv simply run:
+```bash
+pip3 install cenv_tool
+```
+
+Now run `init_cenv` to create the relevant config-files and add the
+autoactivate- and autoupdate-shell-function to your `.bashrc` / `.zshrc`.
+
+
+# autoactivate and autoupdate
+
+Per default these features are deactivated, even if added to your shell by
+running `init_cenv`.
+
+
+## autoactivate-feature
+
+The autoactivate-feature activates the conda-environment as named
+`extra`-section in the meta.yaml located at `conda-build/meta.yaml`, if the
+environment exists.
+To activate the autoactivate-features run:
+```bash
+autoactivate_toggle
+```
+
+## autoupdate-feature
+
+The autoupdate checks if the content of the meta.yaml changed.
+The current state is stored as a md5sum in `conda-build/meta.md5`.
+If it changed the cenv-process is called.
+
+For the autoupdate-feature run:
+```bash
+autoupdate_toggle
+```
diff --git a/docs/license.md b/docs/license.md
new file mode 100644
index 0000000..268f2a9
--- /dev/null
+++ b/docs/license.md
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Simon Kallfass
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/docs/usage.md b/docs/usage.md
new file mode 100644
index 0000000..5ac1ff5
--- /dev/null
+++ b/docs/usage.md
@@ -0,0 +1,110 @@
+All steps required to create or update the projects conda environment are
+run automatically running:
+```bash
+cenv
+```
+
+**ATTENTION**:
+> If you use cenv, each environment should only be created, updated and
+> modified using `cenv`!
+> This means the commands `conda install`, `conda remove` are not used
+> anymore.
+> Changes of the dependencies of the environment are defined inside the
+> `meta.yaml` and are applied by using `cenv`.
+>
+> This means:
+>
+> * new dependency required => add it in `meta.yaml` and run `cenv`.
+> * dependency not needed anymore => remove it from `meta.yaml` and run
+> `cenv`.
+> * need of another version of dependency => change the version of dependency
+> in `meta.yaml` and run `cenv`.
+
+The required information about the projects conda environment are extracted
+from the meta.yaml.
+This meta.yaml should be located inside the project folder at
+`./conda-build/meta.yaml`.
+The project-configuration is defined in the `extra` section of the `meta.yaml`.
+There you can define the name of the projects conda-environment at
+`env_name`.
+Also you can define requirements only needed during development but not to be
+included into the resulting conda package.
+These requirements have to be defined in the `dev_requirements`-section.
+
+All other parts of the `meta.yaml` have to be defined as default.
+
+A meta.yaml valid for cenv should look like the following:
+```yaml
+ {% set data = load_setup_py_data() %}
+
+ package:
+ name: "example_package"
+ version: {{ data.get("version") }}
+
+ source:
+ path: ..
+
+ build:
+ build: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }}
+ preserve_egg_dir: True
+ script: python -m pip install --no-deps --ignore-installed .
+
+ requirements:
+ build:
+ - python 3.6.8
+ - pip
+ - setuptools
+ run:
+ - python 3.6.8
+ - attrs >=18.2
+ - jinja2 >=2.10
+ - ruamel.yaml >=0.15.23
+ - six >=1.12.0
+ - yaml >=0.1.7
+ - marshmallow >=3.0.0rc1*
+
+ test:
+ imports:
+ - example_package
+
+ extra:
+ env_name: example
+ dev_requirements:
+ - ipython >=7
+```
+
+**ATTENTION**:
+> In the `requirements-run-section` the minimal version of each package
+> has to be defined!
+> The same is required for the `dev_requirements`-section.
+> Not defining a version will not create or update a conda-environment,
+> because this is not the purpose of the conda-usage.
+> The validity of the `meta.yaml` is checked in `cenv` using the
+> `marshmallow` package.
+> You can additionally add upper limits for the version like the following:
+> `- package >=0.1,<0.3`
+
+If cenv is run the environment is created / updated from the definition inside
+this `meta.yaml`.
+The creation of the backup of the previous environment ensures to undo changes
+if any error occurs during recreation of the environment.
+
+
+**ATTENTION**:
+> `cenv` can only update the environment if it is not activated.
+> So ensure the environment to be deactivated before running `cenv`.
+
+Per default exporting the conda environment definition into an environment.yml
+is turned off.
+If you want to turn this functionality on you need to modify your
+`~/.config/cenv.yml` as described in [configuration](configuration.md).
+
+Example for the output of the `cenv` command:
+
+```bash
+ ┣━━ Cloning existing env as backup ...
+ ┣━━ Removing existing env ...
+ ┣━━ Creating env ...
+ ┣━━ Removing backup ...
+ ┗━━ Exporting env to environment.yml ...
+```
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..7049af5
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,13 @@
+site_name: 'conda-env-manager: cenv'
+nav:
+ - Home: index.md
+ - Installation: installation.md
+ - Configuration: configuration.md
+ - Usage: usage.md
+ - About: about.md
+ - License: license.md
+ - Impressum: impressum.md
+theme:
+ name: readthedocs
+ hljs_languages:
+ - yaml
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..644af77
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,563 @@
+[[package]]
+category = "dev"
+description = "Disable App Nap on OS X 10.9"
+marker = "sys_platform == \"darwin\""
+name = "appnope"
+optional = false
+python-versions = "*"
+version = "0.1.0"
+
+[[package]]
+category = "dev"
+description = "An abstract syntax tree for Python with inference support."
+name = "astroid"
+optional = false
+python-versions = ">=3.4.*"
+version = "2.2.5"
+
+[package.dependencies]
+lazy-object-proxy = "*"
+six = "*"
+typed-ast = ">=1.3.0"
+wrapt = "*"
+
+[[package]]
+category = "dev"
+description = "Atomic file writes."
+name = "atomicwrites"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.3.0"
+
+[[package]]
+category = "main"
+description = "Classes Without Boilerplate"
+name = "attrs"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "19.1.0"
+
+[[package]]
+category = "dev"
+description = "Specifications for callback functions passed in to an API"
+name = "backcall"
+optional = false
+python-versions = "*"
+version = "0.1.0"
+
+[[package]]
+category = "dev"
+description = "Composable command line interface toolkit"
+name = "click"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "7.0"
+
+[[package]]
+category = "dev"
+description = "Cross-platform colored terminal text."
+marker = "sys_platform == \"win32\""
+name = "colorama"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.4.1"
+
+[[package]]
+category = "dev"
+description = "Code coverage measurement for Python"
+name = "coverage"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4"
+version = "4.5.4"
+
+[[package]]
+category = "dev"
+description = "Generate coverage badges for Coverage.py."
+name = "coverage-badge"
+optional = false
+python-versions = "*"
+version = "1.0.1"
+
+[[package]]
+category = "dev"
+description = "Better living through Python with decorators"
+name = "decorator"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*"
+version = "4.4.0"
+
+[[package]]
+category = "dev"
+description = "Read metadata from Python packages"
+name = "importlib-metadata"
+optional = false
+python-versions = ">=2.7,!=3.0,!=3.1,!=3.2,!=3.3"
+version = "0.19"
+
+[package.dependencies]
+zipp = ">=0.5"
+
+[[package]]
+category = "dev"
+description = "IPython: Productive Interactive Computing"
+name = "ipython"
+optional = false
+python-versions = ">=3.5"
+version = "7.7.0"
+
+[package.dependencies]
+appnope = "*"
+backcall = "*"
+colorama = "*"
+decorator = "*"
+jedi = ">=0.10"
+pexpect = "*"
+pickleshare = "*"
+prompt-toolkit = ">=2.0.0,<2.1.0"
+pygments = "*"
+setuptools = ">=18.5"
+traitlets = ">=4.2"
+
+[[package]]
+category = "dev"
+description = "Vestigial utilities from IPython"
+name = "ipython-genutils"
+optional = false
+python-versions = "*"
+version = "0.2.0"
+
+[[package]]
+category = "dev"
+description = "A Python utility / library to sort Python imports."
+name = "isort"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "4.3.21"
+
+[[package]]
+category = "dev"
+description = "An autocompletion tool for Python that can be used for text editors."
+name = "jedi"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.14.1"
+
+[package.dependencies]
+parso = ">=0.5.0"
+
+[[package]]
+category = "main"
+description = "A small but fast and easy to use stand-alone template engine written in pure python."
+name = "jinja2"
+optional = false
+python-versions = "*"
+version = "2.10.1"
+
+[package.dependencies]
+MarkupSafe = ">=0.23"
+
+[[package]]
+category = "dev"
+description = "A fast and thorough lazy object proxy."
+name = "lazy-object-proxy"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.4.1"
+
+[[package]]
+category = "dev"
+description = "Python LiveReload is an awesome tool for web developers"
+name = "livereload"
+optional = false
+python-versions = "*"
+version = "2.6.1"
+
+[package.dependencies]
+six = "*"
+tornado = "*"
+
+[[package]]
+category = "dev"
+description = "Python implementation of Markdown."
+name = "markdown"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
+version = "3.1.1"
+
+[package.dependencies]
+setuptools = ">=36"
+
+[[package]]
+category = "main"
+description = "Safely add untrusted strings to HTML/XML markup."
+name = "markupsafe"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
+version = "1.1.1"
+
+[[package]]
+category = "main"
+description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
+name = "marshmallow"
+optional = false
+python-versions = "*"
+version = "2.19.5"
+
+[[package]]
+category = "dev"
+description = "McCabe checker, plugin for flake8"
+name = "mccabe"
+optional = false
+python-versions = "*"
+version = "0.6.1"
+
+[[package]]
+category = "dev"
+description = "Project documentation with Markdown."
+name = "mkdocs"
+optional = false
+python-versions = ">=2.7.9,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
+version = "1.0.4"
+
+[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 = "Generating type annotations from sampled production types"
+name = "monkeytype"
+optional = false
+python-versions = ">=3.6"
+version = "19.5.0"
+
+[package.dependencies]
+retype = "*"
+
+[[package]]
+category = "dev"
+description = "More routines for operating on iterables, beyond itertools"
+name = "more-itertools"
+optional = false
+python-versions = ">=3.4"
+version = "7.2.0"
+
+[[package]]
+category = "dev"
+description = "A Python Parser"
+name = "parso"
+optional = false
+python-versions = "*"
+version = "0.5.1"
+
+[[package]]
+category = "dev"
+description = "Pexpect allows easy control of interactive console applications."
+marker = "sys_platform != \"win32\""
+name = "pexpect"
+optional = false
+python-versions = "*"
+version = "4.7.0"
+
+[package.dependencies]
+ptyprocess = ">=0.5"
+
+[[package]]
+category = "dev"
+description = "Tiny 'shelve'-like database with concurrency support"
+name = "pickleshare"
+optional = false
+python-versions = "*"
+version = "0.7.5"
+
+[[package]]
+category = "dev"
+description = "plugin and hook calling mechanisms for python"
+name = "pluggy"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "0.12.0"
+
+[package.dependencies]
+importlib-metadata = ">=0.12"
+
+[[package]]
+category = "dev"
+description = "Library for building powerful interactive command lines in Python"
+name = "prompt-toolkit"
+optional = false
+python-versions = "*"
+version = "2.0.9"
+
+[package.dependencies]
+six = ">=1.9.0"
+wcwidth = "*"
+
+[[package]]
+category = "dev"
+description = "Run a subprocess in a pseudo terminal"
+marker = "sys_platform != \"win32\""
+name = "ptyprocess"
+optional = false
+python-versions = "*"
+version = "0.6.0"
+
+[[package]]
+category = "dev"
+description = "library with cross-python path, ini-parsing, io, code, log facilities"
+name = "py"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "1.8.0"
+
+[[package]]
+category = "dev"
+description = "Create Python API documentation in Markdown format"
+name = "pydoc-markdown"
+optional = false
+python-versions = "*"
+version = "2.0.5"
+
+[package.dependencies]
+Markdown = ">=2.6.11"
+MkDocs = ">=0.16.0"
+PyYAML = ">=3.12"
+six = ">=0.11.0"
+
+[[package]]
+category = "dev"
+description = "Pygments is a syntax highlighting package written in Python."
+name = "pygments"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+version = "2.4.2"
+
+[[package]]
+category = "dev"
+description = "python code static checker"
+name = "pylint"
+optional = false
+python-versions = ">=3.4.*"
+version = "2.3.1"
+
+[package.dependencies]
+astroid = ">=2.2.0,<3"
+colorama = "*"
+isort = ">=4.2.5,<5"
+mccabe = ">=0.6,<0.7"
+
+[[package]]
+category = "dev"
+description = "pytest: simple powerful testing with Python"
+name = "pytest"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "3.10.1"
+
+[package.dependencies]
+atomicwrites = ">=1.0"
+attrs = ">=17.4.0"
+colorama = "*"
+more-itertools = ">=4.0.0"
+pluggy = ">=0.7"
+py = ">=1.5.0"
+setuptools = "*"
+six = ">=1.10.0"
+
+[[package]]
+category = "dev"
+description = "Pytest plugin for measuring coverage."
+name = "pytest-cov"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "2.7.1"
+
+[package.dependencies]
+coverage = ">=4.4"
+pytest = ">=3.6"
+
+[[package]]
+category = "dev"
+description = "py.test plugin to create a 'tmpdir' containing predefined files/directories."
+name = "pytest-datafiles"
+optional = false
+python-versions = "*"
+version = "2.0"
+
+[package.dependencies]
+py = "*"
+pytest = ">=3.6"
+
+[[package]]
+category = "dev"
+description = "YAML parser and emitter for Python"
+name = "pyyaml"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+version = "5.1.2"
+
+[[package]]
+category = "dev"
+description = "Re-apply types from .pyi stub files to your codebase."
+name = "retype"
+optional = false
+python-versions = "*"
+version = "17.12.0"
+
+[package.dependencies]
+click = "*"
+typed-ast = "*"
+
+[[package]]
+category = "main"
+description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order"
+name = "ruamel.yaml"
+optional = false
+python-versions = "*"
+version = "0.16.1"
+
+[package.dependencies]
+[package.dependencies."ruamel.yaml.clib"]
+python = "<3.8"
+version = ">=0.1.2"
+
+[[package]]
+category = "main"
+description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml"
+marker = "platform_python_implementation == \"CPython\" and python_version < \"3.8\""
+name = "ruamel.yaml.clib"
+optional = false
+python-versions = "*"
+version = "0.1.2"
+
+[[package]]
+category = "main"
+description = "Python 2 and 3 compatibility utilities"
+name = "six"
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*"
+version = "1.12.0"
+
+[[package]]
+category = "dev"
+description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
+name = "tornado"
+optional = false
+python-versions = ">= 3.5"
+version = "6.0.3"
+
+[[package]]
+category = "dev"
+description = "Traitlets Python config system"
+name = "traitlets"
+optional = false
+python-versions = "*"
+version = "4.3.2"
+
+[package.dependencies]
+decorator = "*"
+ipython-genutils = "*"
+six = "*"
+
+[[package]]
+category = "dev"
+description = "a fork of Python 2 and 3 ast modules with type comment support"
+name = "typed-ast"
+optional = false
+python-versions = "*"
+version = "1.4.0"
+
+[[package]]
+category = "dev"
+description = "Measures number of Terminal column cells of wide-character codes"
+name = "wcwidth"
+optional = false
+python-versions = "*"
+version = "0.1.7"
+
+[[package]]
+category = "dev"
+description = "Module for decorators, wrappers and monkey patching."
+name = "wrapt"
+optional = false
+python-versions = "*"
+version = "1.11.2"
+
+[[package]]
+category = "dev"
+description = "A formatter for Python code."
+name = "yapf"
+optional = false
+python-versions = "*"
+version = "0.28.0"
+
+[[package]]
+category = "dev"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+name = "zipp"
+optional = false
+python-versions = ">=2.7"
+version = "0.5.2"
+
+[extras]
+docs = []
+
+[metadata]
+content-hash = "e22c73191a4da9993606ee107a024cdfb23a4a67294465ee622ee2d2ee961feb"
+python-versions = "^3.7"
+
+[metadata.hashes]
+appnope = ["5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", "8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71"]
+astroid = ["6560e1e1749f68c64a4b5dee4e091fce798d2f0d84ebe638cf0e0585a343acf4", "b65db1bbaac9f9f4d190199bb8680af6f6f84fd3769a5ea883df8a91fe68b4c4"]
+atomicwrites = ["03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", "75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"]
+attrs = ["69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", "f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399"]
+backcall = ["38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", "bbbf4b1e5cd2bdb08f915895b51081c041bac22394fdfcfdfbe9f14b77c08bf2"]
+click = ["2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"]
+colorama = ["05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d", "f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48"]
+coverage = ["08907593569fe59baca0bf152c43f3863201efb6113ecb38ce7e97ce339805a6", "0be0f1ed45fc0c185cfd4ecc19a1d6532d72f86a2bac9de7e24541febad72650", "141f08ed3c4b1847015e2cd62ec06d35e67a3ac185c26f7635f4406b90afa9c5", "19e4df788a0581238e9390c85a7a09af39c7b539b29f25c89209e6c3e371270d", "23cc09ed395b03424d1ae30dcc292615c1372bfba7141eb85e11e50efaa6b351", "245388cda02af78276b479f299bbf3783ef0a6a6273037d7c60dc73b8d8d7755", "331cb5115673a20fb131dadd22f5bcaf7677ef758741312bee4937d71a14b2ef", "386e2e4090f0bc5df274e720105c342263423e77ee8826002dcffe0c9533dbca", "3a794ce50daee01c74a494919d5ebdc23d58873747fa0e288318728533a3e1ca", "60851187677b24c6085248f0a0b9b98d49cba7ecc7ec60ba6b9d2e5574ac1ee9", "63a9a5fc43b58735f65ed63d2cf43508f462dc49857da70b8980ad78d41d52fc", "6b62544bb68106e3f00b21c8930e83e584fdca005d4fffd29bb39fb3ffa03cb5", "6ba744056423ef8d450cf627289166da65903885272055fb4b5e113137cfa14f", "7494b0b0274c5072bddbfd5b4a6c6f18fbbe1ab1d22a41e99cd2d00c8f96ecfe", "826f32b9547c8091679ff292a82aca9c7b9650f9fda3e2ca6bf2ac905b7ce888", "93715dffbcd0678057f947f496484e906bf9509f5c1c38fc9ba3922893cda5f5", "9a334d6c83dfeadae576b4d633a71620d40d1c379129d587faa42ee3e2a85cce", "af7ed8a8aa6957aac47b4268631fa1df984643f07ef00acd374e456364b373f5", "bf0a7aed7f5521c7ca67febd57db473af4762b9622254291fbcbb8cd0ba5e33e", "bf1ef9eb901113a9805287e090452c05547578eaab1b62e4ad456fcc049a9b7e", "c0afd27bc0e307a1ffc04ca5ec010a290e49e3afbe841c5cafc5c5a80ecd81c9", "dd579709a87092c6dbee09d1b7cfa81831040705ffa12a1b248935274aee0437", "df6712284b2e44a065097846488f66840445eb987eb81b3cc6e4149e7b6982e1", "e07d9f1a23e9e93ab5c62902833bf3e4b1f65502927379148b6622686223125c", "e2ede7c1d45e65e209d6093b762e98e8318ddeff95317d07a27a2140b80cfd24", "e4ef9c164eb55123c62411f5936b5c2e521b12356037b6e1c2617cef45523d47", "eca2b7343524e7ba246cab8ff00cab47a2d6d54ada3b02772e908a45675722e2", "eee64c616adeff7db37cc37da4180a3a5b6177f5c46b187894e633f088fb5b28", "ef824cad1f980d27f26166f86856efe11eff9912c4fed97d3804820d43fa550c", "efc89291bd5a08855829a3c522df16d856455297cf35ae827a37edac45f466a7", "fa964bae817babece5aa2e8c1af841bebb6d0b9add8e637548809d040443fee0", "ff37757e068ae606659c28c3bd0d923f9d29a85de79bf25b2b34b148473b5025"]
+coverage-badge = ["142fd121f3bd14956aff3c45bff6f8bc37bd74c6350626a950ebb6accb24276e", "3796de21b4e190d38beb8806956946fbdb02fe3a2a7452b460a9cff958009833"]
+decorator = ["86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de", "f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"]
+importlib-metadata = ["23d3d873e008a513952355379d93cbcab874c58f4f034ff657c7a87422fa64e8", "80d2de76188eabfbfcf27e6a37342c2827801e59c4cc14b0371c56fed43820e3"]
+ipython = ["1d3a1692921e932751bc1a1f7bb96dc38671eeefdc66ed33ee4cbc57e92a410e", "537cd0176ff6abd06ef3e23f2d0c4c2c8a4d9277b7451544c6cbf56d1c79a83d"]
+ipython-genutils = ["72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", "eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"]
+isort = ["54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"]
+jedi = ["53c850f1a7d3cfcd306cc513e2450a54bdf5cacd7604b74e42dd1f0758eaaf36", "e07457174ef7cb2342ff94fa56484fe41cec7ef69b0059f01d3f812379cb6f7c"]
+jinja2 = ["065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", "14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b"]
+lazy-object-proxy = ["159a745e61422217881c4de71f9eafd9d703b93af95618635849fe469a283661", "23f63c0821cc96a23332e45dfaa83266feff8adc72b9bcaef86c202af765244f", "3b11be575475db2e8a6e11215f5aa95b9ec14de658628776e10d96fa0b4dac13", "3f447aff8bc61ca8b42b73304f6a44fa0d915487de144652816f950a3f1ab821", "4ba73f6089cd9b9478bc0a4fa807b47dbdb8fad1d8f31a0f0a5dbf26a4527a71", "4f53eadd9932055eac465bd3ca1bd610e4d7141e1278012bd1f28646aebc1d0e", "64483bd7154580158ea90de5b8e5e6fc29a16a9b4db24f10193f0c1ae3f9d1ea", "6f72d42b0d04bfee2397aa1862262654b56922c20a9bb66bb76b6f0e5e4f9229", "7c7f1ec07b227bdc561299fa2328e85000f90179a2f44ea30579d38e037cb3d4", "7c8b1ba1e15c10b13cad4171cfa77f5bb5ec2580abc5a353907780805ebe158e", "8559b94b823f85342e10d3d9ca4ba5478168e1ac5658a8a2f18c991ba9c52c20", "a262c7dfb046f00e12a2bdd1bafaed2408114a89ac414b0af8755c696eb3fc16", "acce4e3267610c4fdb6632b3886fe3f2f7dd641158a843cf6b6a68e4ce81477b", "be089bb6b83fac7f29d357b2dc4cf2b8eb8d98fe9d9ff89f9ea6012970a853c7", "bfab710d859c779f273cc48fb86af38d6e9210f38287df0069a63e40b45a2f5c", "c10d29019927301d524a22ced72706380de7cfc50f767217485a912b4c8bd82a", "dd6e2b598849b3d7aee2295ac765a578879830fb8966f70be8cd472e6069932e", "e408f1eacc0a68fed0c08da45f31d0ebb38079f043328dce69ff133b95c29dc1"]
+livereload = ["78d55f2c268a8823ba499305dcac64e28ddeb9a92571e12d543cd304faf5817b", "89254f78d7529d7ea0a3417d224c34287ebfe266b05e67e51facaf82c27f0f66"]
+markdown = ["2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a", "56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c"]
+markupsafe = ["00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", "09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", "09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", "1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", "24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", "29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", "43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", "46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", "500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", "535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", "62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", "6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", "717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", "79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", "7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", "88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", "8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", "98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", "9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", "9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", "ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", "b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", "b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", "b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", "ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", "c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", "cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", "e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"]
+marshmallow = ["9cedfc5b6f568d57e8a2cf3d293fbd81b05e5ef557854008d03e25660a39ccfd", "a4d99922116a76e5abd8f997ec0519086e24814b7e1e1344bebe2a312ba50235"]
+mccabe = ["ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", "dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"]
+mkdocs = ["17d34329aad75d5de604b9ed4e31df3a4d235afefdc46ce7b1964fddb2e1e939", "8cc8b38325456b9e942c981a209eaeb1e9f3f77b493ad755bfef889b9c8d356a"]
+monkeytype = ["67c8a5694fbc78b3c763eccca834bcbc0a7964969fa467f78e43a4141d650787", "adb86d4dd4760e80e9670179ab09b09b4e3765115e31f9f2840c1ef3ebc91167"]
+more-itertools = ["409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4"]
+parso = ["63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", "666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c"]
+pexpect = ["2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", "9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb"]
+pickleshare = ["87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", "9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"]
+pluggy = ["0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", "b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c"]
+prompt-toolkit = ["11adf3389a996a6d45cc277580d0d53e8a5afd281d0c9ec71b28e6f121463780", "2519ad1d8038fd5fc8e770362237ad0364d16a7650fb5724af6997ed5515e3c1", "977c6583ae813a37dc1c2e1b715892461fcbdaa57f6fc62f33a528c4886c8f55"]
+ptyprocess = ["923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0", "d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"]
+py = ["64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53"]
+pydoc-markdown = ["f1511ad5a6ca33a52dd2010651d18e9debb57bcf36ab87eca560dd15a753ce1f"]
+pygments = ["71e430bc85c88a430f000ac1d9b331d2407f681d6f6aec95e8bcfbc3df5b0127", "881c4c157e45f30af185c1ffe8d549d48ac9127433f2c380c24b84572ad66297"]
+pylint = ["5d77031694a5fb97ea95e828c8d10fc770a1df6eb3906067aaed42201a8a6a09", "723e3db49555abaf9bf79dc474c6b9e2935ad82230b10c1138a71ea41ac0fff1"]
+pytest = ["3f193df1cfe1d1609d4c583838bea3d532b18d6160fd3f55c9447fdca30848ec", "e246cf173c01169b9617fc07264b7b1316e78d7a650055235d6d897bc80d9660"]
+pytest-cov = ["2b097cde81a302e1047331b48cadacf23577e431b61e9c6f49a1170bbe3d3da6", "e00ea4fdde970725482f1f35630d12f074e121a23801aabf2ae154ec6bdd343a"]
+pytest-datafiles = ["143329cbb1dbbb07af24f88fa4668e2f59ce233696cf12c49fd1c98d1756dbf9", "e349b6ad7bcca111f3677b7201d3ca81f93b5e09dcfae8ee2be2c3cae9f55bc7"]
+pyyaml = ["0113bc0ec2ad727182326b61326afa3d1d8280ae1122493553fd6f4397f33df9", "01adf0b6c6f61bd11af6e10ca52b7d4057dd0be0343eb9283c878cf3af56aee4", "5124373960b0b3f4aa7df1707e63e9f109b5263eca5976c66e08b1c552d4eaf8", "5ca4f10adbddae56d824b2c09668e91219bb178a1eee1faa56af6f99f11bf696", "7907be34ffa3c5a32b60b95f4d95ea25361c951383a894fec31be7252b2b6f34", "7ec9b2a4ed5cad025c2278a1e6a19c011c80a3caaac804fd2d329e9cc2c287c9", "87ae4c829bb25b9fe99cf71fbb2140c448f534e24c998cc60f39ae4f94396a73", "9de9919becc9cc2ff03637872a440195ac4241c80536632fffeb6a1e25a74299", "a5a85b10e450c66b49f98846937e8cfca1db3127a9d5d1e31ca45c3d0bef4c5b", "b0997827b4f6a7c286c01c5f60384d218dca4ed7d9efa945c3e1aa623d5709ae", "b631ef96d3222e62861443cc89d6563ba3eeb816eeb96b2629345ab795e53681", "bf47c0607522fdbca6c9e817a6e81b08491de50f3766a7a0e6a5be7905961b41", "f81025eddd0327c7d4cfe9b62cf33190e1e736cc6e97502b3ec425f574b3e7a8"]
+retype = ["33cfb36601bfeb355924731d8db78fa82f3f12eb37e87236e9179d81aba97740", "b64b767befbe6f5fd918603ab7d6bbff07fc4c431bae2f471e195677a0c9b327"]
+"ruamel.yaml" = ["547aeab5c51c93bc750ed2a320c1559b605bde3aa569216aa75fd91d8a1c4623"]
+"ruamel.yaml.clib" = ["0bbe19d3e099f8ba384e1846e6b54f245f58aeec8700edbbf9abb87afa54fd82", "2f38024592613f3a8772bbc2904be027d9abf463518ba145f2d0c8e6da27009f", "44449b3764a3f75815eea8ae5930b98e8326be64a90b0f782747318f861abfe0", "5710be9a357801c31c1eaa37b9bc92d38176d785af5b2f0c9751385c5dc9659a", "5a089acb6833ed5f412e24cbe3e665683064c1429824d2819137b5ade54435c3", "6143386ddd61599ea081c012a69a16e5bdd7b3c6c231bd039534365a48940f30", "6726aaf851f5f9e4cbdd3e1e414bc700bdd39220e8bc386415fd41c87b1b53c2", "68fbc3b5d94d145a391452f886ae5fca240cb7e3ab6bd66e1a721507cdaac28a", "75ebddf99ba9e0b48f32b5bdcf9e5a2b84c017da9e0db7bf11995fa414aa09cd", "79948a6712baa686773a43906728e20932c923f7b2a91be7347993be2d745e55", "8a2dd8e8b08d369558cade05731172c4b5e2f4c5097762c6b352bd28fd9f9dc4", "c747acdb5e8c242ab2280df6f0c239e62838af4bee647031d96b3db2f9cefc04", "cadc8eecd27414dca30366b2535cb5e3f3b47b4e2d6be7a0b13e4e52e459ff9f", "cee86ecc893a6a8ecaa7c6a9c2d06f75f614176210d78a5f155f8e78d6989509", "e59af39e895aff28ee5f55515983cab3466d1a029c91c04db29da1c0f09cf333", "eee7ecd2eee648884fae6c51ae50c814acdcc5d6340dc96c970158aebcd25ac6", "ef8d4522d231cb9b29f6cdd0edc8faac9d9715c60dc7becbd6eb82c915a98e5b", "f504d45230cc9abf2810623b924ae048b224a90adb01f97db4e766cfdda8e6eb"]
+six = ["3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", "d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"]
+tornado = ["349884248c36801afa19e342a77cc4458caca694b0eda633f5878e458a44cb2c", "398e0d35e086ba38a0427c3b37f4337327231942e731edaa6e9fd1865bbd6f60", "4e73ef678b1a859f0cb29e1d895526a20ea64b5ffd510a2307b5998c7df24281", "559bce3d31484b665259f50cd94c5c28b961b09315ccd838f284687245f416e5", "abbe53a39734ef4aba061fca54e30c6b4639d3e1f59653f0da37a0003de148c7", "c845db36ba616912074c5b1ee897f8e0124df269468f25e4fe21fe72f6edd7a9", "c9399267c926a4e7c418baa5cbe91c7d1cf362d505a1ef898fde44a07c9dd8a5"]
+traitlets = ["9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", "c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9"]
+typed-ast = ["18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12"]
+wcwidth = ["3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c"]
+wrapt = ["565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"]
+yapf = ["02ace10a00fa2e36c7ebd1df2ead91dbfbd7989686dc4ccbdc549e95d19f5780", "6f94b6a176a7c114cfa6bad86d40f259bbe0f10cf2fa7f2f4b3596fc5802a41b"]
+zipp = ["4970c3758f4e89a7857a973b1e2a5d75bcdc47794442f2e2dd4fe8e0466e809a", "8a5712cfd3bb4248015eb3b0b3c54a5f6ee3f2425963ef2a0125b8bc40aafaec"]
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..7552553
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,69 @@
+[tool.poetry]
+name = "cenv_tool"
+version = "1.0.0"
+description = "conda environment creation and update from meta.yaml"
+license = "MIT"
+authors = ["Simon Kallfass "]
+readme = "README.md"
+homepage = "https://www.cenv.ouroboros.info"
+keywords = ["conda", "environment", "dependencies"]
+
+
+[tool.poetry.dependencies]
+python = "^3.7"
+attrs = "~19"
+jinja2 = ">=2"
+"ruamel.yaml" = ">=0.15"
+six = ">=1.12"
+marshmallow = ">=2.19,<3"
+
+
+[tool.poetry.dev-dependencies]
+coverage-badge = "~1"
+ipython = ">=7"
+mkdocs = "~1"
+monkeytype = ">=19"
+pydoc-markdown = "~2"
+pylint = ">=2"
+pytest = "^3.0"
+pytest-cov = '~2'
+pytest-datafiles = '~2'
+yapf = ">=0"
+
+
+[tool.poetry.scripts]
+cenv = "cenv_tool.project:main"
+init_cenv = "cenv_tool.init_cenv:main"
+
+
+[build-system]
+requires = ["poetry>=0.12"]
+build-backend = "poetry.masonry.api"
+
+
+[tool.poetry.extras]
+docs = ["mkdocs", "pydoc-markdown"]
+
+
+[tool.dephell.main]
+# read from poetry format
+from = {format = "poetry", path = "pyproject.toml"}
+# drop dev-dependencies
+envs = ["main"]
+# and convert into setup.py
+to = {format = "setuppy", path = "setup.py"}
+versioning = "semver"
+
+
+[tool.dephell.lock]
+from = {format = "poetry", path = "pyproject.toml"}
+to = {format = "poetrylock", path = "poetry.lock"}
+
+
+[tool.dephell.docs]
+# read dependencies from poetry format
+from = {format = "poetry", path = "pyproject.toml"}
+# install only `docs` extra dependencies
+envs = ["docs"]
+# run this command:
+command = "mkdocs build"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..01fea58
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,61 @@
+[tool:pytest]
+addopts = --cov=cenv_tool
+ --cov-report html
+ --cov-report term-missing:skip-covered
+ --cov-config=setup.cfg
+
+
+[coverage:report]
+exclude_lines =
+ pragma: no cover
+ def __repr__
+ if __name__ == .__main__.:
+ def main
+ def script_options
+
+[pydocstyle]
+convention = google
+
+[flake8]
+max-line-length = 100
+
+[yapf]
+align_closing_bracket_with_visual_indent=False
+allow_multiline_dictionary_keys=False
+allow_multiline_lambdas=False
+allow_split_before_default_or_named_assigns=True
+allow_split_before_dict_value=True
+arithmetic_precedence_indication=False
+blank_lines_around_top_level_definition=2
+blank_line_before_class_docstring=False
+blank_line_before_module_docstring=False
+blank_line_before_nested_class_or_def=True
+coalesce_brackets=False
+column_limit=80
+continuation_align_style=SPACE
+continuation_indent_width=4
+dedent_closing_brackets=True
+disable_ending_comma_heuristic=False
+each_dict_entry_on_separate_line=True
+i18n_comment=#\..*
+i18n_function_call=N_, _
+indent_blank_lines=False
+indent_dictionary_value=False
+indent_width=4
+join_multiple_lines=True
+spaces_around_default_or_named_assign=False
+spaces_around_power_operator=False
+spaces_before_comment=2
+space_between_ending_comma_and_closing_bracket=False
+split_all_comma_separated_values=False
+split_arguments_when_comma_terminated=False
+split_before_arithmetic_operator=False
+split_before_bitwise_operator=False
+split_before_closing_bracket=True
+split_before_dict_set_generator=False
+split_before_dot=False
+split_before_expression_after_opening_paren=False
+split_before_first_argument=False
+split_before_logical_operator=False
+split_before_named_assigns=True
+split_complex_comprehension=True
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..467effa
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+# DO NOT EDIT THIS FILE!
+# This file has been autogenerated by dephell <3
+# https://github.com/dephell/dephell
+
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+
+import os.path
+
+readme = ''
+here = os.path.abspath(os.path.dirname(__file__))
+readme_path = os.path.join(here, 'README.rst')
+if os.path.exists(readme_path):
+ with open(readme_path, 'rb') as stream:
+ readme = stream.read().decode('utf8')
+
+setup(
+ long_description=readme,
+ name='cenv_tool',
+ version='1.0.0',
+ description='conda environment creation and update from meta.yaml',
+ python_requires='==3.*,>=3.7.0',
+ project_urls={'homepage': 'https://www.cenv.ouroboros.info'},
+ author='Simon Kallfass',
+ author_email='skallfass@ouroboros.info',
+ license='MIT',
+ keywords='conda environment dependencies',
+ entry_points={
+ 'console_scripts': [
+ 'cenv = cenv_tool.project:main',
+ 'init_cenv = cenv_tool.init_cenv:main'
+ ]
+ },
+ packages=['cenv_tool'],
+ package_data={'cenv_tool': ['*.sh', '*.yml']},
+ install_requires=[
+ 'attrs==19.*,>=19.0.0', 'jinja2>=2', 'marshmallow<3,>=2.19',
+ 'ruamel.yaml>=0.15', 'six>=1.12'
+ ],
+ extras_require={
+ 'dev': [
+ 'coverage-badge==1.*,>=1.0.0', 'ipython>=7', 'mkdocs==1.*,>=1.0.0',
+ 'monkeytype>=19', 'pydoc-markdown==2.*,>=2.0.0', 'pylint>=2',
+ 'pytest==3.*,>=3.0.0', 'pytest-cov==2.*,>=2.0.0',
+ 'pytest-datafiles==2.*,>=2.0.0', 'yapf>=0'
+ ],
+ 'docs': ['mkdocs==1.*,>=1.0.0', 'pydoc-markdown==2.*,>=2.0.0']
+ },
+)
diff --git a/tests/.condarc b/tests/.condarc
new file mode 100644
index 0000000..e839621
--- /dev/null
+++ b/tests/.condarc
@@ -0,0 +1,17 @@
+allow_other_channels: True
+show_channel_urls: True
+
+conda-build:
+ output-folder: /opt/conda/conda-bld
+
+pkgs_dirs:
+ - /shared/conda/pkgs
+
+envs_dirs:
+ - /shared/conda/envs
+
+channels:
+ - defaults
+ - local
+ - https://repository-channel.ouroboros.info
+ - conda-forge
diff --git a/tests/home_test/.bashrc b/tests/home_test/.bashrc
new file mode 100644
index 0000000..2ef267e
--- /dev/null
+++ b/tests/home_test/.bashrc
@@ -0,0 +1 @@
+some content
diff --git a/tests/home_test/.zshrc b/tests/home_test/.zshrc
new file mode 100644
index 0000000..2ef267e
--- /dev/null
+++ b/tests/home_test/.zshrc
@@ -0,0 +1 @@
+some content
diff --git a/tests/init_cenv_test.py b/tests/init_cenv_test.py
new file mode 100644
index 0000000..d3072be
--- /dev/null
+++ b/tests/init_cenv_test.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+from pathlib import Path
+
+import pytest
+
+from cenv_tool.init_cenv import initialize_cenv
+from cenv_tool.init_cenv import RC_CONTENT
+
+
+@pytest.mark.datafiles('tests/home_test')
+def test_initialize_cenv(datafiles):
+ expected_result = 'some content\n' + RC_CONTENT
+ config_path = Path(datafiles) / '.config/cenv'
+ config_file = config_path / 'cenv.yml'
+ autoenv_script_path = config_path / 'cenv.sh'
+ zshrc = Path(datafiles) / '.zshrc'
+ bashrc = Path(datafiles) / '.bashrc'
+ for _ in range(2):
+ initialize_cenv(
+ config_path=config_path,
+ autoenv_script_path=autoenv_script_path,
+ autoenv_script_source_path=Path('cenv_tool/cenv.sh'),
+ config_file=config_file,
+ config_file_source=Path('cenv_tool/cenv.yml'),
+ zshrc=zshrc,
+ bashrc=bashrc,
+ )
+ assert expected_result == zshrc.read_text()
+ assert expected_result == bashrc.read_text()
+ assert autoenv_script_path.exists()
+ assert config_file.exists()
+ assert config_path.exists()
diff --git a/tests/invalid_testproject/conda-build/meta.yaml b/tests/invalid_testproject/conda-build/meta.yaml
new file mode 100644
index 0000000..dc634d0
--- /dev/null
+++ b/tests/invalid_testproject/conda-build/meta.yaml
@@ -0,0 +1,43 @@
+{% set data = load_setup_py_data() %}
+
+package:
+ name: "testproject"
+ version: {{ data.get("version") }}
+
+source:
+ path: ..
+
+build:
+ build: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }}
+ preserve_egg_dir: True
+ script: python -m pip install --no-deps --ignore-installed .
+ entry_points:
+ - testproject = testproject.testproject:main
+
+requirements:
+ build:
+ - python 3.7.3
+ - pip
+ - setuptools
+ run:
+ - python 3.7.3
+ - attrs >=19
+ - jinja2 >=2.10
+ - ruamel.yaml >=0.15.23
+ - six >=1.12.0
+ - yaml >=0.1.7
+ - marshmallow >=2,<3
+
+test:
+ imports:
+ - testproject
+ - testproject.testproject
+ commands:
+ - testproject --help
+ - testproject -v
+
+extra:
+ dev_requirements:
+ - ipython >=7.2.0
+ - mkdocs >=1.0.4
+ - pylint >=2.2.2
diff --git a/tests/project_test.py b/tests/project_test.py
new file mode 100644
index 0000000..91e372f
--- /dev/null
+++ b/tests/project_test.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+import os
+from pathlib import Path
+
+import pytest
+
+from cenv_tool.project import Project
+from cenv_tool.rules import RULES
+
+
+def test_project_collect_available_envs():
+ current_path = Path.cwd()
+ testfolder = Path('tests/testproject')
+ os.chdir(str(testfolder))
+ project = Project(rules=RULES)
+ os.chdir(str(current_path))
+ available_envs = project.collect_available_envs()
+ print(available_envs)
+
+
+@pytest.mark.datafiles('tests/testproject')
+def test_project_update(datafiles):
+ created_env = Path('/shared/conda/envs/cenv_testing_project0001')
+ environment_yml = Path(datafiles) / 'conda-build/environment.yml'
+ current_folder = Path.cwd()
+ os.chdir(datafiles)
+ project = Project(rules=RULES)
+ project.update()
+ assert created_env.exists()
+ project = Project(rules=RULES)
+ project.update()
+ assert created_env.exists()
+ project = Project(rules=RULES)
+ project.remove_previous_environment()
+ project.remove_backup_environment()
+ project.create_environment(cloned=False)
+ project.export_environment_definition()
+ assert environment_yml.exists()
+ environment_yml.unlink()
+ project.remove_previous_environment()
+ project.remove_backup_environment()
+ os.chdir(str(current_folder))
diff --git a/tests/rules_test.py b/tests/rules_test.py
new file mode 100644
index 0000000..25ab753
--- /dev/null
+++ b/tests/rules_test.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+"""Test the rules module of cenv."""
+from cenv_tool.rules import RULES
+
+
+def test_rules():
+ """Test if RULES contain attributes as expected."""
+ assert RULES.git_folder
+ assert RULES.conda_cmds
+ assert RULES.conda_cmds.remove
+ assert RULES.conda_cmds.export
+ assert RULES.conda_cmds.create
+ assert RULES.conda_cmds.clone
+ assert RULES.conda_cmds.restore
+ assert RULES.conda_cmds.clean
diff --git a/tests/testproject/conda-build/meta.yaml b/tests/testproject/conda-build/meta.yaml
new file mode 100644
index 0000000..966901d
--- /dev/null
+++ b/tests/testproject/conda-build/meta.yaml
@@ -0,0 +1,39 @@
+{% set data = load_setup_py_data() %}
+
+package:
+ name: "cenv_testing_project0001"
+ version: {{ data.get("version") }}
+
+source:
+ path: ..
+
+build:
+ build: {{ environ.get('GIT_DESCRIBE_NUMBER', 0) }}
+ preserve_egg_dir: True
+ script: python -m pip install --no-deps --ignore-installed .
+ entry_points:
+ - testproject = testproject.testproject:main
+
+requirements:
+ build:
+ - python 3.7.3
+ - pip
+ - setuptools
+ run:
+ - python 3.7.3
+ - attrs >=19
+ - jinja2 >=2.10
+ - six >=1.12.0
+ - yaml >=0.1.7
+
+test:
+ imports:
+ - cenv_testing_project0001
+ commands:
+ - cenv_testing_project0001 --help
+ - cenv_testing_project0001 -v
+
+extra:
+ env_name: cenv_testing_project0001
+ dev_requirements:
+ - pylint >=2.2.2
diff --git a/tests/utils_test.py b/tests/utils_test.py
new file mode 100644
index 0000000..8931748
--- /dev/null
+++ b/tests/utils_test.py
@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+"""Test the utils module of cenv."""
+from pathlib import Path
+
+import pytest
+from marshmallow import ValidationError
+
+from cenv_tool.utils import CenvProcessError
+from cenv_tool.utils import read_meta_yaml
+from cenv_tool.utils import run_in_bash
+
+
+@pytest.mark.parametrize(
+ 'meta_yaml_path, expected_meta_yaml_content, expected_dependencies',
+ [
+ (
+ Path('tests/testproject'),
+ {
+ 'requirements': {
+ 'build': ['python 3.7.3', 'pip', 'setuptools'],
+ 'run': [
+ 'python 3.7.3',
+ 'attrs >=19',
+ 'jinja2 >=2.10',
+ 'six >=1.12.0',
+ 'yaml >=0.1.7',
+ 'pylint >=2.2.2',
+ ],
+ },
+ 'extra': {
+ 'dev_requirements': ['pylint >=2.2.2'],
+ 'env_name': 'cenv_testing_project0001',
+ },
+ 'source': {
+ 'path': '..',
+ },
+ 'test': {
+ 'commands': [
+ 'cenv_testing_project0001 --help',
+ 'cenv_testing_project0001 -v',
+ ],
+ 'imports': ['cenv_testing_project0001'],
+ },
+ 'build': {
+ 'build': '0',
+ 'preserve_egg_dir': 'True',
+ 'script':
+ 'python -m pip install --no-deps --ignore-installed .',
+ 'entry_points':
+ ['testproject = testproject.testproject:main'],
+ },
+ 'package': {
+ 'version': 'None',
+ 'name': 'cenv_testing_project0001',
+ },
+ },
+ [
+ 'python 3.7.3',
+ 'attrs >=19',
+ 'jinja2 >=2.10',
+ 'six >=1.12.0',
+ 'yaml >=0.1.7',
+ 'pylint >=2.2.2',
+ ],
+ ),
+ ],
+)
+def test_read_meta_yaml(
+ meta_yaml_path,
+ expected_meta_yaml_content,
+ expected_dependencies,
+):
+ """Test if the read_meta_yaml function works as expected."""
+ meta_yaml_content, dependencies = read_meta_yaml(path=meta_yaml_path)
+ assert meta_yaml_content == expected_meta_yaml_content
+ assert expected_dependencies == dependencies
+
+
+@pytest.mark.parametrize('meta_yaml_path', [Path('tests/invalid_testproject')])
+def test_read_meta_yaml_fails(meta_yaml_path):
+ """Test if read_meta_yaml function fails on invalid meta.yaml."""
+ with pytest.raises(ValidationError):
+ read_meta_yaml(path=meta_yaml_path)
+
+
+def test_run_in_bash():
+ """Test if run_in_bash works as expected."""
+ cmd_result = run_in_bash(cmd='ls tests/testproject/conda-build')
+ assert cmd_result == 'meta.yaml'
+
+
+def test_run_in_bash_fails():
+ """Test if run_in_bash works as expected."""
+ with pytest.raises(CenvProcessError):
+ run_in_bash(cmd='ls-a tests/testproject/conda-build')