diff --git a/docs/features.md b/docs/features.md index fea28fb..6a20d1a 100644 --- a/docs/features.md +++ b/docs/features.md @@ -6,6 +6,7 @@ hold useful information: * ``exit_code``: is the exit code of cookiecutter, ``0`` means successful termination * ``exception``: is the exception that happened if one did * ``project``: a [py.path.local] object pointing to the rendered project +* ``context``: is the rendered context The returned ``LocalPath`` instance provides you with a powerful interface to filesystem related information, that comes in handy for validating the generated diff --git a/pytest_cookies.py b/pytest_cookies.py index af889c4..67950cd 100644 --- a/pytest_cookies.py +++ b/pytest_cookies.py @@ -4,6 +4,8 @@ import pytest from cookiecutter.main import cookiecutter +from cookiecutter.generate import generate_context +from cookiecutter.prompt import prompt_for_config USER_CONFIG = u""" cookiecutters_dir: "{cookiecutters_dir}" @@ -14,9 +16,16 @@ class Result(object): """Holds the captured result of the cookiecutter project generation.""" - def __init__(self, exception=None, exit_code=0, project_dir=None): + def __init__( + self, + exception=None, + exit_code=0, + project_dir=None, + context=None, + ): self.exception = exception self.exit_code = exit_code + self.context = context self._project_dir = project_dir @property @@ -28,6 +37,7 @@ def project(self): def __repr__(self): if self.exception: return ''.format(self.exception) + return ''.format(self.project) @@ -50,17 +60,30 @@ def bake(self, extra_context=None, template=None): exception = None exit_code = 0 project_dir = None + context = None if template is None: template = self._default_template + context_file = py.path.local(template).join('cookiecutter.json') + try: + # Render the context, so that we can store it on the Result + context = prompt_for_config( + generate_context( + context_file=str(context_file), + extra_context=extra_context, + ), + no_input=True, + ) + + # Run cookiecutter to generate a new project project_dir = cookiecutter( template, no_input=True, extra_context=extra_context, output_dir=str(self._new_output_dir()), - config_file=str(self._config_file) + config_file=str(self._config_file), ) except SystemExit as e: if e.code != 0: @@ -70,7 +93,12 @@ def bake(self, extra_context=None, template=None): exception = e exit_code = -1 - return Result(exception, exit_code, project_dir) + return Result( + exception=exception, + exit_code=exit_code, + project_dir=project_dir, + context=context, + ) @pytest.fixture(scope='session') diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 2586e10..6ff40a2 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import json +import collections import pytest @@ -33,10 +34,10 @@ def test_valid_fixture(cookies): def cookiecutter_template(tmpdir): template = tmpdir.ensure('cookiecutter-template', dir=True) - template_config = { - 'repo_name': 'foobar', - 'short_description': 'Test Project', - } + template_config = collections.OrderedDict([ + ('repo_name', 'foobar'), + ('short_description', 'Test Project'), + ]) template.join('cookiecutter.json').write(json.dumps(template_config)) template_readme = '\n'.join([ @@ -138,6 +139,85 @@ def test_bake_project(cookies): ]) +def test_cookies_bake_result_context(testdir, cookiecutter_template): + """Programmatically create a **Cookiecutter** template and use `bake` to + create a project from it. + + Check that the result holds the rendered context. + """ + + testdir.makepyfile(""" + # -*- coding: utf-8 -*- + + import collections + + def test_bake_project(cookies): + result = cookies.bake(extra_context=collections.OrderedDict([ + ('repo_name', 'cookies'), + ('short_description', '{{cookiecutter.repo_name}} is awesome'), + ])) + + assert result.exit_code == 0 + assert result.exception is None + assert result.project.basename == 'cookies' + assert result.project.isdir() + + assert result.context == { + 'repo_name': 'cookies', + 'short_description': 'cookies is awesome', + } + + assert str(result) == ''.format(result.project) + """) + + result = testdir.runpytest( + '-v', + '--template={}'.format(cookiecutter_template) + ) + + result.stdout.fnmatch_lines([ + '*::test_bake_project PASSED*', + ]) + + +def test_cookies_bake_result_context_exception(testdir, cookiecutter_template): + """Programmatically create a **Cookiecutter** template and use `bake` to + create a project from it. + + Check that exceptions resulting from rendering the context are stored on + result and that the rendered context is not set. + """ + + testdir.makepyfile(""" + # -*- coding: utf-8 -*- + + import collections + + def test_bake_project(cookies): + result = cookies.bake(extra_context=collections.OrderedDict([ + ('repo_name', 'cookies'), + ('short_description', '{{cookiecutter.nope}}'), + ])) + + assert result.exit_code == -1 + assert result.exception is not None + assert result.project is None + + assert result.context is None + + assert str(result) == ''.format(result.exception) + """) + + result = testdir.runpytest( + '-v', + '--template={}'.format(cookiecutter_template) + ) + + result.stdout.fnmatch_lines([ + '*::test_bake_project PASSED*', + ]) + + def test_cookies_bake_should_create_new_output_directories( testdir, cookiecutter_template ):