diff --git a/.coverage b/.coverage new file mode 100644 index 00000000..18e2d2c3 Binary files /dev/null and b/.coverage differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/AUTHORS.rst b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/AUTHORS.rst new file mode 100644 index 00000000..784772f8 --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/AUTHORS.rst @@ -0,0 +1,23 @@ +============ +Contributors +============ + +* Florian Wilhelm +* Felix Wick +* Holger Peters +* Uwe Korn +* Patrick Mühlbauer +* Florian Rathgeber +* Eva Schmücker +* Tim Werner +* Julian Gethmann +* Will Usher +* Anderson Bravalheri +* David Hilton +* Pablo Aguiar +* Vicky C Lau +* Reuven Podmazo +* Juan Leni +* Anthony Sottile +* Henning Häcker +* Noah Pendleton diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/LICENSE.txt b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/LICENSE.txt new file mode 100644 index 00000000..dd9fa415 --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Blue Yonder GmbH + +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/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/PKG-INFO b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/PKG-INFO new file mode 100644 index 00000000..92bd62a1 --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/PKG-INFO @@ -0,0 +1,277 @@ +Metadata-Version: 2.1 +Name: PyScaffold +Version: 3.2.3 +Summary: Template tool for putting up the scaffold of a Python project +Home-page: https://github.com/pyscaffold/pyscaffold/ +Author: Florian Wilhelm +Author-email: Florian.Wilhelm@gmail.com +License: MIT +Project-URL: Documentation, https://pyscaffold.org/ +Project-URL: Twitter, https://twitter.com/PyScaffold +Project-URL: Conda-Forge, https://anaconda.org/conda-forge/pyscaffold +Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Topic :: Utilities +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Unix +Classifier: Operating System :: MacOS +Classifier: Operating System :: Microsoft :: Windows +Requires-Python: >=3.4 +Description-Content-Type: text/x-rst; charset=UTF-8 +Requires-Dist: setuptools (>=38.3) +Provides-Extra: all +Requires-Dist: django ; extra == 'all' +Requires-Dist: cookiecutter ; extra == 'all' +Requires-Dist: pyscaffoldext-markdown ; extra == 'all' +Requires-Dist: pyscaffoldext-pyproject ; extra == 'all' +Requires-Dist: pyscaffoldext-custom-extension ; extra == 'all' +Requires-Dist: pyscaffoldext-dsproject ; extra == 'all' +Provides-Extra: ds +Requires-Dist: pyscaffoldext-dsproject ; extra == 'ds' +Provides-Extra: md +Requires-Dist: pyscaffoldext-markdown ; extra == 'md' +Provides-Extra: testing +Requires-Dist: sphinx ; extra == 'testing' +Requires-Dist: flake8 ; extra == 'testing' +Requires-Dist: pytest ; extra == 'testing' +Requires-Dist: pytest-cov ; extra == 'testing' +Requires-Dist: pytest-shutil ; extra == 'testing' +Requires-Dist: pytest-virtualenv ; extra == 'testing' +Requires-Dist: pytest-fixture-config ; extra == 'testing' +Requires-Dist: pytest-xdist ; extra == 'testing' + +.. image:: https://api.cirrus-ci.com/github/pyscaffold/pyscaffold.svg?branch=master + :alt: Built Status + :target: https://cirrus-ci.com/github/pyscaffold/pyscaffold +.. image:: https://readthedocs.org/projects/pyscaffold/badge/?version=latest + :alt: ReadTheDocs + :target: https://pyscaffold.org/ +.. image:: https://img.shields.io/coveralls/github/pyscaffold/pyscaffold/master.svg + :alt: Coveralls + :target: https://coveralls.io/r/pyscaffold/pyscaffold +.. image:: https://img.shields.io/pypi/v/pyscaffold.svg + :alt: PyPI-Server + :target: https://pypi.org/project/pyscaffold/ +.. image:: https://img.shields.io/conda/vn/conda-forge/pyscaffold.svg + :alt: Conda-Forge + :target: https://anaconda.org/conda-forge/pyscaffold +.. image:: https://img.shields.io/twitter/url/http/shields.io.svg?style=social&label=Follow + :alt: Twitter + :target: https://twitter.com/pyscaffold + + +| + +.. image:: https://pyscaffold.org/en/latest/_images/logo.png + :height: 512px + :width: 512px + :scale: 60 % + :alt: PyScaffold logo + :align: center + +| + +PyScaffold helps you setup a new Python project. Just install it with:: + + pip install pyscaffold + +or if you want to also install all *extensions* with:: + + pip install pyscaffold[all] + +If you prefer *conda* over *pip*, just install PyScaffold with:: + + conda install -c conda-forge pyscaffold + +This will give you a new ``putup`` command and you can just type:: + + putup my_project + +This will create a new folder called ``my_project`` containing a perfect *project +template* with everything you need for some serious coding. After the usual:: + + python setup.py develop + +you are all set and ready to go. + +Type ``putup -h`` to learn about more configuration options. PyScaffold assumes +that you have Git_ installed and set up on your PC, +meaning at least your name and email are configured. +The project template in ``my_project`` provides you with following features: + + +Configuration & Packaging +========================= + +All configuration can be done in ``setup.cfg`` like changing the description, +url, classifiers, installation requirements and so on as defined by setuptools_. +That means in most cases it is not necessary to tamper with ``setup.py``. + +In order to build a source, binary or wheel distribution, just run +``python setup.py sdist``, ``python setup.py bdist`` or +``python setup.py bdist_wheel`` (recommended). + +.. rubric:: Package and Files Data + +Additional data, e.g. images and text files, that reside within your package and +are tracked by Git will automatically be included +(``include_package_data = True`` in ``setup.cfg``). +It is not necessary to have a ``MANIFEST.in`` file for this to work. + +Versioning and Git Integration +============================== + +Your project is an already initialised Git repository and ``setup.py`` uses +the information of tags to infer the version of your project with the help of +setuptools_scm_. +To use this feature, you need to tag with the format ``MAJOR.MINOR[.PATCH]`` +, e.g. ``0.0.1`` or ``0.1``. +Run ``python setup.py --version`` to retrieve the current PEP440_-compliant +version. This version +will be used when building a package and is also accessible through +``my_project.__version__``. + +Unleash the power of Git by using its `pre-commit hooks`_. This feature is +available through the ``--pre-commit`` flag. After your project's scaffold +was generated, make sure pre-commit is installed, e.g. ``pip install pre-commit``, +then just run ``pre-commit install``. + +A default ``.gitignore`` file is also provided; it is +well adjusted for Python projects and the most common tools. + + +Sphinx Documentation +==================== + +Build the documentation with ``python setup.py docs`` and run doctests with +``python setup.py doctest`` after you have `Sphinx`_ installed. +Start editing the file ``docs/index.rst`` to extend the documentation. +The documentation also works with `Read the Docs`_. + +The `Numpy and Google style docstrings`_ are activated by default. +Just make sure Sphinx 1.3 or above is installed. + + +Unittest & Coverage +=================== + +Run ``python setup.py test`` to run all unittests defined in the subfolder +``tests`` with the help of `py.test`_ and pytest-runner_. Some sane +default flags for py.test are already defined in the ``[tool:pytest]`` section of +``setup.cfg``. The py.test plugin `pytest-cov`_ is used to automatically +generate a coverage report. It is also possible to provide additional +parameters and flags on the commandline, e.g., type:: + + python setup.py test --addopts -h + +to show the help of py.test. + +.. rubric:: JUnit and Coverage HTML/XML + +For usage with a continuous integration software JUnit and Coverage XML output +can be activated in ``setup.cfg``. Use the flag ``--travis`` to generate +templates of the `Travis`_ configuration files +``.travis.yml`` and ``tests/travis_install.sh`` which even features the +coverage and stats system `Coveralls`_. +In order to use the virtualenv management and test tool `Tox`_ the flag +``--tox`` can be specified. + + +Management of Requirements & Licenses +===================================== + +Installation requirements of your project can be defined inside ``setup.cfg``, +e.g. ``install_requires = numpy; scipy``. To avoid package dependency problems, +it is common to not pin installation requirements to any specific version, +although minimum versions, e.g. ``sphinx>=1.3``, or maximum versions, e.g. +``pandas<0.12``, are used sometimes. + +More specific installation requirements should go into ``requirements.txt``. +This file can also be managed with the help of ``pip compile`` from `pip-tools`_ +that basically pins packages to the current version, e.g. ``numpy==1.13.1``. +The packages defined in ``requirements.txt`` can be easily installed with:: + + pip install -r requirements.txt + +All licenses from `choosealicense.com`_ can be easily selected with the help +of the ``--license`` flag. + + +Extensions +========== + +PyScaffold comes with several extensions: + +* If you want a project setup for a *Data Science* task, just use ``--dsproject`` + after having installed `pyscaffoldext-dsproject`_. + +* Create a `Django project`_ with the flag ``--django`` which is equivalent to + ``django-admin.py startproject my_project`` enhanced by PyScaffold's features. + +* Create a template for your own PyScaffold extension with ``--custom-extension`` + after having installed `pyscaffoldext-custom-extension`_ with ``pip``. + +* Have a ``README.md`` based on MarkDown instead of ``README.rst`` by using + ``--markdown`` after having installed `pyscaffoldext-markdown`_ with ``pip``. + +* Add a ``pyproject.toml`` file according to `PEP 518`_ to your template by using + ``--pyproject`` after having installed `pyscaffoldext-pyproject`_ with ``pip``. + +* With the help of `Cookiecutter`_ it is possible to further customize your project + setup with a template tailored for PyScaffold. Just use the flag ``--cookiecutter TEMPLATE`` + to use a cookiecutter template which will be refined by PyScaffold afterwards. + +* ... and many more like ``--gitlab`` to create the necessary files for GitLab_. + +Find more extensions within the `PyScaffold organisation`_ and consider contributing your own. +All extensions can easily be installed with ``pip pyscaffoldext-NAME``. + +Easy Updating +============= + +Keep your project's scaffold up-to-date by applying +``putup --update my_project`` when a new version of PyScaffold was released. +An update will only overwrite files that are not often altered by users like +``setup.py``. To update all files use ``--update --force``. +An existing project that was not setup with PyScaffold can be converted with +``putup --force existing_project``. The force option is completely safe to use +since the git repository of the existing project is not touched! + + +.. _setuptools: http://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files +.. _setuptools_scm: https://pypi.python.org/pypi/setuptools_scm/ +.. _Git: http://git-scm.com/ +.. _PEP440: http://www.python.org/dev/peps/pep-0440/ +.. _pre-commit hooks: http://pre-commit.com/ +.. _py.test: http://pytest.org/ +.. _Sphinx: http://www.sphinx-doc.org/ +.. _Read the Docs: https://readthedocs.org/ +.. _Numpy and Google style docstrings: http://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html +.. _pytest-runner: https://pypi.python.org/pypi/pytest-runner +.. _pytest-cov: https://github.com/schlamar/pytest-cov +.. _Travis: https://travis-ci.org +.. _Coveralls: https://coveralls.io/ +.. _Tox: https://tox.readthedocs.org/ +.. _choosealicense.com: http://choosealicense.com/ +.. _Django project: https://www.djangoproject.com/ +.. _Cookiecutter: https://cookiecutter.readthedocs.org/ +.. _GitLab: https://about.gitlab.com/ +.. _pip-tools: https://github.com/jazzband/pip-tools/ +.. _pyscaffoldext-dsproject: https://github.com/pyscaffold/pyscaffoldext-dsproject +.. _pyscaffoldext-custom-extension: https://github.com/pyscaffold/pyscaffoldext-custom-extension +.. _pyscaffoldext-markdown: https://github.com/pyscaffold/pyscaffoldext-markdown +.. _pyscaffoldext-pyproject: https://github.com/pyscaffold/pyscaffoldext-pyproject +.. _PEP 518: https://www.python.org/dev/peps/pep-0518/ +.. _PyScaffold organisation: https://github.com/pyscaffold/ + + diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/RECORD b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/RECORD new file mode 100644 index 00000000..b6a56c48 --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/RECORD @@ -0,0 +1,91 @@ +pyscaffold/__init__.py,sha256=1K2gdVGkEKzBMB4wgm8_XDp0TQfRTQqRwsDAE3BD5C0,205 +pyscaffold/cli.py,sha256=6Eoy4bn6vTlKJlHnZguydyBew9mpT5rbANRvEUJ9D_k,6867 +pyscaffold/exceptions.py,sha256=FRH1y0dotSVFQ-03Kr6J6Px_VUJMtklYj-Q4xEJEqJU,3212 +pyscaffold/info.py,sha256=Hbs19bM9nnq3GgfMcMyL9i3H0SZjkeJjjn30iHJwzk0,5886 +pyscaffold/integration.py,sha256=RyK1ULdNIdqLczsu4r1VToZm2pj74bYlxbPeUCKPNOo,3295 +pyscaffold/log.py,sha256=AUhFPND6CQB_EVQnr9fzY6fB-LVsSfcETNobZuCI6tM,10690 +pyscaffold/repo.py,sha256=GalmynRntxoh987twIwBECxqa9OqS_rifYXYKty506Q,3003 +pyscaffold/shell.py,sha256=U4pDyckFQFsEnGr3vSe2pgNy1RTMxbE2GdO654548XE,3554 +pyscaffold/structure.py,sha256=l4LYHb65AA8pXbpr-_GuSc_5AuU4l5JeKw82QbJLnW8,3936 +pyscaffold/termui.py,sha256=LvpNeU20c2lJ5FhjynRTg2zh9GLE9J-Jf2euzd-7Pz4,2262 +pyscaffold/update.py,sha256=LlWxdHmfqDvCbl5DQGgVKQvuGMiwj7ZIB90Y2IRwiNM,7567 +pyscaffold/utils.py,sha256=EASRtB6jPo1vWF80xgDz1qTKHQU86-uuNacB1yGWme0,14167 +pyscaffold/warnings.py,sha256=m3RkML631kQmFTx75LitwXwS_NJkrxU8VsjpfQ3R79c,739 +pyscaffold/api/__init__.py,sha256=shhdFTFcScVY0lUpvjQ8eEHQHOEWXCga-osiFjPfBi4,11516 +pyscaffold/api/helpers.py,sha256=aVww2ckouKZ-HORmGdFrO1yEIZjjz5ZmtU7h6lxJ1AQ,12874 +pyscaffold/contrib/__init__.py,sha256=mUWwzNdmGgB3cySAKxXd0dqtRp0t9sbQVLT3EpsX37M,1735 +pyscaffold/contrib/configupdater.py,sha256=zgCzxqdDIGWQY4gMzFyUMbzdAuI6GBD5KYeDa02kwr8,37133 +pyscaffold/contrib/ptr.py,sha256=6BlnIXLmJ7aVbpXERfPcRO3N3Mn7mXP1jtbeC5GvK8E,6867 +pyscaffold/contrib/setuptools_scm/__init__.py,sha256=9WuARIaYh9jvNy6SyHoHbF6bbdpODVF5y3xQO28y6cU,5023 +pyscaffold/contrib/setuptools_scm/__main__.py,sha256=r2weno4bqNJ3pFJCrArU2ryJOKNWZ_pg1Fz6wiEe6t0,423 +pyscaffold/contrib/setuptools_scm/config.py,sha256=T1FE7At-qVydGmbTYfjFyxGrowv_apvEeVAB-IuLsqQ,2890 +pyscaffold/contrib/setuptools_scm/discover.py,sha256=LqX0raq94k3llpxXmjNnDbkqECIGqEm1UcU3g2Rn2NI,416 +pyscaffold/contrib/setuptools_scm/file_finder.py,sha256=bH3QaU4NdinhjFP_pWl2DIdjAEUsjgZ46A_dpjAS5pc,2234 +pyscaffold/contrib/setuptools_scm/file_finder_git.py,sha256=FafbO3HqVDPBvJNvRqRvluiyQMiej4MAkSWe1EHB-i8,1879 +pyscaffold/contrib/setuptools_scm/file_finder_hg.py,sha256=FOX60VjepspOV4eL234lRUADsC7L2BRvniYl4_rR4Ng,1415 +pyscaffold/contrib/setuptools_scm/git.py,sha256=YaAZIL45ItXO4Gp7sj7MsxNGCeBnrQD3wVcQAjjH9lY,4064 +pyscaffold/contrib/setuptools_scm/hacks.py,sha256=uTMI3IxvOen631QOZmVQonGqDjstBpx5aYpR6Z2KeHA,839 +pyscaffold/contrib/setuptools_scm/hg.py,sha256=vYsqCe5jb-Ry-ldypn2gBjFrKTFTEQjAkpz6uFEzdDc,3371 +pyscaffold/contrib/setuptools_scm/integration.py,sha256=wcKh3Sb7_wobpCuCzgt-WXAkCKMBi9Xtopg3ggnOstU,769 +pyscaffold/contrib/setuptools_scm/utils.py,sha256=Wc0xlgqgYQDj3-oQSmpPvihqeyFcgWUYSOK8eUdwdqU,2707 +pyscaffold/contrib/setuptools_scm/version.py,sha256=-vSwpaelXN-Za_00wIycwy-irf42VtcQFPrMnPeSAk8,8400 +pyscaffold/contrib/setuptools_scm/win_py31_compat.py,sha256=w-TAp2Z21O_shi3gPvlcbxz1JZ8cGOLFd9HguCTYB6E,5098 +pyscaffold/extensions/__init__.py,sha256=Hl7FGT5dc6skMFuqPtDhQ8DGJXgqjLa-Hd0FbF-XE0M,68 +pyscaffold/extensions/cookiecutter.py,sha256=4bU3wfv7yGedH2QSdWBuM7FSH9JcyV7qfZfCrJd0lcM,5549 +pyscaffold/extensions/django.py,sha256=3GkgusNs4zux8FgZsnIZn9FXkxjxc8k27ZqW2mVHUi4,3478 +pyscaffold/extensions/gitlab_ci.py,sha256=GQMSohlSt5LeU-ANqebaaWX6VD_CN6wC_SeUIw5fDCw,1152 +pyscaffold/extensions/namespace.py,sha256=JR6P18UgWesgTi37wDYvxiPzcC_1egAiX8FW8gkTOIY,5101 +pyscaffold/extensions/no_skeleton.py,sha256=HYCnFNVQNGnLNtqiuyGRKlOmj8V_rOpVR1kffr8C1xM,1330 +pyscaffold/extensions/pre_commit.py,sha256=RotOdnfbD50n4ky7_x0Il7RfDzuGVpG3ApgvaD1b_TU,2428 +pyscaffold/extensions/tox.py,sha256=fgS7HfdhI40w4kgLCrqtDuWgrz7sUxajW7rXIVHCl3Y,1136 +pyscaffold/extensions/travis.py,sha256=37R6Vrv9-ERETzAZRQIkQrYD8suFqItLHfGmoOuTX3I,1311 +pyscaffold/templates/__init__.py,sha256=OnUeWFkpvQEQXQBg4kTE5BI4y58VjfkmI9Fr5ZFYOgc,10030 +pyscaffold/templates/__init__.template,sha256=iNd_73ecYzkk6BtkeGVU51L-n0CcK2iZYnRhFtpy3MU,370 +pyscaffold/templates/authors.template,sha256=kZhIVRI8z1iqP4fmWGTRPgdR1N3juaPav3zl0WVBsrc,63 +pyscaffold/templates/changelog.template,sha256=L345FrkHS-vk5Wz9QRhn1ozRwg82M3k8JtH5khqn7Zc,128 +pyscaffold/templates/conftest_py.template,sha256=DBPUkRyukwad5JyUdKuwBGT_0dWT1e0BICsjIfEOjxk,231 +pyscaffold/templates/coveragerc.template,sha256=FRWbDOC39biCsdqybJzUc0M8_k_Yp7KnXBAPDdA6QGU,593 +pyscaffold/templates/gitignore.template,sha256=x1_efLYSuWt_kBeuDTrJtN6MJNOsYQ8K8UK1jeSQVqw,520 +pyscaffold/templates/gitignore_empty.template,sha256=W8R-6gcyzaCaj6XGYoyQPYDErVDyYsYY3cB2K_gngcE,18 +pyscaffold/templates/gitlab_ci.template,sha256=awGv6bBPBj7I-X_ixtNc75i5vdPCp-2nq_H7_B1SKgU,1819 +pyscaffold/templates/isort_cfg.template,sha256=Je7a0tdEEsVT_VJJyK8rVRjRN6a5HoMMlGi06YK8kE0,279 +pyscaffold/templates/license_affero_3.0.template,sha256=-5zott_E9lzLVytUzEt7C8LMEYn2L9E1fuOwRLLHf9g,32386 +pyscaffold/templates/license_apache.template,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357 +pyscaffold/templates/license_artistic_2.0.template,sha256=vbuMkuP1JQoc2uwVeOvwzrP0Dn5mp667l0jxkXidYnE,8917 +pyscaffold/templates/license_cc0_1.0.template,sha256=Nv_Z3AhdUpp-YOEnbXOuWgMLAgMT5sVAhZOmrirzlnM,6555 +pyscaffold/templates/license_eclipse_1.0.template,sha256=IuD_PpRjd6Zc7eQjALiUqyKEiBlLFHRBK7MFpoMntPA,11514 +pyscaffold/templates/license_gpl_2.0.template,sha256=ep2MnyVJhVVmwNOTHFjPcmMmKt073qeUI-5aQJr2iR4,15238 +pyscaffold/templates/license_gpl_3.0.template,sha256=xh8S2nza1Sa9y-1HpMCmA-YNu_2vi2aTPNCI6RMsMD8,32472 +pyscaffold/templates/license_isc.template,sha256=smGfo69BolAspbc26dJJI3lDM25zEiZbULOZMBNoOlQ,743 +pyscaffold/templates/license_lgpl_2.1.template,sha256=Izf7sczKycbMcwNEJ2DYO7s65dROESN82pX4PxrXAM4,24478 +pyscaffold/templates/license_lgpl_3.0.template,sha256=2n6rt7r999OuXp8iOqW9we7ORaxWncIbOwN1ILRGR2g,7651 +pyscaffold/templates/license_mit.template,sha256=oGVn-cTPf40_sPzor2eAHMaaIy2BdRQF-he319JJ84s,1079 +pyscaffold/templates/license_mozilla.template,sha256=rxdbnZbuk8IaA2FS4bkFsLlTBNSujCySHHYJEAuo334,15921 +pyscaffold/templates/license_new_bsd.template,sha256=wVmvXwNyrxtjBhh3DHYOPx0wUuustKwrrwlR0gp5a4I,1480 +pyscaffold/templates/license_none.template,sha256=mgaX6c85gu-V-bt0nfhmLH-OhwQcAMnRYIGRRuUCFOE,28 +pyscaffold/templates/license_public_domain.template,sha256=iNm062BXnBkew5HKBMFhMFctfu3EqG2qWL8oxuFMm80,1210 +pyscaffold/templates/license_simplified_bsd.template,sha256=_Aqllv965q30M3DMSC41_uF0NImmeIa64P1NULpHTAs,1295 +pyscaffold/templates/namespace.template,sha256=vXgXMgYCDkxR9ifnsCGWSHLloiNxhHjy0mpN2L-EspA,80 +pyscaffold/templates/pre-commit-config.template,sha256=jtLGrClWyMpNiM4KLDv7KZCWuQ515cBOYVzI52Gx2m0,563 +pyscaffold/templates/readme.template,sha256=4dkvbiPrbgZY_hVJNTZ9PY8AoT2uRAJMiorH2IBz-7Y,251 +pyscaffold/templates/requirements.template,sha256=CoRaPsO1_HgRnKK5n_DQfnMC4jRX-VaCo-VRPmFktcs,660 +pyscaffold/templates/setup_cfg.template,sha256=YFEpAARQtBd-BD9BFDycxJV9Og7ji_6eXu3iEFdDOFw,2726 +pyscaffold/templates/setup_py.template,sha256=0DAdn8pPoOC-vChymBA2LW4Qkm1BMCkzeglzhW0o2sk,579 +pyscaffold/templates/skeleton.template,sha256=UYr_nl595FZkYTV_f4Lf9k5tJxc9HpYi67EFktGCkUM,2803 +pyscaffold/templates/sphinx_authors.template,sha256=souXZONouU5QfKsH3_QZZH9jmZtKp6Qsjx1XV2DQcTY,41 +pyscaffold/templates/sphinx_changelog.template,sha256=75VknA-hfA5D5s-6qNok9jhKBvdhs84_akjtdqR3-7Q,43 +pyscaffold/templates/sphinx_conf.template,sha256=UqVpYr9ofJvGFNTkvwXmvFnvx2pYAzin3vu25vNO7WM,9179 +pyscaffold/templates/sphinx_index.template,sha256=takzscme4NFRYa2L10GeLGZxkZlHIeSHp0rZKJOg4CQ,2209 +pyscaffold/templates/sphinx_license.template,sha256=2gAe5dsf5f8Ru32MGYsPnAEoKFu03IXgyV7Ojj8y9m0,67 +pyscaffold/templates/sphinx_makefile.template,sha256=jZ_YMXxzszNicHdu-vlrHtkeD1OOQ0fGs7mn-X7N2Vw,7618 +pyscaffold/templates/test_skeleton.template,sha256=8e6kzKL_srJPAo7obSSu8Wxe8wZn789YA2OCPW7swio,302 +pyscaffold/templates/tox_ini.template,sha256=w0AqeopdYLmJrI5YgzpDkR8R9zDkWEQaG2X5d0-2RWc,318 +pyscaffold/templates/travis.template,sha256=BC925g7dqztW0QZLedlL3WIKZCtkPpUwMKS02BrRaWc,1160 +pyscaffold/templates/travis_install.template,sha256=h833Dh082QCQdx2SA2haR7l5xbz-oDys36KTnUOXNno,2115 +PyScaffold-3.2.3.dist-info/AUTHORS.rst,sha256=sAMnD7f2_84F6TTSqUhX5dqYaAbZpbjXJYzMYhXh8Dw,348 +PyScaffold-3.2.3.dist-info/LICENSE.txt,sha256=xKMzeaNltWYizeeiJyyTUYWGWC7ZYve3q3HGRBLACSg,1083 +PyScaffold-3.2.3.dist-info/METADATA,sha256=XztMtlnJfk1l2TgJ3xM4uWXqXWlfX5FLUgxzjY4QZ6I,11438 +PyScaffold-3.2.3.dist-info/WHEEL,sha256=S8S5VL-stOTSZDYxHyf0KP7eds0J72qrK0Evu3TfyAY,92 +PyScaffold-3.2.3.dist-info/entry_points.txt,sha256=w_3zDGcul01fy4-McBA3WciUgz_EdmmjYx2HR1BBQhg,1753 +PyScaffold-3.2.3.dist-info/top_level.txt,sha256=Japu2f8SrMt7bpkTRiszWK5AhbDeFiNnbB--oEYOU6k,11 +PyScaffold-3.2.3.dist-info/RECORD,, diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/WHEEL b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/WHEEL new file mode 100644 index 00000000..c57a5970 --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.33.4) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/entry_points.txt b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/entry_points.txt new file mode 100644 index 00000000..255987a1 --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/entry_points.txt @@ -0,0 +1,42 @@ +[console_scripts] +putup = pyscaffold.cli:run + +[distutils.setup_keywords] +use_pyscaffold = pyscaffold.integration:pyscaffold_keyword + +[pyscaffold.cli] +cookiecutter = pyscaffold.extensions.cookiecutter:Cookiecutter +django = pyscaffold.extensions.django:Django +gitlab = pyscaffold.extensions.gitlab_ci:GitLab +namespace = pyscaffold.extensions.namespace:Namespace +no_skeleton = pyscaffold.extensions.no_skeleton:NoSkeleton +pre_commit = pyscaffold.extensions.pre_commit:PreCommit +tox = pyscaffold.extensions.tox:Tox +travis = pyscaffold.extensions.travis:Travis + +[setuptools.file_finders] +setuptools_scm = pyscaffold.contrib.setuptools_scm.integration:find_files + +[setuptools_scm.files_command] +.git = pyscaffold.contrib.setuptools_scm.file_finder_git:git_find_files +.hg = pyscaffold.contrib.setuptools_scm.file_finder_hg:hg_find_files + +[setuptools_scm.local_scheme] +dirty-tag = pyscaffold.contrib.setuptools_scm.version:get_local_dirty_tag +node-and-date = pyscaffold.contrib.setuptools_scm.version:get_local_node_and_date +node-and-timestamp = pyscaffold.contrib.setuptools_scm.version:get_local_node_and_timestamp + +[setuptools_scm.parse_scm] +.git = pyscaffold.contrib.setuptools_scm.git:parse +.hg = pyscaffold.contrib.setuptools_scm.hg:parse + +[setuptools_scm.parse_scm_fallback] +.hg_archival.txt = pyscaffold.contrib.setuptools_scm.hg:parse_archival +PKG-INFO = pyscaffold.contrib.setuptools_scm.hacks:parse_pkginfo +pip-egg-info = pyscaffold.contrib.setuptools_scm.hacks:parse_pip_egg_info + +[setuptools_scm.version_scheme] +guess-next-dev = pyscaffold.contrib.setuptools_scm.version:guess_next_dev_version +post-release = pyscaffold.contrib.setuptools_scm.version:postrelease_version +python-simplified-semver = setuptools_scm.version:simplified_semver_version + diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/requires.txt b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/requires.txt new file mode 100644 index 00000000..d0889d0b --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/requires.txt @@ -0,0 +1,25 @@ +setuptools>=38.3 + +[all] +cookiecutter +django +pyscaffoldext-custom-extension +pyscaffoldext-dsproject +pyscaffoldext-markdown +pyscaffoldext-pyproject + +[ds] +pyscaffoldext-dsproject + +[md] +pyscaffoldext-markdown + +[testing] +flake8 +pytest +pytest-cov +pytest-fixture-config +pytest-shutil +pytest-virtualenv +pytest-xdist +sphinx diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/top_level.txt b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/top_level.txt new file mode 100644 index 00000000..559345ff --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/EGG-INFO/top_level.txt @@ -0,0 +1 @@ +pyscaffold diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__init__.py b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__init__.py new file mode 100644 index 00000000..eb39031b --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +from pkg_resources import get_distribution, DistributionNotFound + +try: + __version__ = get_distribution(__name__).version +except DistributionNotFound: + __version__ = 'unknown' diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/__init__.cpython-39.pyc b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 00000000..d3244047 Binary files /dev/null and b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/__init__.cpython-39.pyc differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/exceptions.cpython-39.pyc b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/exceptions.cpython-39.pyc new file mode 100644 index 00000000..ed12d124 Binary files /dev/null and b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/exceptions.cpython-39.pyc differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/integration.cpython-39.pyc b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/integration.cpython-39.pyc new file mode 100644 index 00000000..411f7948 Binary files /dev/null and b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/integration.cpython-39.pyc differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/log.cpython-39.pyc b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/log.cpython-39.pyc new file mode 100644 index 00000000..3706924d Binary files /dev/null and b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/log.cpython-39.pyc differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/repo.cpython-39.pyc b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/repo.cpython-39.pyc new file mode 100644 index 00000000..3661ff2b Binary files /dev/null and b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/repo.cpython-39.pyc differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/shell.cpython-39.pyc b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/shell.cpython-39.pyc new file mode 100644 index 00000000..4c573826 Binary files /dev/null and b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/shell.cpython-39.pyc differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/termui.cpython-39.pyc b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/termui.cpython-39.pyc new file mode 100644 index 00000000..3c7755a6 Binary files /dev/null and b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/termui.cpython-39.pyc differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/utils.cpython-39.pyc b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/utils.cpython-39.pyc new file mode 100644 index 00000000..a46594a5 Binary files /dev/null and b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/__pycache__/utils.cpython-39.pyc differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/api/__init__.py b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/api/__init__.py new file mode 100644 index 00000000..81ae8840 --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/api/__init__.py @@ -0,0 +1,349 @@ +# -*- coding: utf-8 -*- +""" +Exposed API for accessing PyScaffold via Python. +""" + +import os +from datetime import date, datetime +from functools import reduce + +import pyscaffold + +from .. import info, repo, utils +from ..exceptions import ( + DirectoryAlreadyExists, + DirectoryDoesNotExist, + GitDirtyWorkspace, + InvalidIdentifier +) +from ..log import logger +from ..structure import ( + create_structure, + define_structure +) +from ..update import ( + apply_update_rules, + version_migration, + invoke_action +) +from . import helpers + + +# -------- Extension Main Class -------- + +class Extension(object): + """Base class for PyScaffold's extensions + + Args: + name (str): How the extension should be named. Default: name of class + By default, this value is used to create the activation flag in + PyScaffold cli. + """ + mutually_exclusive = False + + def __init__(self, name): + self.name = name + self.args = None + + @property + def flag(self): + return '--{flag}'.format(flag=utils.dasherize(self.name)) + + def augment_cli(self, parser): + """Augments the command-line interface parser + + A command line argument ``--FLAG`` where FLAG=``self.name`` is added + which appends ``self.activate`` to the list of extensions. As help + text the docstring of the extension class is used. + In most cases this method does not need to be overwritten. + + Args: + parser: current parser object + """ + help = self.__doc__[0].lower() + self.__doc__[1:] + + parser.add_argument( + self.flag, + help=help, + dest="extensions", + action="append_const", + const=self) + return self + + def activate(self, actions): + """Activates the extension by registering its functionality + + Args: + actions (list): list of action to perform + + Returns: + list: updated list of actions + """ + raise NotImplementedError( + "Extension {} has no actions registered".format(self.name)) + + @staticmethod + def register(*args, **kwargs): + """Shortcut for :obj:`helpers.register`""" + return helpers.register(*args, **kwargs) + + @staticmethod + def unregister(*args, **kwargs): + """Shortcut for :obj:`helpers.unregister`""" + return helpers.unregister(*args, **kwargs) + + def __call__(self, *args, **kwargs): + """Just delegating to :obj:`self.activate`""" + return self.activate(*args, **kwargs) + + +# -------- Actions -------- + +DEFAULT_OPTIONS = {'update': False, + 'force': False, + 'description': 'Add a short description here!', + 'url': 'https://github.com/pyscaffold/pyscaffold/', + 'license': 'mit', + 'version': pyscaffold.__version__, + 'classifiers': ['Development Status :: 4 - Beta', + 'Programming Language :: Python'], + } + + +def discover_actions(extensions): + """Retrieve the action list. + + This is done by concatenating the default list with the one generated after + activating the extensions. + + Args: + extensions (list): list of functions responsible for activating the + extensions. + + Returns: + list: scaffold actions. + """ + actions = DEFAULT_ACTIONS.copy() + + # Order the extensions lexicographically which is needed for determinism, + # also internal before external "pyscaffold.*" < "pyscaffoldext.*" + def sort_by_qual_name(ext): + return '.'.join([ext.__module__, ext.__class__.__qualname__]) + + extensions = sorted(extensions, key=sort_by_qual_name) + # Activate the extensions + return reduce(lambda acc, f: _activate(f, acc), extensions, actions) + + +def get_default_options(struct, opts): + """Compute all the options that can be automatically derived. + + This function uses all the available information to generate sensible + defaults. Several options that can be derived are computed when possible. + + Args: + struct (dict): project representation as (possibly) nested + :obj:`dict`. + opts (dict): given options, see :obj:`create_project` for + an extensive list. + + Returns: + dict, dict: project representation and options with default values set + + Raises: + :class:`~.DirectoryDoesNotExist`: when PyScaffold is told to + update an nonexistent directory + :class:`~.GitNotInstalled`: when git command is not available + :class:`~.GitNotConfigured`: when git does not know user information + + Note: + This function uses git to determine some options, such as author name + and email. + """ + # This function uses information from git, so make sure it is available + info.check_git() + + given_opts = opts + # Initial parameters that need to be provided also during an update + opts = DEFAULT_OPTIONS.copy() + opts.update(given_opts) + opts.setdefault('package', utils.make_valid_identifier(opts['project'])) + opts.setdefault('author', info.username()) + opts.setdefault('email', info.email()) + opts.setdefault('release_date', date.today().strftime('%Y-%m-%d')) + # All kinds of derived parameters + year = datetime.strptime(opts['release_date'], '%Y-%m-%d').year + opts.setdefault('year', year) + opts.setdefault('title', + '='*len(opts['project']) + '\n' + opts['project'] + '\n' + + '='*len(opts['project'])) + + # Initialize empty list of all requirements and extensions + # (since not using deep_copy for the DEFAULT_OPTIONS, better add compound + # values inside this function) + opts.setdefault('requirements', list()) + opts.setdefault('extensions', list()) + opts.setdefault('root_pkg', opts['package']) + opts.setdefault('qual_pkg', opts['package']) + opts.setdefault('cli_params', {'extensions': list(), 'args': dict()}) + opts.setdefault('pretend', False) + + return struct, opts + + +def verify_options_consistency(struct, opts): + """Perform some sanity checks about the given options. + + Args: + struct (dict): project representation as (possibly) nested + :obj:`dict`. + opts (dict): given options, see :obj:`create_project` for + an extensive list. + + Returns: + dict, dict: updated project representation and options + """ + if not utils.is_valid_identifier(opts['package']): + raise InvalidIdentifier( + "Package name {} is not a valid " + "identifier.".format(opts['package'])) + + if opts['update'] and not opts['force']: + if not info.is_git_workspace_clean(opts['project']): + raise GitDirtyWorkspace + + return struct, opts + + +def verify_project_dir(struct, opts): + """Check if PyScaffold can materialize the project dir structure. + + Args: + struct (dict): project representation as (possibly) nested + :obj:`dict`. + opts (dict): given options, see :obj:`create_project` for + an extensive list. + + Returns: + dict, dict: updated project representation and options + """ + if os.path.exists(opts['project']): + if not opts['update'] and not opts['force']: + raise DirectoryAlreadyExists( + "Directory {dir} already exists! Use the `update` option to " + "update an existing project or the `force` option to " + "overwrite an existing directory.".format(dir=opts['project'])) + elif opts['update']: + raise DirectoryDoesNotExist( + "Project {project} does not exist and thus cannot be " + "updated!".format(project=opts['project'])) + + return struct, opts + + +def init_git(struct, opts): + """Add revision control to the generated files. + + Args: + struct (dict): project representation as (possibly) nested + :obj:`dict`. + opts (dict): given options, see :obj:`create_project` for + an extensive list. + + Returns: + dict, dict: updated project representation and options + """ + if not opts['update'] and not repo.is_git_repo(opts['project']): + repo.init_commit_repo(opts['project'], struct, + log=True, pretend=opts.get('pretend')) + + return struct, opts + + +# -------- API -------- + +DEFAULT_ACTIONS = [ + get_default_options, + verify_options_consistency, + define_structure, + verify_project_dir, + apply_update_rules, + version_migration, + create_structure, + init_git +] + + +def create_project(opts=None, **kwargs): + """Create the project's directory structure + + Args: + opts (dict): options of the project + **kwargs: extra options, passed as keyword arguments + + Returns: + tuple: a tuple of `struct` and `opts` dictionary + + Valid options include: + + :Naming: - **project** (*str*) + - **package** (*str*) + + :Package Information: - **author** (*str*) + - **email** (*str*) + - **release_date** (*str*) + - **year** (*str*) + - **title** (*str*) + - **description** (*str*) + - **url** (*str*) + - **classifiers** (*str*) + - **requirements** (*list*) + + :PyScaffold Control: - **update** (*bool*) + - **force** (*bool*) + - **pretend** (*bool*) + - **extensions** (*list*) + + Some of these options are equivalent to the command line options, others + are used for creating the basic python package meta information, but the + last tree can change the way PyScaffold behaves. + + When the **force** flag is ``True``, existing files will be overwritten. + When the **update** flag is ``True``, PyScaffold will consider that some + files can be updated (usually the packaging boilerplate), + but will keep others intact. + When the **pretend** flag is ``True``, the project will not be + created/updated, but the expected outcome will be logged. + + Finally, the **extensions** list may contain any function that follows the + `extension API <../extensions>`_. Note that some PyScaffold features, such + as travis, tox and pre-commit support, are implemented as built-in + extensions. In order to use these features it is necessary to include the + respective functions in the extension list. All built-in extensions are + accessible via :mod:`pyscaffold.extensions` submodule. + + Note that extensions may define extra options. For example, built-in + cookiecutter extension define a ``cookiecutter`` option that + should be the address to the git repository used as template. + """ + opts = opts if opts else {} + opts.update(kwargs) + + actions = discover_actions(opts.get('extensions', [])) + + # call the actions to generate final struct and opts + struct = {} + struct, opts = reduce(lambda acc, f: invoke_action(f, *acc), + actions, (struct, opts)) + return struct, opts + + +# -------- Auxiliary functions -------- + +def _activate(extension, actions): + """Activate extension with proper logging.""" + logger.report('activate', extension.__module__) + with logger.indent(): + actions = extension(actions) + + return actions diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/api/helpers.py b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/api/helpers.py new file mode 100644 index 00000000..175a5f95 --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/api/helpers.py @@ -0,0 +1,373 @@ +# -*- coding: utf-8 -*- +""" +Useful functions for manipulating the action list and project structure. +""" + +from copy import deepcopy +from pathlib import PurePath + +from ..exceptions import ActionNotFound +from ..log import logger +from ..structure import FileOp, define_structure +from ..utils import get_id + +logger = logger # Sphinx workaround to force documenting imported members +"""Logger wrapper, that provides methods like :obj:`~.ReportLogger.report`. +See :class:`~.ReportLogger`. +""" + +NO_OVERWRITE = FileOp.NO_OVERWRITE +"""Do not overwrite an existing file during update +(still created if not exists) +""" + +NO_CREATE = FileOp.NO_CREATE +"""Do not create the file during an update""" + + +# -------- Project Structure -------- + +def _id_func(x): + """Identity function""" + return x + + +def modify(struct, path, modifier=_id_func, update_rule=None): + """Modify the contents of a file in the representation of the project tree. + + If the given path, does not exist the parent directories are automatically + created. + + Args: + struct (dict): project representation as (possibly) nested + :obj:`dict`. See :obj:`~.merge`. + + path (os.PathLike): path-like string or object relative to the + structure root. The following examples are equivalent:: + + from pathlib import PurePath + + 'docs/api/index.html' + PurePath('docs', 'api', 'index.html') + + *Deprecated* - Alternatively, a list with the parts of the path can + be provided, ordered from the structure root to the file itself. + + modifier (callable): function (or callable object) that receives the + old content as argument and returns the new content. + If no modifier is passed, the identity function will be used. + Note that, if the file does not exist in ``struct``, ``None`` will + be passed as argument. Example:: + + modifier = lambda old: (old or '') + 'APPENDED CONTENT'! + modifier = lambda old: 'PREPENDED CONTENT!' + (old or '') + + update_rule: see :class:`~.FileOp`, ``None`` by default. + Note that, if no ``update_rule`` is passed, the previous one is + kept. + + Returns: + dict: updated project tree representation + + Note: + Use an empty string as content to ensure a file is created empty + (``None`` contents will not be created). + + Warning: + *Deprecation Notice* - In the next major release, the usage of lists + for the ``path`` argument will result in an error. Please use + :obj:`pathlib.PurePath` instead. + """ + # Retrieve a list of parts from a path-like object + if isinstance(path, (list, tuple)): + path_parts = path + else: + # TODO: Remove conditional for v4 (always do the following) + path_parts = PurePath(path).parts + + # Walk the entire path, creating parents if necessary. + root = deepcopy(struct) + last_parent = root + name = path_parts[-1] + for parent in path_parts[:-1]: + last_parent = last_parent.setdefault(parent, {}) + + # Get the old value if existent. + old_value = last_parent.get(name, (None, None)) + if not isinstance(old_value, (list, tuple)): + old_value = (old_value, None) + + # Update the value. + new_value = (modifier(old_value[0]), update_rule) + last_parent[name] = _merge_file_leaf(old_value, new_value) + + return root + + +def ensure(struct, path, content=None, update_rule=None): + """Ensure a file exists in the representation of the project tree + with the provided content. + All the parent directories are automatically created. + + Args: + struct (dict): project representation as (possibly) nested + :obj:`dict`. See :obj:`~.merge`. + + path (os.PathLike): path-like string or object relative to the + structure root. The following examples are equivalent:: + + from pathlib import PurePath + + 'docs/api/index.html' + PurePath('docs', 'api', 'index.html') + + *Deprecated* - Alternatively, a list with the parts of the path can + be provided, ordered from the structure root to the file itself. + + content (str): file text contents, ``None`` by default. + The old content is preserved if ``None``. + + update_rule: see :class:`~.FileOp`, ``None`` by default + + Returns: + dict: updated project tree representation + + Note: + Use an empty string as content to ensure a file is created empty. + + Warning: + *Deprecation Notice* - In the next major release, the usage of lists + for the ``path`` argument will result in an error. Please use + :obj:`pathlib.PurePath` instead. + """ + modifier = _id_func if content is None else (lambda _: content) + return modify(struct, path, modifier, update_rule) + + +def reject(struct, path): + """Remove a file from the project tree representation if existent. + + Args: + struct (dict): project representation as (possibly) nested + :obj:`dict`. See :obj:`~.merge`. + + path (os.PathLike): path-like string or object relative to the + structure root. The following examples are equivalent:: + + from pathlib import PurePath + + 'docs/api/index.html' + PurePath('docs', 'api', 'index.html') + + *Deprecated* - Alternatively, a list with the parts of the path can + be provided, ordered from the structure root to the file itself. + + Returns: + dict: modified project tree representation + + Warning: + *Deprecation Notice* - In the next major release, the usage of lists + for the ``path`` argument will result in an error. Please use + :obj:`pathlib.PurePath` instead. + """ + # Retrieve a list of parts from a path-like object + if isinstance(path, (list, tuple)): + path_parts = path + else: + # TODO: Remove conditional for v4 (always do the following) + path_parts = PurePath(path).parts + + # Walk the entire path, creating parents if necessary. + root = deepcopy(struct) + last_parent = root + name = path_parts[-1] + for parent in path_parts[:-1]: + if parent not in last_parent: + return root # one ancestor already does not exist, do nothing + last_parent = last_parent[parent] + + if name in last_parent: + del last_parent[name] + + return root + + +def merge(old, new): + """Merge two dict representations for the directory structure. + + Basically a deep dictionary merge, except from the leaf update method. + + Args: + old (dict): directory descriptor that takes low precedence + during the merge + new (dict): directory descriptor that takes high precedence + during the merge + + The directory tree is represented as a (possibly nested) dictionary. + The keys indicate the path where a file will be generated, while the + value indicates the content. Additionally, tuple values are allowed in + order to specify the rule that will be followed during an ``update`` + operation (see :class:`~.FileOp`). In this case, the first element is + the file content and the second element is the update rule. For + example, the dictionary:: + + {'project': { + 'namespace': { + 'module.py': ('print("Hello World!")', + helpers.NO_OVERWRITE)}} + + represents a ``project/namespace/module.py`` file with content + ``print("Hello World!")``, that will be created only if not + present. + + Returns: + dict: resulting merged directory representation + + Note: + Use an empty string as content to ensure a file is created empty. + (``None`` contents will not be created). + """ + return _inplace_merge(deepcopy(old), new) + + +def _inplace_merge(old, new): + """Similar to :obj:`~.merge` but modifies the first dict.""" + + for key, value in new.items(): + old_value = old.get(key, None) + new_is_dict = isinstance(value, dict) + old_is_dict = isinstance(old_value, dict) + if new_is_dict and old_is_dict: + old[key] = _inplace_merge(old_value, value) + elif old_value is not None and not new_is_dict and not old_is_dict: + # both are defined and final leaves + old[key] = _merge_file_leaf(old_value, value) + else: + old[key] = deepcopy(value) + + return old + + +def _merge_file_leaf(old_value, new_value): + """Merge leaf values for the directory tree representation. + + The leaf value is expected to be a tuple ``(content, update_rule)``. + When a string is passed, it is assumed to be the content and + ``None`` is used for the update rule. + + Args: + old_value (tuple or str): descriptor for the file that takes low + precedence during the merge + new_value (tuple or str): descriptor for the file that takes high + precedence during the merge + + Note: + ``None`` contents are ignored, use and empty string to force empty + contents. + + Returns: + tuple or str: resulting value for the merged leaf + """ + if not isinstance(old_value, (list, tuple)): + old_value = (old_value, None) + if not isinstance(new_value, (list, tuple)): + new_value = (new_value, None) + + content = new_value[0] if new_value[0] is not None else old_value[0] + rule = new_value[1] if new_value[1] is not None else old_value[1] + + if rule is None: + return content + + return (content, rule) + + +# -------- Action List -------- + +def register(actions, action, before=None, after=None): + """Register a new action to be performed during scaffold. + + Args: + actions (list): previous action list. + action (callable): function with two arguments: the first one is a + (nested) dict representing the file structure of the project + and the second is a dict with scaffold options. + This function **MUST** return a tuple with two elements similar + to its arguments. Example:: + + def do_nothing(struct, opts): + return (struct, opts) + + **kwargs (dict): keyword arguments make it possible to choose a + specific order when executing actions: when ``before`` or + ``after`` keywords are provided, the argument value is used as + a reference position for the new action. Example:: + + helpers.register(actions, do_nothing, + after='create_structure') + # Look for the first action with a name + # `create_structure` and inserts `do_nothing` after it. + # If more than one registered action is named + # `create_structure`, the first one is selected. + + helpers.register( + actions, do_nothing, + before='pyscaffold.structure:create_structure') + # Similar to the previous example, but the probability + # of name conflict is decreased by including the module + # name. + + When no keyword argument is provided, the default execution + order specifies that the action will be performed after the + project structure is defined, but before it is written to the + disk. Example:: + + + helpers.register(actions, do_nothing) + # The action will take place after + # `pyscaffold.structure:define_structure` + + Returns: + list: modified action list. + """ + reference = before or after or get_id(define_structure) + position = _find(actions, reference) + if not before: + position += 1 + + clone = actions[:] + clone.insert(position, action) + + return clone + + +def unregister(actions, reference): + """Prevent a specific action to be executed during scaffold. + + Args: + actions (list): previous action list. + reference (str): action identifier. Similarly to the keyword + arguments of :obj:`~.register` it can assume two formats: + + - the name of the function alone, + - the name of the module followed by ``:`` and the name + of the function + + Returns: + list: modified action list. + """ + position = _find(actions, reference) + return actions[:position] + actions[position+1:] + + +def _find(actions, name): + """Find index of name in actions""" + if ':' in name: + names = [get_id(action) for action in actions] + else: + names = [action.__name__ for action in actions] + + try: + return names.index(name) + except ValueError: + raise ActionNotFound(name) diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/cli.py b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/cli.py new file mode 100644 index 00000000..5623c506 --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/cli.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +""" +Command-Line-Interface of PyScaffold +""" + +import argparse +import logging +import os.path +import sys + +from pkg_resources import parse_version + +from . import __version__ as pyscaffold_version +from . import api, info, shell, templates, utils +from .exceptions import NoPyScaffoldProject +from .log import ReportFormatter, configure_logger +from .utils import get_id + + +def add_default_args(parser): + """Add the default options and arguments to the CLI parser. + + Args: + parser (argparse.ArgumentParser): CLI parser object + """ + + parser.add_argument( + dest="project", + help="project name", + metavar="PROJECT") + parser.add_argument( + "-p", + "--package", + dest="package", + required=False, + help="package name (default: project name)", + metavar="NAME") + parser.add_argument( + "-d", + "--description", + dest="description", + required=False, + help="package description", + metavar="TEXT") + license_choices = templates.licenses.keys() + parser.add_argument( + "-l", + "--license", + dest="license", + choices=license_choices, + required=False, + default="mit", + help="package license like {choices} (default: {default})".format( + choices=', '.join(license_choices), default="mit"), + metavar="LICENSE") + parser.add_argument( + "-u", + "--url", + dest="url", + required=False, + help="package url", + metavar="URL") + parser.add_argument( + "-f", + "--force", + dest="force", + action="store_true", + default=False, + help="force overwriting an existing directory") + parser.add_argument( + "-U", + "--update", + dest="update", + action="store_true", + default=False, + help="update an existing project by replacing the most important files" + " like setup.py etc. Use additionally --force to " + "replace all scaffold files.") + parser.add_argument( + '-V', + '--version', + action='version', + version='PyScaffold {ver}'.format(ver=pyscaffold_version)) + parser.add_argument( + "-v", + "--verbose", + action="store_const", + const=logging.INFO, + dest="log_level", + help="show additional information about current actions") + parser.add_argument( + "-vv", + "--very-verbose", + action="store_const", + const=logging.DEBUG, + dest="log_level", + help="show all available information about current actions") + + group = parser.add_mutually_exclusive_group() + group.add_argument( + "-P", + "--pretend", + dest="pretend", + action="store_true", + default=False, + help="do not create project, but displays the log of all operations" + " as if it had been created.") + group.add_argument( + "--list-actions", + dest="command", + action="store_const", + const=list_actions, + help="do not create project, but show a list of planned actions") + + +def parse_args(args): + """Parse command line parameters respecting extensions + + Args: + args ([str]): command line parameters as list of strings + + Returns: + dict: command line parameters + """ + from pkg_resources import iter_entry_points + + # create the argument parser + parser = argparse.ArgumentParser( + description="PyScaffold is a tool for easily putting up the scaffold " + "of a Python project.") + parser.set_defaults(log_level=logging.WARNING, + extensions=[], + command=run_scaffold) + add_default_args(parser) + # load and instantiate extensions + cli_extensions = [extension.load()(extension.name) for extension + in iter_entry_points('pyscaffold.cli')] + # add a group for mutually exclusive external generators + mutex_group = parser.add_mutually_exclusive_group() + for extension in cli_extensions: + if extension.mutually_exclusive: + extension.augment_cli(mutex_group) + else: + extension.augment_cli(parser) + + # Parse options and transform argparse Namespace object into common dict + opts = vars(parser.parse_args(args)) + return opts + + +def process_opts(opts): + """Process and enrich command line arguments + + Args: + opts (dict): dictionary of parameters + + Returns: + dict: dictionary of parameters from command line arguments + """ + # When pretending the user surely wants to see the output + if opts['pretend']: + opts['log_level'] = logging.INFO + + configure_logger(opts) + + # In case of an update read and parse setup.cfg + if opts['update']: + try: + opts = info.project(opts) + except Exception as e: + raise NoPyScaffoldProject from e + + # Save cli params for later updating + opts['cli_params'] = {'extensions': list(), 'args': dict()} + for extension in opts['extensions']: + opts['cli_params']['extensions'].append(extension.name) + if extension.args is not None: + opts['cli_params']['args'][extension.name] = extension.args + + # Strip (back)slash when added accidentally during update + opts['project'] = opts['project'].rstrip(os.sep) + + # Remove options with None values + opts = {k: v for k, v in opts.items() if v is not None} + return opts + + +def run_scaffold(opts): + """Actually scaffold the project, calling the python API + + Args: + opts (dict): command line options as dictionary + """ + api.create_project(opts) + if opts['update'] and not opts['force']: + note = "Update accomplished!\n" \ + "Please check if your setup.cfg still complies with:\n" \ + "https://pyscaffold.org/en/v{}/configuration.html" + base_version = parse_version(pyscaffold_version).base_version + print(note.format(base_version)) + + +def list_actions(opts): + """Do not create a project, just list actions considering extensions + + Args: + opts (dict): command line options as dictionary + """ + actions = api.discover_actions(opts.get('extensions', [])) + + print('Planned Actions:') + for action in actions: + print(ReportFormatter.SPACING + get_id(action)) + + +def main(args): + """Main entry point for external applications + + Args: + args ([str]): command line arguments + """ + utils.check_setuptools_version() + opts = parse_args(args) + opts = process_opts(opts) + opts['command'](opts) + + +@shell.shell_command_error2exit_decorator +@utils.exceptions2exit([RuntimeError]) +def run(): + """Entry point for console script""" + main(sys.argv[1:]) + + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/__init__.py b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/__init__.py new file mode 100644 index 00000000..2bc75f03 --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/__init__.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +""" +Contribution packages used by PyScaffold + +All packages inside ``contrib`` are external packages that come with their +own licences and are not part of the PyScaffold source code itself. +The reason for shipping these dependencies directly is to avoid problems in +the resolution of ``setup_requires`` dependencies that occurred more often +than not, see issues #71 and #72. + +Currently the contrib packages are: + +1) setuptools_scm v3.3.3 +2) pytest-runner 5.1 +3) configupdater 1.0 + +The packages/modules were just copied over. +""" + +# Following dummy definitions are here in case PyScaffold version < 3 +# is still installed and setuptools checks the registered entry_points. +SCM_HG_FILES_COMMAND = '' +SCM_GIT_FILES_COMMAND = '' + + +def warn_about_deprecated_pyscaffold(): + raise RuntimeError("A PyScaffold version less than 3.0 was detected, " + "please upgrade!") + + +def scm_find_files(*args, **kwargs): + warn_about_deprecated_pyscaffold() + + +def scm_parse_hg(*args, **kwargs): + warn_about_deprecated_pyscaffold() + + +def scm_parse_git(*args, **kwargs): + warn_about_deprecated_pyscaffold() + + +def scm_parse_archival(*args, **kwargs): + warn_about_deprecated_pyscaffold() + + +def scm_parse_pkginfo(*args, **kwargs): + warn_about_deprecated_pyscaffold() + + +def scm_guess_next_dev_version(*args, **kwargs): + warn_about_deprecated_pyscaffold() + + +def scm_postrelease_version(*args, **kwargs): + warn_about_deprecated_pyscaffold() + + +def scm_get_local_node_and_date(*args, **kwargs): + warn_about_deprecated_pyscaffold() + + +def scm_get_local_dirty_tag(*args, **kwargs): + warn_about_deprecated_pyscaffold() + + +def write_pbr_json(*args, **kwargs): + warn_about_deprecated_pyscaffold() diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/__pycache__/__init__.cpython-39.pyc b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 00000000..b295f806 Binary files /dev/null and b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/__pycache__/__init__.cpython-39.pyc differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/__pycache__/ptr.cpython-39.pyc b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/__pycache__/ptr.cpython-39.pyc new file mode 100644 index 00000000..0d0cce12 Binary files /dev/null and b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/__pycache__/ptr.cpython-39.pyc differ diff --git a/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/configupdater.py b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/configupdater.py new file mode 100644 index 00000000..f49db46f --- /dev/null +++ b/.eggs/PyScaffold-3.2.3-py3.9.egg/pyscaffold/contrib/configupdater.py @@ -0,0 +1,1074 @@ +"""Configuration file updater. + +A configuration file consists of sections, lead by a "[section]" header, +and followed by "name: value" entries, with continuations and such in +the style of RFC 822. + +The basic idea of ConfigUpdater is that a configuration file consists of +three kinds of building blocks: sections, comments and spaces for separation. +A section itself consists of three kinds of blocks: options, comments and +spaces. This gives us the corresponding data structures to describe a +configuration file. + +A general block object contains the lines which were parsed and make up +the block. If a block object was not changed then during writing the same +lines that were parsed will be used to express the block. In case a block, +e.g. an option, was changed, it is marked as `updated` and its values will +be transformed into a corresponding string during an update of a +configuration file. + + +.. note:: + + ConfigUpdater was created by starting from Python's ConfigParser source + code and changing it according to my needs. Thus this source code + is subject to the PSF License in a way but I am not a lawyer. +""" + +import io +import os +import re +import sys +from abc import ABC +from collections import OrderedDict as _default_dict +from collections.abc import MutableMapping +from configparser import (ConfigParser, DuplicateOptionError, + DuplicateSectionError, Error, + MissingSectionHeaderError, NoOptionError, + NoSectionError, ParsingError) + +__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError", + "NoOptionError", "NoConfigFileReadError", "ParsingError", + "MissingSectionHeaderError", "ConfigUpdater"] + + +class NoConfigFileReadError(Error): + """Raised when no configuration file was read but update requested.""" + def __init__(self): + super().__init__( + "No configuration file was yet read! Use .read(...) first.") + + +# Used in parser getters to indicate the default behaviour when a specific +# option is not found it to raise an exception. Created to enable 'None' as +# a valid fallback value. +_UNSET = object() + + +class Container(ABC): + """Abstract Mixin Class + """ + def __init__(self, **kwargs): + self._structure = list() + super().__init__(**kwargs) + + @property + def structure(self): + return self._structure + + @property + def last_item(self): + if self._structure: + return self._structure[-1] + else: + return None + + +class Block(ABC): + """Abstract Block type holding lines + + Block objects hold original lines from the configuration file and hold + a reference to a container wherein the object resides. + """ + def __init__(self, container=None, **kwargs): + self._container = container + self.lines = [] + self._updated = False + super().__init__(**kwargs) + + def __str__(self): + return ''.join(self.lines) + + def __len__(self): + return len(self.lines) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.lines == other.lines + else: + return False + + def add_line(self, line): + """Add a line to the current block + + Args: + line (str): one line to add + """ + self.lines.append(line) + return self + + @property + def container(self): + return self._container + + @property + def add_before(self): + """Returns a builder inserting a new block before the current block""" + idx = self._container.structure.index(self) + return BlockBuilder(self._container, idx) + + @property + def add_after(self): + """Returns a builder inserting a new block after the current block""" + idx = self._container.structure.index(self) + return BlockBuilder(self._container, idx+1) + + +class BlockBuilder(object): + """Builder that injects blocks at a given index position.""" + def __init__(self, container, idx): + self._container = container + self._idx = idx + + def comment(self, text, comment_prefix='#'): + """Creates a comment block + + Args: + text (str): content of comment without # + comment_prefix (str): character indicating start of comment + + Returns: + self for chaining + """ + comment = Comment(self._container) + if not text.startswith(comment_prefix): + text = "{} {}".format(comment_prefix, text) + if not text.endswith('\n'): + text = "{}{}".format(text, '\n') + comment.add_line(text) + self._container.structure.insert(self._idx, comment) + self._idx += 1 + return self + + def section(self, section): + """Creates a section block + + Args: + section (str or :class:`Section`): name of section or object + + Returns: + self for chaining + """ + if not isinstance(self._container, ConfigUpdater): + raise ValueError("Sections can only be added at section level!") + if isinstance(section, str): + # create a new section + section = Section(section, container=self._container) + elif not isinstance(section, Section): + raise ValueError("Parameter must be a string or Section type!") + if section.name in [block.name for block in self._container + if isinstance(block, Section)]: + raise DuplicateSectionError(section.name) + self._container.structure.insert(self._idx, section) + self._idx += 1 + return self + + def space(self, newlines=1): + """Creates a vertical space of newlines + + Args: + newlines (int): number of empty lines + + Returns: + self for chaining + """ + space = Space() + for line in range(newlines): + space.add_line('\n') + self._container.structure.insert(self._idx, space) + self._idx += 1 + return self + + def option(self, key, value=None, **kwargs): + """Creates a new option inside a section + + Args: + key (str): key of the option + value (str or None): value of the option + **kwargs: are passed to the constructor of :class:`Option` + + Returns: + self for chaining + """ + if not isinstance(self._container, Section): + raise ValueError("Options can only be added inside a section!") + option = Option(key, value, container=self._container, **kwargs) + option.value = value + self._container.structure.insert(self._idx, option) + self._idx += 1 + return self + + +class Comment(Block): + """Comment block""" + def __init__(self, container=None): + super().__init__(container=container) + + def __repr__(self): + return '' + + +class Space(Block): + """Vertical space block of new lines""" + def __init__(self, container=None): + super().__init__(container=container) + + def __repr__(self): + return '' + + +class Section(Block, Container, MutableMapping): + """Section block holding options + + Attributes: + name (str): name of the section + updated (bool): indicates name change or a new section + """ + def __init__(self, name, container, **kwargs): + self._name = name + self._structure = list() + self._updated = False + super().__init__(container=container, **kwargs) + + def add_option(self, entry): + """Add an Option object to the section + + Used during initial parsing mainly + + Args: + entry (Option): key value pair as Option object + """ + self._structure.append(entry) + return self + + def add_comment(self, line): + """Add a Comment object to the section + + Used during initial parsing mainly + + Args: + line (str): one line in the comment + """ + if not isinstance(self.last_item, Comment): + comment = Comment(self._structure) + self._structure.append(comment) + self.last_item.add_line(line) + return self + + def add_space(self, line): + """Add a Space object to the section + + Used during initial parsing mainly + + Args: + line (str): one line that defines the space, maybe whitespaces + """ + if not isinstance(self.last_item, Space): + space = Space(self._structure) + self._structure.append(space) + self.last_item.add_line(line) + return self + + def _get_option_idx(self, key): + idx = [i for i, entry in enumerate(self._structure) + if isinstance(entry, Option) and entry.key == key] + if idx: + return idx[0] + else: + raise ValueError + + def __str__(self): + if not self.updated: + s = super().__str__() + else: + s = "[{}]\n".format(self._name) + for entry in self._structure: + s += str(entry) + return s + + def __repr__(self): + return ''.format(self.name) + + def __getitem__(self, key): + if key not in self.options(): + raise KeyError(key) + return self._structure[self._get_option_idx(key=key)] + + def __setitem__(self, key, value): + if key in self: + option = self.__getitem__(key) + option.value = value + else: + option = Option(key, value, container=self) + option.value = value + self._structure.append(option) + + def __delitem__(self, key): + if key not in self.options(): + raise KeyError(key) + idx = self._get_option_idx(key=key) + del self._structure[idx] + + def __contains__(self, key): + return key in self.options() + + def __len__(self): + return len(self._structure) + + def __iter__(self): + """Return all entries, not just options""" + return self._structure.__iter__() + + def __eq__(self, other): + if isinstance(other, self.__class__): + return (self.name == other.name and + self._structure == other._structure) + else: + return False + + def option_blocks(self): + """Returns option blocks + + Returns: + list: list of :class:`Option` blocks + """ + return [entry for entry in self._structure + if isinstance(entry, Option)] + + def options(self): + """Returns option names + + Returns: + list: list of option names as strings + """ + return [option.key for option in self.option_blocks()] + + def to_dict(self): + """Transform to dictionary + + Returns: + dict: dictionary with same content + """ + return {key: self.__getitem__(key).value for key in self.options()} + + @property + def updated(self): + """Returns if the option was changed/updated""" + # if no lines were added, treat it as updated since we added it + return self._updated or not self.lines + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = str(value) + self._updated = True + + def set(self, option, value=None): + """Set an option for chaining. + + Args: + option (str): option name + value (str): value, default None + """ + option = self._container.optionxform(option) + if option in self.options(): + self.__getitem__(option).value = value + else: + self.__setitem__(option, value) + return self + + def insert_at(self, idx): + """Returns a builder inserting a new block at the given index + + Args: + idx (int): index where to insert + """ + return BlockBuilder(self, idx) + + +class Option(Block): + """Option block holding a key/value pair. + + Attributes: + key (str): name of the key + value (str): stored value + updated (bool): indicates name change or a new section + """ + def __init__(self, key, value, container, delimiter='=', + space_around_delimiters=True, line=None): + super().__init__(container=container) + self._key = key + self._values = [value] + self._value_is_none = value is None + self._delimiter = delimiter + self._value = None # will be filled after join_multiline_value + self._updated = False + self._multiline_value_joined = False + self._space_around_delimiters = space_around_delimiters + if line: + self.lines.append(line) + + def add_line(self, line): + super().add_line(line) + self._values.append(line.strip()) + + def _join_multiline_value(self): + if not self._multiline_value_joined and not self._value_is_none: + # do what `_join_multiline_value` in ConfigParser would do + self._value = '\n'.join(self._values).rstrip() + self._multiline_value_joined = True + + def __str__(self): + if not self.updated: + return super().__str__() + if self._value is None: + return "{}{}".format(self._key, '\n') + if self._space_around_delimiters: + # no space is needed if we use multi-line arguments + suffix = '' if str(self._value).startswith('\n') else ' ' + delim = " {}{}".format(self._delimiter, suffix) + else: + delim = self._delimiter + return "{}{}{}{}".format(self._key, delim, self._value, '\n') + + def __repr__(self): + return ''.format(self.key, self.value) + + @property + def updated(self): + """Returns if the option was changed/updated""" + # if no lines were added, treat it as updated since we added it + return self._updated or not self.lines + + @property + def key(self): + return self._key + + @key.setter + def key(self, value): + self._join_multiline_value() + self._key = value + self._updated = True + + @property + def value(self): + self._join_multiline_value() + return self._value + + @value.setter + def value(self, value): + self._updated = True + self._multiline_value_joined = True + self._value = value + self._values = [value] + + def set_values(self, values, separator='\n', indent=4*' '): + """Sets the value to a given list of options, e.g. multi-line values + + Args: + values (list): list of values + separator (str): separator for values, default: line separator + indent (str): indentation depth in case of line separator + """ + self._updated = True + self._multiline_value_joined = True + self._values = values + if separator == '\n': + values.insert(0, '') + separator = separator + indent + self._value = separator.join(values) + + +class ConfigUpdater(Container, MutableMapping): + """Parser for updating configuration files. + + ConfigUpdater follows the API of ConfigParser with some differences: + * inline comments are treated as part of a key's value, + * only a single config file can be updated at a time, + * empty lines in values are not valid, + * the original case of sections and keys are kept, + * control over the position of a new section/key. + + Following features are **deliberately not** implemented: + + * interpolation of values, + * propagation of parameters from the default section, + * conversions of values, + * passing key/value-pairs with ``default`` argument, + * non-strict mode allowing duplicate sections and keys. + """ + # Regular expressions for parsing section headers and options + _SECT_TMPL = r""" + \[ # [ + (?P
[^]]+) # very permissive! + \] # ] + """ + _OPT_TMPL = r""" + (?P