Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package cookiecutter for openSUSE:Factory checked in at 2023-09-07 21:13:17 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/cookiecutter (Old) and /work/SRC/openSUSE:Factory/.cookiecutter.new.1766 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "cookiecutter" Thu Sep 7 21:13:17 2023 rev:13 rq:1109385 version:2.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/cookiecutter/cookiecutter.changes 2023-07-26 13:24:02.812036938 +0200 +++ /work/SRC/openSUSE:Factory/.cookiecutter.new.1766/cookiecutter.changes 2023-09-07 21:14:33.157190960 +0200 @@ -1,0 +2,9 @@ +Thu Sep 7 05:19:46 UTC 2023 - Steve Kowalik <steven.kowa...@suse.com> + +- Update to 2.3.0: + * Improve style of prompts using `rich` (#1901) @vemonet + * Fix replay (#1904) @vemonet + * Support multichoice overwrite (#1903) @Meepit +- Switch to pyproject macro. + +------------------------------------------------------------------- Old: ---- cookiecutter-2.2.3.tar.gz New: ---- cookiecutter-2.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ cookiecutter.spec ++++++ --- /var/tmp/diff_new_pack.efQPoZ/_old 2023-09-07 21:14:34.269230713 +0200 +++ /var/tmp/diff_new_pack.efQPoZ/_new 2023-09-07 21:14:34.269230713 +0200 @@ -19,37 +19,34 @@ %{?sle15_python_module_pythons} Name: cookiecutter -Version: 2.2.3 +Version: 2.3.0 Release: 0 Summary: A command-line utility that creates projects from project templates License: BSD-3-Clause -Group: Development/Languages/Python URL: https://github.com/audreyr/cookiecutter Source: https://github.com/cookiecutter/cookiecutter/archive/refs/tags/%{version}.tar.gz#/%{name}-%{version}.tar.gz # recent versions are not published on PyPI: https://github.com/cookiecutter/cookiecutter/issues/1636 #Source: https://files.pythonhosted.org/packages/source/c/cookiecutter/cookiecutter-%%{version}.tar.gz Source1: ccext.py BuildRequires: %{python_module Jinja2 >= 2.7 with %python-Jinja2 < 4} +BuildRequires: %{python_module arrow} BuildRequires: %{python_module binaryornot >= 0.2.0} BuildRequires: %{python_module click >= 7 with %python-click < 9} -BuildRequires: %{python_module future >= 0.15.2} -BuildRequires: %{python_module jinja2-time >= 0.1.0} -BuildRequires: %{python_module poyo >= 0.1.0} +BuildRequires: %{python_module pip} BuildRequires: %{python_module python-slugify} +BuildRequires: %{python_module rich} BuildRequires: %{python_module setuptools} -BuildRequires: %{python_module whichcraft >= 0.4.0} +BuildRequires: %{python_module wheel} BuildRequires: fdupes BuildRequires: git-core BuildRequires: python-rpm-macros Requires: git-core Requires: python-PyYAML +Requires: python-arrow Requires: python-binaryornot >= 0.2.0 -Requires: python-future >= 0.15.2 -Requires: python-jinja2-time >= 0.1.0 -Requires: python-poyo >= 0.1.0 Requires: python-python-slugify Requires: python-requests >= 2.18.0 -Requires: python-whichcraft >= 0.4.0 +Requires: python-rich Requires: (python-Jinja2 >= 2.7 with python-Jinja2 < 4) Requires: (python-click >= 7 with python-click < 9) Requires(post): update-alternatives @@ -105,14 +102,14 @@ rm setup.cfg %build -%python_build +%pyproject_wheel pushd docs sphinx-build -b html -d .build/doctrees . _build/html rm _build/html/.buildinfo popd %install -%python_install +%pyproject_install %python_clone -a %{buildroot}%{_bindir}/cookiecutter %python_expand %fdupes %{buildroot}%{$python_sitelib} # the doc directive in the files section cannot deduplicate, so do it manually @@ -140,7 +137,7 @@ %license LICENSE %python_alternative cookiecutter %{python_sitelib}/cookiecutter -%{python_sitelib}/cookiecutter-%{version}*-info +%{python_sitelib}/cookiecutter-%{version}.dist-info %files -n cookiecutter-doc %license LICENSE ++++++ cookiecutter-2.2.3.tar.gz -> cookiecutter-2.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/.github/workflows/tests.yml new/cookiecutter-2.3.0/.github/workflows/tests.yml --- old/cookiecutter-2.2.3/.github/workflows/tests.yml 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/.github/workflows/tests.yml 2023-08-03 22:32:14.000000000 +0200 @@ -61,7 +61,7 @@ run: "tox -e safety" - name: Send coverage report to codeclimate continue-on-error: true - uses: paambaati/codeclimate-action@v4.0.0 + uses: paambaati/codeclimate-action@v5.0.0 with: coverageCommand: echo "Ignore rerun" coverageLocations: ${{github.workspace}}/coverage.xml:coverage.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/.gitignore new/cookiecutter-2.3.0/.gitignore --- old/cookiecutter-2.2.3/.gitignore 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/.gitignore 2023-08-03 22:32:14.000000000 +0200 @@ -1,5 +1,7 @@ # Adapted from https://github.com/github/gitignore/blob/main/Python.gitignore +tests/tmp/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/.pre-commit-config.yaml new/cookiecutter-2.3.0/.pre-commit-config.yaml --- old/cookiecutter-2.2.3/.pre-commit-config.yaml 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/.pre-commit-config.yaml 2023-08-03 22:32:14.000000000 +0200 @@ -51,7 +51,7 @@ - id: check-yaml exclude: "not_rendered.yml|invalid-config.yaml" - repo: https://github.com/PyCQA/flake8 - rev: 6.0.0 + rev: 6.1.0 hooks: - id: flake8 additional_dependencies: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/HISTORY.md new/cookiecutter-2.3.0/HISTORY.md --- old/cookiecutter-2.2.3/HISTORY.md 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/HISTORY.md 2023-08-03 22:32:14.000000000 +0200 @@ -2,6 +2,26 @@ History is important, but our current roadmap can be found [here](https://github.com/cookiecutter/cookiecutter/projects) +## 2.3.0 (2023-08-03) + +### Minor Changes + +* Improve style of prompts using `rich` (#1901) @vemonet + +### CI/CD and QA changes + +* Bump paambaati/codeclimate-action from 4.0.0 to 5.0.0 (#1908) @dependabot +* [pre-commit.ci] pre-commit autoupdate (#1907) @pre-commit-ci + +### Bugfixes + +* Fix replay (#1904) @vemonet +* Support multichoice overwrite (#1903) @Meepit + +### This release is made by wonderful contributors: + +@Meepit, @dependabot, @dependabot[bot], @ericof, @pre-commit-ci, @pre-commit-ci[bot] and @vemonet + ## 2.2.3 (2023-07-11) ### Changes diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/README.md new/cookiecutter-2.3.0/README.md --- old/cookiecutter-2.2.3/README.md 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/README.md 2023-08-03 22:32:14.000000000 +0200 @@ -113,7 +113,7 @@ ```py {{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}.py ``` -- Simply define your template variables in a `cookiecutter.json` file. You can also add human-readable questions and choices that will be prompted to the user for each variable using the `__prompts__` key. +- Simply define your template variables in a `cookiecutter.json` file. You can also add human-readable questions and choices that will be prompted to the user for each variable using the `__prompts__` key. Those human-readable questions supports [`rich` markup](https://rich.readthedocs.io/en/stable/markup.html) such as `[bold yellow]this is bold and yellow[/]` For example: ```json @@ -128,10 +128,10 @@ "version": "0.1.1", "linting": ["ruff", "flake8", "none"], "__prompts__": { - "full_name": "Provide your full name", - "email": "Provide your email", + "full_name": "Provide your [bold yellow]full name[/]", + "email": "Provide your [bold yellow]email[/]", "linting": { - "__prompt__": "Which linting tool do you want to use?", + "__prompt__": "Which [bold yellow]linting tool[/] do you want to use?", "ruff": "Ruff", "flake8": "Flake8", "none": "No linting tool" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/cookiecutter/VERSION.txt new/cookiecutter-2.3.0/cookiecutter/VERSION.txt --- old/cookiecutter-2.2.3/cookiecutter/VERSION.txt 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/cookiecutter/VERSION.txt 2023-08-03 22:32:14.000000000 +0200 @@ -1 +1 @@ -2.2.3 +2.3.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/cookiecutter/generate.py new/cookiecutter-2.3.0/cookiecutter/generate.py --- old/cookiecutter-2.2.3/cookiecutter/generate.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/cookiecutter/generate.py 2023-08-03 22:32:14.000000000 +0200 @@ -55,7 +55,17 @@ context_value = context[variable] - if isinstance(context_value, list): + if isinstance(context_value, list) and isinstance(overwrite, list): + # We are dealing with a multichoice variable + # Let's confirm all choices are valid for the given context + if set(overwrite).issubset(set(context_value)): + context[variable] = overwrite + else: + raise ValueError( + f"{overwrite} provided for multi-choice variable {variable}, " + f"but valid choices are {context_value}" + ) + elif isinstance(context_value, list): # We are dealing with a choice variable if overwrite in context_value: # This overwrite is actually valid for the given context diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/cookiecutter/main.py new/cookiecutter-2.3.0/cookiecutter/main.py --- old/cookiecutter-2.2.3/cookiecutter/main.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/cookiecutter/main.py 2023-08-03 22:32:14.000000000 +0200 @@ -110,6 +110,7 @@ } context_for_prompting = {} context_for_prompting['cookiecutter'] = items_for_prompting + context = context_from_replayfile logger.debug('prompting context: %s', context_for_prompting) else: context = generate_context( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/cookiecutter/prompt.py new/cookiecutter-2.3.0/cookiecutter/prompt.py --- old/cookiecutter-2.2.3/cookiecutter/prompt.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/cookiecutter/prompt.py 2023-08-03 22:32:14.000000000 +0200 @@ -1,16 +1,15 @@ """Functions for prompting the user for project info.""" -import functools import json from collections import OrderedDict -import click +from rich.prompt import Prompt, Confirm, PromptBase, InvalidResponse from jinja2.exceptions import UndefinedError from cookiecutter.environment import StrictEnvironment from cookiecutter.exceptions import UndefinedVariableInTemplate -def read_user_variable(var_name, default_value, prompts=None): +def read_user_variable(var_name, default_value, prompts=None, prefix=""): """Prompt user for variable and return the entered value or given default. :param str var_name: Variable of the context to query the user @@ -21,10 +20,27 @@ if prompts and var_name in prompts.keys() and prompts[var_name] else var_name ) - return click.prompt(question, default=default_value) + return Prompt.ask(f"{prefix}{question}", default=default_value) -def read_user_yes_no(var_name, default_value, prompts=None): +class YesNoPrompt(Confirm): + """A prompt that returns a boolean for yes/no questions.""" + + yes_choices = ["1", "true", "t", "yes", "y", "on"] + no_choices = ["0", "false", "f", "no", "n", "off"] + + def process_response(self, value: str) -> bool: + """Convert choices to a bool.""" + value = value.strip().lower() + if value in self.yes_choices: + return True + elif value in self.no_choices: + return False + else: + raise InvalidResponse(self.validate_error_message) + + +def read_user_yes_no(var_name, default_value, prompts=None, prefix=""): """Prompt the user to reply with 'yes' or 'no' (or equivalent values). - These input values will be converted to ``True``: @@ -32,7 +48,7 @@ - These input values will be converted to ``False``: "0", "false", "f", "no", "n", "off" - Actual parsing done by :func:`click.prompt`; Check this function codebase change in + Actual parsing done by :func:`prompt`; Check this function codebase change in case of unexpected behaviour. :param str question: Question to the user @@ -43,7 +59,7 @@ if prompts and var_name in prompts.keys() and prompts[var_name] else var_name ) - return click.prompt(question, default=default_value, type=click.BOOL) + return YesNoPrompt.ask(f"{prefix}{question}", default=default_value) def read_repo_password(question): @@ -51,10 +67,10 @@ :param str question: Question to the user """ - return click.prompt(question, hide_input=True) + return Prompt.ask(question, password=True) -def read_user_choice(var_name, options, prompts=None): +def read_user_choice(var_name, options, prompts=None, prefix=""): """Prompt the user to choose from several options for the given variable. The first item will be returned if no input happens. @@ -71,9 +87,11 @@ choice_map = OrderedDict((f'{i}', value) for i, value in enumerate(options, 1)) choices = choice_map.keys() - default = '1' + question = f"Select {var_name}" - choice_lines = ['{} - {}'.format(*c) for c in choice_map.items()] + choice_lines = [ + ' [bold magenta]{}[/] - [bold]{}[/]'.format(*c) for c in choice_map.items() + ] # Handle if human-readable prompt is provided if prompts and var_name in prompts.keys(): @@ -83,23 +101,21 @@ if "__prompt__" in prompts[var_name]: question = prompts[var_name]["__prompt__"] choice_lines = [ - f"{i} - {prompts[var_name][p]}" + f" [bold magenta]{i}[/] - [bold]{prompts[var_name][p]}[/]" if p in prompts[var_name] - else f"{i} - {p}" + else f" [bold magenta]{i}[/] - [bold]{p}[/]" for i, p in choice_map.items() ] prompt = '\n'.join( ( - f"{question}:", + f"{prefix}{question}", "\n".join(choice_lines), - f"Choose from {', '.join(choices)}", + " Choose from", ) ) - user_choice = click.prompt( - prompt, type=click.Choice(choices), default=default, show_choices=False - ) + user_choice = Prompt.ask(prompt, choices=list(choices), default=list(choices)[0]) return choice_map[user_choice] @@ -111,24 +127,32 @@ :param str user_value: User-supplied value to load as a JSON dict """ - if user_value == DEFAULT_DISPLAY: - # Return the given default w/o any processing - return default_value - try: user_dict = json.loads(user_value, object_pairs_hook=OrderedDict) except Exception as error: # Leave it up to click to ask the user again - raise click.UsageError('Unable to decode to JSON.') from error + raise InvalidResponse('Unable to decode to JSON.') from error if not isinstance(user_dict, dict): # Leave it up to click to ask the user again - raise click.UsageError('Requires JSON dict.') + raise InvalidResponse('Requires JSON dict.') return user_dict -def read_user_dict(var_name, default_value, prompts=None): +class JsonPrompt(PromptBase[dict]): + """A prompt that returns a dict from JSON string.""" + + default = None + response_type = dict + validate_error_message = "[prompt.invalid] Please enter a valid JSON string" + + def process_response(self, value: str) -> dict: + """Convert choices to a dict.""" + return process_json(value, self.default) + + +def read_user_dict(var_name, default_value, prompts=None, prefix=""): """Prompt the user to provide a dictionary of data. :param str var_name: Variable as specified in the context @@ -143,16 +167,11 @@ if prompts and var_name in prompts.keys() and prompts[var_name] else var_name ) - user_value = click.prompt( - question, - default=DEFAULT_DISPLAY, - type=click.STRING, - value_proc=functools.partial(process_json, default_value=default_value), + user_value = JsonPrompt.ask( + f"{prefix}{question} [cyan bold]({DEFAULT_DISPLAY})[/]", + default=default_value, + show_default=False, ) - - if click.__version__.startswith("7.") and user_value == DEFAULT_DISPLAY: - # click 7.x does not invoke value_proc on the default value. - return default_value # pragma: no cover return user_value @@ -193,7 +212,7 @@ def prompt_choice_for_config( - cookiecutter_dict, env, key, options, no_input, prompts=None + cookiecutter_dict, env, key, options, no_input, prompts=None, prefix="" ): """Prompt user with a set of options to choose from. @@ -202,7 +221,7 @@ rendered_options = [render_variable(env, raw, cookiecutter_dict) for raw in options] if no_input: return rendered_options[0] - return read_user_choice(key, rendered_options, prompts) + return read_user_choice(key, rendered_options, prompts, prefix) def prompt_for_config(context, no_input=False): @@ -222,6 +241,9 @@ # First pass: Handle simple and raw variables, plus choices. # These must be done first because the dictionaries keys and # values might refer to them. + + count = 0 + size = len(context['cookiecutter'].items()) for key, raw in context['cookiecutter'].items(): if key.startswith('_') and not key.startswith('__'): cookiecutter_dict[key] = raw @@ -230,11 +252,15 @@ cookiecutter_dict[key] = render_variable(env, raw, cookiecutter_dict) continue + if not isinstance(raw, dict): + count += 1 + prefix = f" [dim][{count}/{size}][/] " + try: if isinstance(raw, list): # We are dealing with a choice variable val = prompt_choice_for_config( - cookiecutter_dict, env, key, raw, no_input, prompts + cookiecutter_dict, env, key, raw, no_input, prompts, prefix ) cookiecutter_dict[key] = val elif isinstance(raw, bool): @@ -244,13 +270,13 @@ env, raw, cookiecutter_dict ) else: - cookiecutter_dict[key] = read_user_yes_no(key, raw, prompts) + cookiecutter_dict[key] = read_user_yes_no(key, raw, prompts, prefix) elif not isinstance(raw, dict): # We are dealing with a regular variable val = render_variable(env, raw, cookiecutter_dict) if not no_input: - val = read_user_variable(key, val, prompts) + val = read_user_variable(key, val, prompts, prefix) cookiecutter_dict[key] = val except UndefinedError as err: @@ -266,10 +292,12 @@ try: if isinstance(raw, dict): # We are dealing with a dict variable + count += 1 + prefix = f" [dim][{count}/{size}][/] " val = render_variable(env, raw, cookiecutter_dict) if not no_input and not key.startswith('__'): - val = read_user_dict(key, val, prompts) + val = read_user_dict(key, val, prompts, prefix) cookiecutter_dict[key] = val except UndefinedError as err: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/setup.py new/cookiecutter-2.3.0/setup.py --- old/cookiecutter-2.2.3/setup.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/setup.py 2023-08-03 22:32:14.000000000 +0200 @@ -25,6 +25,7 @@ 'python-slugify>=4.0.0', 'requests>=2.23.0', 'arrow', + 'rich', ] setup( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/fake-repo-replay/cookiecutter.json new/cookiecutter-2.3.0/tests/fake-repo-replay/cookiecutter.json --- old/cookiecutter-2.2.3/tests/fake-repo-replay/cookiecutter.json 1970-01-01 01:00:00.000000000 +0100 +++ new/cookiecutter-2.3.0/tests/fake-repo-replay/cookiecutter.json 2023-08-03 22:32:14.000000000 +0200 @@ -0,0 +1,11 @@ +{ + "full_name": "Audrey Roy", + "email": "audr...@gmail.com", + "github_username": "audreyr", + "project_name": "Replay Project", + "repo_name": "{{ cookiecutter.project_name|lower|replace(' ', '-') }}", + "description": "original", + "release_date": "2013-07-28", + "year": "2013", + "version": "0.1" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/fake-repo-replay/{{cookiecutter.repo_name}}/README.md new/cookiecutter-2.3.0/tests/fake-repo-replay/{{cookiecutter.repo_name}}/README.md --- old/cookiecutter-2.2.3/tests/fake-repo-replay/{{cookiecutter.repo_name}}/README.md 1970-01-01 01:00:00.000000000 +0100 +++ new/cookiecutter-2.3.0/tests/fake-repo-replay/{{cookiecutter.repo_name}}/README.md 2023-08-03 22:32:14.000000000 +0200 @@ -0,0 +1 @@ +{{cookiecutter.description}} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test-replay/valid_replay.json new/cookiecutter-2.3.0/tests/test-replay/valid_replay.json --- old/cookiecutter-2.2.3/tests/test-replay/valid_replay.json 1970-01-01 01:00:00.000000000 +0100 +++ new/cookiecutter-2.3.0/tests/test-replay/valid_replay.json 2023-08-03 22:32:14.000000000 +0200 @@ -0,0 +1,13 @@ +{ + "cookiecutter": { + "version": "0.1.0", + "email": "raph...@hackebrot.de", + "full_name": "Raphael Pierzina", + "github_username": "hackebrot", + "project_name": "Replay Project", + "repo_name": "replay-project", + "release_date": "2013-07-28", + "year": "2013", + "description": "replayed" + } +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test_cli.py new/cookiecutter-2.3.0/tests/test_cli.py --- old/cookiecutter-2.2.3/tests/test_cli.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/tests/test_cli.py 2023-08-03 22:32:14.000000000 +0200 @@ -38,6 +38,19 @@ @pytest.fixture +def remove_tmp_dir(request): + """Remove the fake project directory created during the tests.""" + if os.path.isdir('tests/tmp'): + utils.rmtree('tests/tmp') + + def fin_remove_tmp_dir(): + if os.path.isdir('tests/tmp'): + utils.rmtree('tests/tmp') + + request.addfinalizer(fin_remove_tmp_dir) + + +@pytest.fixture def make_fake_project_dir(request): """Create a fake project to be overwritten in the according tests.""" os.makedirs('fake-project') @@ -139,6 +152,22 @@ ) +@pytest.mark.usefixtures('remove_tmp_dir') +def test_cli_replay_generated(mocker, cli_runner): + """Test cli invocation correctly generates a project with replay.""" + template_path = 'tests/fake-repo-replay/' + result = cli_runner( + template_path, + '--replay-file', + 'tests/test-replay/valid_replay.json', + '-o', + 'tests/tmp/', + '-v', + ) + assert result.exit_code == 0 + assert open('tests/tmp/replay-project/README.md').read().strip() == 'replayed' + + @pytest.mark.usefixtures('remove_fake_project_dir') def test_cli_exit_on_noinput_and_replay(mocker, cli_runner): """Test cli invocation fail if both `no-input` and `replay` flags passed.""" @@ -656,13 +685,3 @@ # this point. path = os.path.sep.join(['tests', 'fake-repo-bad-json', 'cookiecutter.json']) assert path in result.output - - -@pytest.mark.usefixtures('remove_fake_project_dir') -def test_prompt_when_replyfile_not_full(mocker): - """Test execute prompt when replayfile not full.""" - mock_prompt = mocker.patch('cookiecutter.main.prompt_for_config') - cookiecutter( - 'tests/fake-repo-pre/', replay='tests/test-replay/cookiedozer_load.json' - ) - assert mock_prompt.called diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test_cookiecutter_local_with_input.py new/cookiecutter-2.3.0/tests/test_cookiecutter_local_with_input.py --- old/cookiecutter-2.2.3/tests/test_cookiecutter_local_with_input.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/tests/test_cookiecutter_local_with_input.py 2023-08-03 22:32:14.000000000 +0200 @@ -21,7 +21,7 @@ """Verify simple cookiecutter run results, without extra_context provided.""" monkeypatch.setattr( 'cookiecutter.prompt.read_user_variable', - lambda var, default, prompts: default, + lambda var, default, prompts, prefix: default, ) main.cookiecutter('tests/fake-repo-pre/', no_input=False) assert os.path.isdir('tests/fake-repo-pre/{{cookiecutter.repo_name}}') @@ -36,7 +36,7 @@ """Verify simple cookiecutter run results, with extra_context provided.""" monkeypatch.setattr( 'cookiecutter.prompt.read_user_variable', - lambda var, default, prompts: default, + lambda var, default, prompts, prefix: default, ) main.cookiecutter( 'tests/fake-repo-pre', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test_generate_context.py new/cookiecutter-2.3.0/tests/test_generate_context.py --- old/cookiecutter-2.2.3/tests/test_generate_context.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/tests/test_generate_context.py 2023-08-03 22:32:14.000000000 +0200 @@ -135,6 +135,13 @@ ('project_name', 'Kivy Project'), ('repo_name', '{{cookiecutter.project_name|lower}}'), ('orientation', ['all', 'landscape', 'portrait']), + ( + 'deployments', + { + 'preprod': ['eu', 'us', 'ap'], + 'prod': ['eu', 'us', 'ap'], + }, + ), ] ) @@ -196,6 +203,36 @@ ) +def test_apply_overwrites_sets_multichoice_values(template_context): + """Verify variable overwrite for list given multiple valid values.""" + generate.apply_overwrites_to_context( + context=template_context, + overwrite_context={'deployments': {'preprod': ['eu'], 'prod': ['eu']}}, + ) + assert template_context['deployments']['preprod'] == ['eu'] + assert template_context['deployments']['prod'] == ['eu'] + + +def test_apply_overwrites_invalid_multichoice_values(template_context): + """Verify variable overwrite for list given invalid list entries not ignored.""" + with pytest.raises(ValueError): + generate.apply_overwrites_to_context( + context=template_context, + overwrite_context={'deployments': {'preprod': ['na'], 'prod': ['na']}}, + ) + + +def test_apply_overwrites_error_additional_values(template_context): + """Verify variable overwrite for list given additional entries not ignored.""" + with pytest.raises(ValueError): + generate.apply_overwrites_to_context( + context=template_context, + overwrite_context={ + 'deployments': {'preprod': ['eu', 'na'], 'prod': ['eu', 'na']} + }, + ) + + def test_apply_overwrites_sets_default_for_choice_variable(template_context): """Verify overwritten list member became a default value.""" generate.apply_overwrites_to_context( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test_main.py new/cookiecutter-2.3.0/tests/test_main.py --- old/cookiecutter-2.2.3/tests/test_main.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/tests/test_main.py 2023-08-03 22:32:14.000000000 +0200 @@ -78,6 +78,7 @@ 'cookiecutter': {} } mocker.patch('cookiecutter.main.generate_files') + mocker.patch('cookiecutter.main.dump') cookiecutter( '.', @@ -100,6 +101,7 @@ 'cookiecutter': {} } mocker.patch('cookiecutter.main.generate_files') + mocker.patch('cookiecutter.main.dump') cookiecutter( '.', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test_prompt.py new/cookiecutter-2.3.0/tests/test_prompt.py --- old/cookiecutter-2.2.3/tests/test_prompt.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/tests/test_prompt.py 2023-08-03 22:32:14.000000000 +0200 @@ -82,7 +82,7 @@ """Verify `prompt_for_config` call `read_user_variable` on text request.""" monkeypatch.setattr( 'cookiecutter.prompt.read_user_variable', - lambda var, default, prompts: default, + lambda var, default, prompts, prefix: default, ) cookiecutter_dict = prompt.prompt_for_config(context) @@ -109,15 +109,15 @@ """Verify call `read_user_variable` on request when human-readable prompts.""" monkeypatch.setattr( 'cookiecutter.prompt.read_user_variable', - lambda var, default, prompts: default, + lambda var, default, prompts, prefix: default, ) monkeypatch.setattr( 'cookiecutter.prompt.read_user_yes_no', - lambda var, default, prompts: default, + lambda var, default, prompts, prefix: default, ) monkeypatch.setattr( 'cookiecutter.prompt.read_user_choice', - lambda var, default, prompts: default, + lambda var, default, prompts, prefix: default, ) cookiecutter_dict = prompt.prompt_for_config(context) @@ -169,7 +169,7 @@ """Verify `prompt_for_config` call `read_user_variable` on dict request.""" monkeypatch.setattr( 'cookiecutter.prompt.read_user_dict', - lambda var, default, prompts: {"key": "value", "integer": 37}, + lambda var, default, prompts, prefix: {"key": "value", "integer": 37}, ) context = {'cookiecutter': {'details': {}}} @@ -284,7 +284,7 @@ """Verify Jinja2 templating works in unicode prompts.""" monkeypatch.setattr( 'cookiecutter.prompt.read_user_variable', - lambda var, default, prompts: default, + lambda var, default, prompts, prefix: default, ) context = { 'cookiecutter': OrderedDict( @@ -373,6 +373,9 @@ assert cookiecutter_dict == context['cookiecutter'] +DEFAULT_PREFIX = ' [dim][1/1][/] ' + + class TestReadUserChoice: """Class to unite choices prompt related tests.""" @@ -395,7 +398,9 @@ assert not read_user_variable.called assert prompt_choice.called - read_user_choice.assert_called_once_with('orientation', choices, {}) + read_user_choice.assert_called_once_with( + 'orientation', choices, {}, DEFAULT_PREFIX + ) assert cookiecutter_dict == {'orientation': 'all'} def test_should_invoke_read_user_variable(self, mocker): @@ -413,7 +418,9 @@ assert not prompt_choice.called assert not read_user_choice.called - read_user_variable.assert_called_once_with('full_name', 'Your Name', {}) + read_user_variable.assert_called_once_with( + 'full_name', 'Your Name', {}, DEFAULT_PREFIX + ) assert cookiecutter_dict == {'full_name': 'Audrey Roy'} def test_should_render_choices(self, mocker): @@ -448,8 +455,12 @@ } cookiecutter_dict = prompt.prompt_for_config(context) - read_user_variable.assert_called_once_with('project_name', 'A New Project', {}) - read_user_choice.assert_called_once_with('pkg_name', rendered_choices, {}) + read_user_variable.assert_called_once_with( + 'project_name', 'A New Project', {}, ' [dim][1/2][/] ' + ) + read_user_choice.assert_called_once_with( + 'pkg_name', rendered_choices, {}, ' [dim][2/2][/] ' + ) assert cookiecutter_dict == expected @@ -497,7 +508,7 @@ options=choices, no_input=False, # Ask the user for input ) - read_user_choice.assert_called_once_with('orientation', choices, None) + read_user_choice.assert_called_once_with('orientation', choices, None, '') assert expected_choice == actual_choice @@ -523,7 +534,9 @@ cookiecutter_dict = prompt.prompt_for_config(context) assert not read_user_variable.called - read_user_yes_no.assert_called_once_with('run_as_docker', run_as_docker, {}) + read_user_yes_no.assert_called_once_with( + 'run_as_docker', run_as_docker, {}, DEFAULT_PREFIX + ) assert cookiecutter_dict == {'run_as_docker': run_as_docker} def test_boolean_parameter_no_input(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test_read_repo_password.py new/cookiecutter-2.3.0/tests/test_read_repo_password.py --- old/cookiecutter-2.2.3/tests/test_read_repo_password.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/tests/test_read_repo_password.py 2023-08-03 22:32:14.000000000 +0200 @@ -7,9 +7,9 @@ Test for password (hidden input) type invocation. """ - prompt = mocker.patch('click.prompt') + prompt = mocker.patch('rich.prompt.Prompt.ask') prompt.return_value = 'sekrit' assert read_repo_password('Password') == 'sekrit' - prompt.assert_called_once_with('Password', hide_input=True) + prompt.assert_called_once_with('Password', password=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test_read_user_choice.py new/cookiecutter-2.3.0/tests/test_read_user_choice.py --- old/cookiecutter-2.2.3/tests/test_read_user_choice.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/tests/test_read_user_choice.py 2023-08-03 22:32:14.000000000 +0200 @@ -1,17 +1,17 @@ """Tests around prompting for and handling of choice variables.""" -import click import pytest from cookiecutter.prompt import read_user_choice OPTIONS = ['hello', 'world', 'foo', 'bar'] +OPTIONS_INDEX = ['1', '2', '3', '4'] -EXPECTED_PROMPT = """Select varname: -1 - hello -2 - world -3 - foo -4 - bar -Choose from 1, 2, 3, 4""" +EXPECTED_PROMPT = """Select varname + [bold magenta]1[/] - [bold]hello[/] + [bold magenta]2[/] - [bold]world[/] + [bold magenta]3[/] - [bold]foo[/] + [bold magenta]4[/] - [bold]bar[/] + Choose from""" @pytest.mark.parametrize('user_choice, expected_value', enumerate(OPTIONS, 1)) @@ -20,17 +20,12 @@ Test for choice type invocation. """ - choice = mocker.patch('click.Choice') - choice.return_value = click.Choice(OPTIONS) - - prompt = mocker.patch('click.prompt') + prompt = mocker.patch('rich.prompt.Prompt.ask') prompt.return_value = f'{user_choice}' assert read_user_choice('varname', OPTIONS) == expected_value - prompt.assert_called_once_with( - EXPECTED_PROMPT, type=click.Choice(OPTIONS), default='1', show_choices=False - ) + prompt.assert_called_once_with(EXPECTED_PROMPT, choices=OPTIONS_INDEX, default='1') def test_raise_if_options_is_not_a_non_empty_list(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test_read_user_dict.py new/cookiecutter-2.3.0/tests/test_read_user_dict.py --- old/cookiecutter-2.2.3/tests/test_read_user_dict.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/tests/test_read_user_dict.py 2023-08-03 22:32:14.000000000 +0200 @@ -2,15 +2,13 @@ import click import pytest -from cookiecutter.prompt import ( - process_json, - read_user_dict, -) +from cookiecutter.prompt import process_json, read_user_dict, JsonPrompt +from rich.prompt import InvalidResponse def test_process_json_invalid_json(): """Test `process_json` for correct error on malformed input.""" - with pytest.raises(click.UsageError) as exc_info: + with pytest.raises(InvalidResponse) as exc_info: process_json('nope]') assert str(exc_info.value) == 'Unable to decode to JSON.' @@ -18,7 +16,7 @@ def test_process_json_non_dict(): """Test `process_json` for correct error on non-JSON input.""" - with pytest.raises(click.UsageError) as exc_info: + with pytest.raises(InvalidResponse) as exc_info: process_json('[1, 2]') assert str(exc_info.value) == 'Requires JSON dict.' @@ -75,11 +73,10 @@ def test_should_raise_type_error(mocker): """Test `default_value` arg verification in `read_user_dict` function.""" - prompt = mocker.patch('cookiecutter.prompt.click.prompt') + prompt = mocker.patch('cookiecutter.prompt.JsonPrompt.ask') with pytest.raises(TypeError): read_user_dict('name', 'russell') - assert not prompt.called @@ -88,16 +85,14 @@ Verifies generation of a processor for the user input. """ - mock_prompt = mocker.patch('cookiecutter.prompt.click.prompt', autospec=True) + mock_prompt = mocker.patch('cookiecutter.prompt.JsonPrompt.ask', autospec=True) read_user_dict('name', {'project_slug': 'pytest-plugin'}) - + print(mock_prompt.call_args) args, kwargs = mock_prompt.call_args - assert args == ('name',) - assert kwargs['type'] == click.STRING - assert kwargs['default'] == 'default' - assert kwargs['value_proc'].func == process_json + assert args == ('name [cyan bold](default)[/]',) + assert kwargs['default'] == {'project_slug': 'pytest-plugin'} def test_should_not_load_json_from_sentinel(mocker): @@ -113,7 +108,7 @@ mock_json_loads.assert_not_called() -@pytest.mark.parametrize("input", ["\n", "default\n"]) +@pytest.mark.parametrize("input", ["\n", "\ndefault\n"]) def test_read_user_dict_default_value(mocker, input): """Make sure that `read_user_dict` returns the default value. @@ -124,3 +119,11 @@ val = read_user_dict('name', {'project_slug': 'pytest-plugin'}) assert val == {'project_slug': 'pytest-plugin'} + + +def test_json_prompt_process_response(): + """Test `JsonPrompt` process_response to convert str to json.""" + jp = JsonPrompt() + assert jp.process_response('{"project_slug": "something"}') == { + 'project_slug': 'something' + } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test_read_user_variable.py new/cookiecutter-2.3.0/tests/test_read_user_variable.py --- old/cookiecutter-2.2.3/tests/test_read_user_variable.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/tests/test_read_user_variable.py 2023-08-03 22:32:14.000000000 +0200 @@ -1,6 +1,4 @@ """test_read_user_variable.""" -import click - from cookiecutter.prompt import read_user_variable VARIABLE = 'project_name' @@ -12,9 +10,9 @@ Test for string type invocation. """ - prompt = mocker.patch('click.prompt') + prompt = mocker.patch('rich.prompt.Prompt.ask') prompt.return_value = DEFAULT assert read_user_variable(VARIABLE, DEFAULT) == DEFAULT - click.prompt.assert_called_once_with(VARIABLE, default=DEFAULT) + prompt.assert_called_once_with(VARIABLE, default=DEFAULT) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/cookiecutter-2.2.3/tests/test_read_user_yes_no.py new/cookiecutter-2.3.0/tests/test_read_user_yes_no.py --- old/cookiecutter-2.2.3/tests/test_read_user_yes_no.py 2023-07-11 17:56:39.000000000 +0200 +++ new/cookiecutter-2.3.0/tests/test_read_user_yes_no.py 2023-08-03 22:32:14.000000000 +0200 @@ -1,7 +1,9 @@ """test_read_user_yes_no.""" -import click +import pytest -from cookiecutter.prompt import read_user_yes_no +from rich.prompt import InvalidResponse + +from cookiecutter.prompt import read_user_yes_no, YesNoPrompt QUESTION = 'Is it okay to delete and re-clone it?' DEFAULT = 'y' @@ -12,9 +14,18 @@ Test for boolean type invocation. """ - prompt = mocker.patch('click.prompt') + prompt = mocker.patch('cookiecutter.prompt.YesNoPrompt.ask') prompt.return_value = DEFAULT assert read_user_yes_no(QUESTION, DEFAULT) == DEFAULT - click.prompt.assert_called_once_with(QUESTION, default=DEFAULT, type=click.BOOL) + prompt.assert_called_once_with(QUESTION, default=DEFAULT) + + +def test_yesno_prompt_process_response(): + """Test `YesNoPrompt` process_response to convert str to bool.""" + ynp = YesNoPrompt() + with pytest.raises(InvalidResponse): + ynp.process_response('wrong') + assert ynp.process_response('t') is True + assert ynp.process_response('f') is False