[Python-modules-commits] [cookiecutter] 01/06: Import cookiecutter_1.3.0.orig.tar.gz

Vincent Bernat bernat at moszumanska.debian.org
Sat Dec 5 16:25:55 UTC 2015


This is an automated email from the git hooks/post-receive script.

bernat pushed a commit to branch master
in repository cookiecutter.

commit 1016a2016d22987f1c56f7208ef8693ab9101f96
Author: Vincent Bernat <bernat at debian.org>
Date:   Sat Dec 5 17:09:27 2015 +0100

    Import cookiecutter_1.3.0.orig.tar.gz
---
 CONTRIBUTING.rst                                   |   8 ++
 HISTORY.rst                                        |  41 +++++++++
 Makefile                                           |  18 +++-
 README.rst                                         |  16 ++++
 appveyor.yml                                       |  14 ++-
 cookiecutter/__init__.py                           |   2 +-
 cookiecutter/cli.py                                |  30 +++++-
 cookiecutter/config.py                             |  39 +++++---
 cookiecutter/exceptions.py                         |   6 ++
 cookiecutter/generate.py                           |  25 +++--
 cookiecutter/hooks.py                              |  15 ++-
 cookiecutter/main.py                               |   8 +-
 cookiecutter/vcs.py                                |   1 +
 docs/advanced_usage.rst                            |  48 +++++++++-
 docs/conf.py                                       |   2 +-
 docs/cookiecutter.rst                              |   8 --
 docs/requirements.txt                              |   2 +
 docs/types_of_contributions.rst                    |   8 ++
 setup.cfg                                          |  11 +++
 setup.py                                           |  19 ++--
 tests/hooks-abort-render/hooks/post_gen_project.py |  12 +++
 tests/hooks-abort-render/hooks/pre_gen_project.py  |  12 +++
 .../{{cookiecutter.repo_dir}}/README.rst           |   2 +
 tests/skipif_markers.py                            |  14 +--
 tests/test_abort_generate_on_hook_error.py         |  46 ++++++++++
 tests/test_cli.py                                  | 102 ++++++++++++++++++++-
 tests/test_cookiecutters.py                        |   7 +-
 tests/test_get_user_config.py                      |  53 +++++++++++
 tests/test_hooks.py                                |  16 +++-
 tests/test_more_cookiecutters.py                   |   7 +-
 tests/test_vcs.py                                  |  13 +++
 31 files changed, 531 insertions(+), 74 deletions(-)

diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 9493126..cf36c48 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -68,6 +68,14 @@ Cookiecutter could always use more documentation, whether as part of the
 official Cookiecutter docs, in docstrings, or even on the web in blog posts,
 articles, and such.
 
+If you want to review your changes on the documentation locally, you can do::
+
+    pip install -r docs/requirements.txt
+    make servedocs
+
+This will compile the documentation, open it in your browser and start
+watching the files for changes, recompiling as you save.
+
 Submit Feedback
 ~~~~~~~~~~~~~~~
 
diff --git a/HISTORY.rst b/HISTORY.rst
index 25bfce5..96f7636 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -3,6 +3,47 @@
 History
 -------
 
+1.3.0 (2015-11-10) Pumpkin Spice
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The goal of this release is to extend the user config feature and to make hook execution more robust.
+
+New Features:
+
+* Abort project generation if ``pre_gen_project`` or ``post_gen_project`` hook scripts fail, thanks to `@eliasdorneles`_ (#464, #549)
+* Extend user config capabilities with additional cli options ``--config-file``
+  and ``--default-config`` and environment variable ``COOKIECUTTER_CONFIG``, thanks to `@jhermann`_, `@pfmoore`_, and `@hackebrot`_ (#258, #424, #565)
+
+Bug Fixes:
+
+* Fixed conditional dependencies for wheels in setup.py, thanks to `@hackebrot`_ (#557, #568)
+* Reverted skipif markers to use correct reasons (bug fixed in pytest), thanks to `@hackebrot`_ (#574)
+
+
+Other Changes:
+
+* Improved path and documentation for rendering the Sphinx documentation, thanks to `@eliasdorneles`_ and `@hackebrot`_ (#562, #583)
+* Added additional help entrypoints, thanks to `@michaeljoseph`_ (#563, #492)
+* Added Two Scoops Academy to the README, thanks to `@hackebrot`_ (#576)
+* Now handling trailing slash on URL, thanks to `@ramiroluz`_ (#573, #546)
+* Support for testing x86 and x86-64 architectures on appveyor, thanks to `@maiksensi`_ (#567)
+* Made tests work without installing Cookiecutter, thanks to `@vincentbernat`_ (#550)
+* Encoded the result of the hook template to utf8, thanks to `@ionelmc`_ (#577. #578)
+* Added test for _run_hook_from_repo_dir, thanks to `@hackebrot`_ (#579, #580)
+* Implemented bumpversion, thanks to `@hackebrot`_ (#582)
+* Added more cookiecutter templates to the mix:
+
+  * `cookiecutter-octoprint-plugin`_ by `@foosel`_ (#560)
+  * `wagtail-cookiecutter-foundation`_ by `@chrisdev`_, et al. (#566)
+
+.. _`@foosel`: https://github.com/foosel
+.. _`@chrisdev`: https://github.com/chrisdev
+.. _`@jhermann`: https://github.com/jhermann
+
+.. _`cookiecutter-octoprint-plugin`: https://github.com/OctoPrint/cookiecutter-octoprint-plugin
+.. _`wagtail-cookiecutter-foundation`: https://github.com/chrisdev/wagtail-cookiecutter-foundation
+
+
 1.2.1 (2015-10-18) Zimtsterne
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/Makefile b/Makefile
index 9e0208b..d214493 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,15 @@
 .PHONY: clean-pyc clean-build docs
+define BROWSER_PYSCRIPT
+import os, webbrowser, sys
+try:
+	from urllib import pathname2url
+except:
+	from urllib.request import pathname2url
+
+webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1])))
+endef
+export BROWSER_PYSCRIPT
+BROWSER := python -c "$$BROWSER_PYSCRIPT"
 
 help:
 	@echo "clean-build - remove build artifacts"
@@ -35,7 +46,7 @@ test-all:
 
 coverage:
 	tox -e cov-report
-	open htmlcov/index.html
+	$(BROWSER) htmlcov/index.html
 
 docs:
 	rm -f docs/cookiecutter.rst
@@ -44,7 +55,10 @@ docs:
 	$(MAKE) -C docs clean
 	$(MAKE) -C docs html
 	make contributing
-	open docs/_build/html/index.html
+	$(BROWSER) docs/_build/html/index.html
+
+servedocs: docs
+	watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D .
 
 release: clean
 	python setup.py sdist bdist_wheel upload
diff --git a/README.rst b/README.rst
index 7456660..c9ce169 100644
--- a/README.rst
+++ b/README.rst
@@ -171,6 +171,7 @@ Python
 * `cookiecutter-tapioca`_: A Template for building `tapioca-wrapper`_ based web API wrappers (clients).
 * `cookiecutter-sublime-text-3-plugin`_: Sublime Text 3 plugin template with custom settings, commands, key bindings and main menu.
 * `cookiecutter-muffin`_: A Muffin template with Bootstrap 3, starter templates, and working user registration.
+* `cookiecutter-octoprint-plugin`_: A template for building plugins for `OctoPrint`_.
 
 Python-Django
 ^^^^^^^^^^^^^
@@ -186,6 +187,7 @@ Python-Django
 * `cookiecutter-django-paas`_: Django template ready to use in SAAS platforms like Heroku, OpenShift, etc..
 * `cookiecutter-django-rest-framework`_: A template for creating reusable Django REST Framework packages.
 * `cookiecutter-wagtail`_ : A cookiecutter template for `Wagtail`_ CMS based sites.
+* `wagtail-cookiecutter-foundation`_: A complete template for Wagtail CMS projects featuring Zurb Foundation 5, ansible provisioning and deployment , front-end dependency management with bower, modular apps to get your site up and running including photo_gallery, RSS feed etc.
 
 C
 ~~
@@ -294,6 +296,9 @@ HTML
 .. _`cookiecutter-muffin`: https://github.com/drgarcia1986/cookiecutter-muffin
 .. _`cookiecutter-wagtail`: https://github.com/torchbox/cookiecutter-wagtail
 .. _`Wagtail`: https://github.com/torchbox/wagtail
+.. _`cookiecutter-octoprint-plugin`: https://github.com/OctoPrint/cookiecutter-octoprint-plugin
+.. _`OctoPrint`: https://github.com/foosel/OctoPrint
+.. _`wagtail-cookiecutter-foundation`: https://github.com/chrisdev/wagtail-cookiecutter-foundation
 
 Scala
 ~~~~~
@@ -413,6 +418,17 @@ Waiting for a response to an issue/question?
 * Need a fix/feature/release/help urgently, and can't wait? `@audreyr`_ is
   available for hire for consultation or custom development.
 
+Support This Project
+--------------------
+
+This project is maintained by volunteers. Support their efforts by spreading the word about:
+
+.. image:: https://s3.amazonaws.com/tsacademy/images/tsa-logo-250x60-transparent-01.png
+   :name: Two Scoops Academy
+   :align: center
+   :alt: Two Scoops Academy
+   :target: http://www.twoscoops.academy/
+
 Code of Conduct
 ---------------
 
diff --git a/appveyor.yml b/appveyor.yml
index 71450da..eaad642 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -6,19 +6,31 @@ environment:
     - PYTHON: "C:\\Python27"
       TOX_ENV: "py27"
 
+    - PYTHON: "C:\\Python27-x64"
+      TOX_ENV: "py27"
+
     - PYTHON: "C:\\Python33"
       TOX_ENV: "py33"
 
+    - PYTHON: "C:\\Python33-x64"
+      TOX_ENV: "py33"
+
     - PYTHON: "C:\\Python34"
       TOX_ENV: "py34"
 
+    - PYTHON: "C:\\Python34-x64"
+      TOX_ENV: "py34"
+
     - PYTHON: "C:\\Python35"
       TOX_ENV: "py35"
 
+    - PYTHON: "C:\\Python35-x64"
+      TOX_ENV: "py35"
+
 
 init:
   - "%PYTHON%/python -V"
-  - "%PYTHON%/python -c \"import struct;print( 8 * struct.calcsize(\'P\'))\""
+  - "%PYTHON%/python -c \"import struct;print(8 * struct.calcsize(\'P\'))\""
 
 install:
   - "%PYTHON%/Scripts/easy_install -U pip"
diff --git a/cookiecutter/__init__.py b/cookiecutter/__init__.py
index caf230e..53a354a 100755
--- a/cookiecutter/__init__.py
+++ b/cookiecutter/__init__.py
@@ -8,4 +8,4 @@ cookiecutter
 Main package for Cookiecutter.
 """
 
-__version__ = '1.2.1'
+__version__ = '1.3.0'
diff --git a/cookiecutter/cli.py b/cookiecutter/cli.py
index 6f53e24..8825a8f 100755
--- a/cookiecutter/cli.py
+++ b/cookiecutter/cli.py
@@ -15,9 +15,10 @@ import logging
 import click
 
 from cookiecutter import __version__
+from cookiecutter.config import USER_CONFIG_PATH
 from cookiecutter.main import cookiecutter
 from cookiecutter.exceptions import (
-    OutputDirExistsException, InvalidModeException
+    OutputDirExistsException, InvalidModeException, FailedHookException
 )
 
 logger = logging.getLogger(__name__)
@@ -30,7 +31,7 @@ def version_msg():
     return message.format(location, python_version)
 
 
- at click.command()
+ at click.command(context_settings=dict(help_option_names=[u'-h', u'--help']))
 @click.version_option(__version__, u'-V', u'--version', message=version_msg())
 @click.argument(u'template')
 @click.option(
@@ -59,8 +60,16 @@ def version_msg():
     u'-o', u'--output-dir', default='.', type=click.Path(),
     help=u'Where to output the generated project dir into'
 )
+ at click.option(
+    u'--config-file', type=click.Path(), default=USER_CONFIG_PATH,
+    help=u'User configuration file'
+)
+ at click.option(
+    u'--default-config', is_flag=True,
+    help=u'Do not load a config file. Use the defaults instead'
+)
 def main(template, no_input, checkout, verbose, replay, overwrite_if_exists,
-         output_dir):
+         output_dir, config_file, default_config):
     """Create a project from a Cookiecutter project template (TEMPLATE)."""
     if verbose:
         logging.basicConfig(
@@ -75,15 +84,26 @@ def main(template, no_input, checkout, verbose, replay, overwrite_if_exists,
         )
 
     try:
+        # If you _need_ to support a local template in a directory
+        # called 'help', use a qualified path to the directory.
+        if template == u'help':
+            click.echo(click.get_current_context().get_help())
+            sys.exit(0)
+
+        user_config = None if default_config else config_file
+
         cookiecutter(
             template, checkout, no_input,
             replay=replay,
             overwrite_if_exists=overwrite_if_exists,
-            output_dir=output_dir
+            output_dir=output_dir,
+            config_file=user_config
         )
-    except (OutputDirExistsException, InvalidModeException) as e:
+    except (OutputDirExistsException,
+            InvalidModeException, FailedHookException) as e:
         click.echo(e)
         sys.exit(1)
 
+
 if __name__ == "__main__":
     main()
diff --git a/cookiecutter/config.py b/cookiecutter/config.py
index 00c8113..edfcec4 100755
--- a/cookiecutter/config.py
+++ b/cookiecutter/config.py
@@ -25,6 +25,8 @@ from .exceptions import InvalidConfiguration
 
 logger = logging.getLogger(__name__)
 
+USER_CONFIG_PATH = os.path.expanduser('~/.cookiecutterrc')
+
 DEFAULT_CONFIG = {
     'cookiecutters_dir': os.path.expanduser('~/.cookiecutters/'),
     'replay_dir': os.path.expanduser('~/.cookiecutter_replay/'),
@@ -57,15 +59,30 @@ def get_config(config_path):
     return config_dict
 
 
-def get_user_config():
-    """
-    Retrieve config from the user's ~/.cookiecutterrc, if it exists.
-    Otherwise, return None.
+def get_user_config(config_file=USER_CONFIG_PATH):
+    """Retrieve the config from a file or return the defaults if None is
+    passed. If an environment variable `COOKIECUTTER_CONFIG` is set up, try
+    to load its value. Otherwise fall back to a default file or config.
     """
-
-    # TODO: test on windows...
-    USER_CONFIG_PATH = os.path.expanduser('~/.cookiecutterrc')
-
-    if os.path.exists(USER_CONFIG_PATH):
-        return get_config(USER_CONFIG_PATH)
-    return copy.copy(DEFAULT_CONFIG)
+    # Do NOT load a config. Return defaults instead.
+    if config_file is None:
+        return copy.copy(DEFAULT_CONFIG)
+
+    # Load the given config file
+    if config_file and config_file is not USER_CONFIG_PATH:
+        return get_config(config_file)
+
+    try:
+        # Does the user set up a config environment variable?
+        env_config_file = os.environ['COOKIECUTTER_CONFIG']
+    except KeyError:
+        # Load an optional user config if it exists
+        # otherwise return the defaults
+        if os.path.exists(USER_CONFIG_PATH):
+            return get_config(USER_CONFIG_PATH)
+        else:
+            return copy.copy(DEFAULT_CONFIG)
+    else:
+        # There is a config environment variable. Try to load it.
+        # Do not check for existence, so invalid file paths raise an error.
+        return get_config(env_config_file)
diff --git a/cookiecutter/exceptions.py b/cookiecutter/exceptions.py
index 0ad6b58..415f99e 100755
--- a/cookiecutter/exceptions.py
+++ b/cookiecutter/exceptions.py
@@ -81,3 +81,9 @@ class InvalidModeException(CookiecutterException):
     Raised when cookiecutter is called with both `no_input==True` and
     `replay==True` at the same time.
     """
+
+
+class FailedHookException(CookiecutterException):
+    """
+    Raised when a hook script fails
+    """
diff --git a/cookiecutter/generate.py b/cookiecutter/generate.py
index 94e6e94..7a4d461 100755
--- a/cookiecutter/generate.py
+++ b/cookiecutter/generate.py
@@ -24,10 +24,11 @@ from binaryornot.check import is_binary
 from .exceptions import (
     NonTemplatedInputDirException,
     ContextDecodingException,
+    FailedHookException,
     OutputDirExistsException
 )
 from .find import find_template
-from .utils import make_sure_path_exists, work_in
+from .utils import make_sure_path_exists, work_in, rmtree
 from .hooks import run_hook
 
 
@@ -222,6 +223,20 @@ def ensure_dir_is_templated(dirname):
         raise NonTemplatedInputDirException
 
 
+def _run_hook_from_repo_dir(repo_dir, hook_name, project_dir, context):
+    """
+    Run hook from repo directory, cleaning up project directory if hook fails
+    """
+    with work_in(repo_dir):
+        try:
+            run_hook(hook_name, project_dir, context)
+        except FailedHookException:
+            rmtree(project_dir)
+            logging.error("Stopping generation because %s"
+                          " hook script didn't exit sucessfully" % hook_name)
+            raise
+
+
 def generate_files(repo_dir, context=None, output_dir='.',
                    overwrite_if_exists=False):
     """
@@ -255,9 +270,7 @@ def generate_files(repo_dir, context=None, output_dir='.',
     project_dir = os.path.abspath(project_dir)
     logging.debug('project_dir is {0}'.format(project_dir))
 
-    # run pre-gen hook from repo_dir
-    with work_in(repo_dir):
-        run_hook('pre_gen_project', project_dir, context)
+    _run_hook_from_repo_dir(repo_dir, 'pre_gen_project', project_dir, context)
 
     with work_in(template_dir):
         env = Environment(keep_trailing_newline=True)
@@ -313,8 +326,6 @@ def generate_files(repo_dir, context=None, output_dir='.',
                 logging.debug('f is {0}'.format(f))
                 generate_file(project_dir, infile, context, env)
 
-    # run post-gen hook from repo_dir
-    with work_in(repo_dir):
-        run_hook('post_gen_project', project_dir, context)
+    _run_hook_from_repo_dir(repo_dir, 'post_gen_project', project_dir, context)
 
     return project_dir
diff --git a/cookiecutter/hooks.py b/cookiecutter/hooks.py
index 37d1764..254bc2e 100755
--- a/cookiecutter/hooks.py
+++ b/cookiecutter/hooks.py
@@ -18,12 +18,15 @@ import tempfile
 from jinja2 import Template
 
 from cookiecutter import utils
+from .exceptions import FailedHookException
+
 
 _HOOKS = [
     'pre_gen_project',
     'post_gen_project',
     # TODO: other hooks should be listed here
 ]
+EXIT_SUCCESS = 0
 
 
 def find_hooks():
@@ -67,7 +70,10 @@ def run_script(script_path, cwd='.'):
         shell=run_thru_shell,
         cwd=cwd
     )
-    proc.wait()
+    exit_status = proc.wait()
+    if exit_status != EXIT_SUCCESS:
+        raise FailedHookException(
+            "Hook script failed (exit status: %d)" % exit_status)
 
 
 def run_script_with_context(script_path, cwd, context):
@@ -84,10 +90,11 @@ def run_script_with_context(script_path, cwd, context):
 
     with tempfile.NamedTemporaryFile(
         delete=False,
-        mode='w',
+        mode='wb',
         suffix=extension
     ) as temp:
-        temp.write(Template(contents).render(**context))
+        output = Template(contents).render(**context)
+        temp.write(output.encode('utf-8'))
 
     run_script(temp.name, cwd)
 
@@ -104,4 +111,4 @@ def run_hook(hook_name, project_dir, context):
     if script is None:
         logging.debug('No hooks found')
         return
-    return run_script_with_context(script, project_dir, context)
+    run_script_with_context(script, project_dir, context)
diff --git a/cookiecutter/main.py b/cookiecutter/main.py
index eae455b..e6d858f 100755
--- a/cookiecutter/main.py
+++ b/cookiecutter/main.py
@@ -16,7 +16,7 @@ import logging
 import os
 import re
 
-from .config import get_user_config
+from .config import get_user_config, USER_CONFIG_PATH
 from .exceptions import InvalidModeException
 from .prompt import prompt_for_config
 from .generate import generate_context, generate_files
@@ -71,7 +71,8 @@ def expand_abbreviations(template, config_dict):
 
 def cookiecutter(
         template, checkout=None, no_input=False, extra_context=None,
-        replay=False, overwrite_if_exists=False, output_dir='.'):
+        replay=False, overwrite_if_exists=False, output_dir='.',
+        config_file=USER_CONFIG_PATH):
     """
     API equivalent to using Cookiecutter at the command line.
 
@@ -84,6 +85,7 @@ def cookiecutter(
     :param: overwrite_if_exists: Overwrite the contents of output directory
         if it exists
     :param output_dir: Where to output the generated project dir into.
+    :param config_file: User configuration file path.
     """
     if replay and ((no_input is not False) or (extra_context is not None)):
         err_msg = (
@@ -94,7 +96,7 @@ def cookiecutter(
 
     # Get user config from ~/.cookiecutterrc or equivalent
     # If no config file, sensible defaults from config.DEFAULT_CONFIG are used
-    config_dict = get_user_config()
+    config_dict = get_user_config(config_file=config_file)
 
     template = expand_abbreviations(template, config_dict)
 
diff --git a/cookiecutter/vcs.py b/cookiecutter/vcs.py
index 269fee3..4f7dbd9 100755
--- a/cookiecutter/vcs.py
+++ b/cookiecutter/vcs.py
@@ -103,6 +103,7 @@ def clone(repo_url, checkout=None, clone_to_dir=".", no_input=False):
         msg = "'{0}' is not installed.".format(repo_type)
         raise VCSNotInstalled(msg)
 
+    repo_url = repo_url.rstrip('/')
     tail = os.path.split(repo_url)[1]
     if repo_type == 'git':
         repo_dir = os.path.normpath(os.path.join(clone_to_dir,
diff --git a/docs/advanced_usage.rst b/docs/advanced_usage.rst
index 2809db7..5d762de 100644
--- a/docs/advanced_usage.rst
+++ b/docs/advanced_usage.rst
@@ -34,11 +34,55 @@ hooks, as these can be run on any platform. However, if you intend for your
 template to only be run on a single platform, a shell script (or `.bat` file
 on Windows) can be a quicker alternative.
 
+.. note::
+    Make sure your hook scripts work in a robust manner. If a hook script fails
+    (that is, `if it finishes with a nonzero exit status
+    <https://docs.python.org/3/library/sys.html#sys.exit>`_), the project
+    generation will stop and the generated directory will be cleaned up.
+
+Example: Validating template variables
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Here is an example of script that validates a template variable
+before generating the project, to be used as ``hooks/pre_gen_project.py``:
+
+.. code-block:: python
+
+    import re
+    import sys
+
+
+    MODULE_REGEX = r'^[_a-zA-Z][_a-zA-Z0-9]+$'
+
+    module_name = '{{ cookiecutter.module_name }}'
+
+    if not re.match(MODULE_REGEX, module_name):
+        print('ERROR: %s is not a valid Python module name!' % module_name)
+
+        # exits with status 1 to indicate failure
+        sys.exit(1)
+
+
 User Config (0.7.0+)
 ----------------------
 
-If you use Cookiecutter a lot, you'll find it useful to have a
-`.cookiecutterrc` file in your home directory like this:
+If you use Cookiecutter a lot, you'll find it useful to have a user config
+file. By default Cookiecutter tries to retrieve settings from a `.cookiecutterrc`
+file in your home directory.
+
+From version 1.3.0 you can also specify a config file on the command line via ``--config-file``::
+
+    $ cookiecutter --config-file /home/audreyr/my-custom-config.yaml cookiecutter-pypackage
+
+Or you can set the ``COOKIECUTTER_CONFIG`` environment variable::
+
+    $ export COOKIECUTTER_CONFIG=/home/audreyr/my-custom-config.yaml
+
+If you wish to stick to the built-in config and not load any user config file at all,
+use the cli option ``--default-config`` instead. Preventing Cookiecutter from loading
+user settings is crucial for writing integration tests in an isolated environment.
+
+Example user config:
 
 .. code-block:: yaml
 
diff --git a/docs/conf.py b/docs/conf.py
index e09f31b..ae5b80e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -132,7 +132,7 @@ pygments_style = 'sphinx'
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'default'
+html_theme = 'sphinx_rtd_theme'
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
diff --git a/docs/cookiecutter.rst b/docs/cookiecutter.rst
index 0ff329a..7c91eff 100644
--- a/docs/cookiecutter.rst
+++ b/docs/cookiecutter.rst
@@ -12,14 +12,6 @@ cookiecutter.cli module
     :undoc-members:
     :show-inheritance:
 
-cookiecutter.compat module
---------------------------
-
-.. automodule:: cookiecutter.compat
-    :members:
-    :undoc-members:
-    :show-inheritance:
-
 cookiecutter.config module
 --------------------------
 
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..fc1a115
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,2 @@
+watchdog==0.8.3
+sphinx-rtd-theme==0.1.9
diff --git a/docs/types_of_contributions.rst b/docs/types_of_contributions.rst
index ebff7ff..0230520 100644
--- a/docs/types_of_contributions.rst
+++ b/docs/types_of_contributions.rst
@@ -51,6 +51,14 @@ Cookiecutter could always use more documentation, whether as part of the
 official Cookiecutter docs, in docstrings, or even on the web in blog posts,
 articles, and such.
 
+If you want to review your changes on the documentation locally, you can do::
+
+    pip install -r docs/requirements.txt
+    make servedocs
+
+This will compile the documentation, open it in your browser and start
+watching the files for changes, recompiling as you save.
+
 Submit Feedback
 ~~~~~~~~~~~~~~~
 
diff --git a/setup.cfg b/setup.cfg
index f5f139e..6ab4c54 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,16 @@
+[bumpversion]
+current_version = 1.3.0
+commit = True
+tag = True
+tag_name = {new_version}
+
+[bumpversion:file:setup.py]
+
+[bumpversion:file:cookiecutter/__init__.py]
+
 [flake8]
 ignore = E731
 
 [bdist_wheel]
 universal = 1
+
diff --git a/setup.py b/setup.py
index eff59cf..e6e63b9 100755
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,6 @@
 #!/usr/bin/env python
 
 import os
-import platform
 import sys
 
 try:
@@ -9,7 +8,7 @@ try:
 except ImportError:
     from distutils.core import setup
 
-version = "1.2.1"
+version = "1.3.0"
 
 if sys.argv[-1] == 'publish':
     os.system('python setup.py sdist upload')
@@ -35,14 +34,6 @@ requirements = [
     'whichcraft>=0.1.1'
 ]
 
-# Use PyYAML for 2.7 on Windows, ruamel.yaml everywhere else
-PY2 = sys.version_info[0] == 2
-windows = platform.system().lower().startswith('windows')
-if PY2 and windows:
-    requirements.append('PyYAML>=3.10')
-else:
-    requirements.append('ruamel.yaml>=0.10.12')
-
 long_description = readme + '\n\n' + history
 
 if sys.argv[-1] == 'readme':
@@ -71,6 +62,14 @@ setup(
     },
     include_package_data=True,
     install_requires=requirements,
+    extras_require={
+        ':sys_platform=="win32" and python_version=="2.7"': [
+            'PyYAML>=3.10'
+        ],
+        ':sys_platform!="win32" or python_version!="2.7"': [
+            'ruamel.yaml>=0.10.12'
+        ]
+    },
     license='BSD',
     zip_safe=False,
     classifiers=[
diff --git a/tests/hooks-abort-render/hooks/post_gen_project.py b/tests/hooks-abort-render/hooks/post_gen_project.py
new file mode 100644
index 0000000..3816f29
--- /dev/null
+++ b/tests/hooks-abort-render/hooks/post_gen_project.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# flake8: noqa
+
+import sys
+
+{% if cookiecutter.abort_post_gen == "yes" %}
+sys.exit(1)
+{% else %}
+sys.exit(0)
+{% endif %}
diff --git a/tests/hooks-abort-render/hooks/pre_gen_project.py b/tests/hooks-abort-render/hooks/pre_gen_project.py
new file mode 100644
index 0000000..62da436
--- /dev/null
+++ b/tests/hooks-abort-render/hooks/pre_gen_project.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# flake8: noqa
+
+import sys
+
+{% if cookiecutter.abort_pre_gen == "yes" %}
+sys.exit(1)
+{% else %}
+sys.exit(0)
+{% endif %}
diff --git a/tests/hooks-abort-render/{{cookiecutter.repo_dir}}/README.rst b/tests/hooks-abort-render/{{cookiecutter.repo_dir}}/README.rst
new file mode 100644
index 0000000..6dfb07b
--- /dev/null
+++ b/tests/hooks-abort-render/{{cookiecutter.repo_dir}}/README.rst
@@ -0,0 +1,2 @@
+{{cookiecutter.repo_name}}
+==========================
diff --git a/tests/skipif_markers.py b/tests/skipif_markers.py
index 7b212fb..72b50d7 100644
--- a/tests/skipif_markers.py
+++ b/tests/skipif_markers.py
@@ -26,14 +26,10 @@ except KeyError:
 else:
     no_network = True
 
-# For some reason pytest incorrectly uses the first reason text regardless of
-# which condition matches. Using a unified message for now
-# travis_reason = 'Works locally with tox but fails on Travis.'
-# no_network_reason = 'Needs a network connection to GitHub.'
-reason = (
-    'Fails on Travis or else there is no network connection to '
-    'GitHub/Bitbucket.'
+skipif_travis = pytest.mark.skipif(
+    travis, reason='Works locally with tox but fails on Travis.'
 )
 
-skipif_travis = pytest.mark.skipif(travis, reason=reason)
-skipif_no_network = pytest.mark.skipif(no_network, reason=reason)
+skipif_no_network = pytest.mark.skipif(
+    no_network, reason='Needs a network connection to GitHub/Bitbucket.'
+)
diff --git a/tests/test_abort_generate_on_hook_error.py b/tests/test_abort_generate_on_hook_error.py
new file mode 100644
index 0000000..34e5420
--- /dev/null
+++ b/tests/test_abort_generate_on_hook_error.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
+from cookiecutter import generate
+from cookiecutter import exceptions
+
+
+ at pytest.mark.usefixtures('clean_system')
+def test_pre_gen_hook(tmpdir):
+    context = {
+        'cookiecutter': {
+            "repo_dir": "foobar",
+            "abort_pre_gen": "yes",
+            "abort_post_gen": "no"
+        }
+    }
+
+    with pytest.raises(exceptions.FailedHookException):
+        generate.generate_files(
+            repo_dir='tests/hooks-abort-render',
+            context=context,
+            output_dir=str(tmpdir)
+        )
+
+    assert not tmpdir.join('foobar').isdir()
+
+
+ at pytest.mark.usefixtures('clean_system')
+def test_post_gen_hook(tmpdir):
+    context = {
+        'cookiecutter': {
+            "repo_dir": "foobar",
+            "abort_pre_gen": "no",
+            "abort_post_gen": "yes"
+        }
+    }
+
+    with pytest.raises(exceptions.FailedHookException):
+        generate.generate_files(
+            repo_dir='tests/hooks-abort-render',
+            context=context,
+            output_dir=str(tmpdir)
+        )
+
+    assert not tmpdir.join('foobar').isdir()
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 60f8795..383d7ea 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -5,7 +5,7 @@ from click.testing import CliRunner
 
 from cookiecutter.cli import main
 from cookiecutter.main import cookiecutter
-from cookiecutter import utils
+from cookiecutter import utils, config
 
 runner = CliRunner()
 
@@ -80,7 +80,8 @@ def test_cli_replay(mocker):
         False,
         replay=True,
         overwrite_if_exists=False,
-        output_dir='.'
+        output_dir='.',
+        config_file=config.USER_CONFIG_PATH
     )
 
 
@@ -114,7 +115,8 @@ def test_cli_exit_on_noinput_and_replay(mocker):
         True,
         replay=True,
         overwrite_if_exists=False,
-        output_dir='.'
+        output_dir='.',
+        config_file=config.USER_CONFIG_PATH
     )
 
 
@@ -147,7 +149,8 @@ def test_run_cookiecutter_on_overwrite_if_exists_and_replay(
         False,
         replay=True,
         overwrite_if_exists=True,
-        output_dir='.'
+        output_dir='.',
+        config_file=config.USER_CONFIG_PATH
     )
 
 
@@ -201,5 +204,94 @@ def test_cli_output_dir(mocker, output_dir_flag, output_dir):
         False,
         replay=False,
         overwrite_if_exists=False,
-        output_dir=output_dir
+        output_dir=output_dir,
+        config_file=config.USER_CONFIG_PATH
+    )
+
+
+ at pytest.fixture(params=['-h', '--help', 'help'])
+def help_cli_flag(request):
+    return request.param
+
+
+def test_cli_help(help_cli_flag):
+    result = runner.invoke(main, [help_cli_flag])
+    assert result.exit_code == 0
+    assert result.output.startswith('Usage')
+
+
+ at pytest.fixture
+def user_config_path(tmpdir):
+    return str(tmpdir.join('tests/config.yaml'))
+
+
+def test_user_config(mocker, user_config_path):
+    mock_cookiecutter = mocker.patch(
+        'cookiecutter.cli.cookiecutter'
+    )
+
+    template_path = 'tests/fake-repo-pre/'
+    result = runner.invoke(main, [
+        template_path,
+        '--config-file',
+        user_config_path
+    ])
+
+    assert result.exit_code == 0
+    mock_cookiecutter.assert_called_once_with(
+        template_path,
+        None,
+        False,
+        replay=False,
+        overwrite_if_exists=False,
+        output_dir='.',
+        config_file=user_config_path
+    )
+
+
+def test_default_user_config_overwrite(mocker, user_config_path):
+    mock_cookiecutter = mocker.patch(
+        'cookiecutter.cli.cookiecutter'
+    )
+
+    template_path = 'tests/fake-repo-pre/'
+    result = runner.invoke(main, [
+        template_path,
+        '--config-file',
+        user_config_path,
+        '--default-config'
+    ])
+
+    assert result.exit_code == 0
+    mock_cookiecutter.assert_called_once_with(
+        template_path,
+        None,
+        False,
+        replay=False,
+        overwrite_if_exists=False,
+        output_dir='.',
+        config_file=None
+    )
+
+
+def test_default_user_config(mocker):
+    mock_cookiecutter = mocker.patch(
+        'cookiecutter.cli.cookiecutter'
+    )
+
+    template_path = 'tests/fake-repo-pre/'
+    result = runner.invoke(main, [
+        template_path,
+        '--default-config'
+    ])
+
+    assert result.exit_code == 0
+    mock_cookiecutter.assert_called_once_with(
+        template_path,
+        None,
+        False,
+        replay=False,
+        overwrite_if_exists=False,
+        output_dir='.',
... 182 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/cookiecutter.git



More information about the Python-modules-commits mailing list