From 5aca4da4d22beef197c64bc3ca9d2b7044bb1baf Mon Sep 17 00:00:00 2001 From: "Augusto W. Andreoli" Date: Mon, 29 Mar 2021 18:01:49 +0200 Subject: [PATCH 1/4] __wip__ --- src/nitpick/cli.py | 8 ++++++++ tests/helpers.py | 9 ++++++++- tests/test_cli.py | 16 ++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/nitpick/cli.py b/src/nitpick/cli.py index d1ae1455..48022bd2 100644 --- a/src/nitpick/cli.py +++ b/src/nitpick/cli.py @@ -114,3 +114,11 @@ def ls(context, files): # pylint: disable=invalid-name # TODO: test API .configured_files for file in nit.configured_files(*files): click.secho(relative_to_current_dir(file), fg="green" if file.exists() else "red") + + +@nitpick_cli.command() +@click.pass_context +def ls(context): + """Initialise Nitpick configuration.""" + nit = get_nitpick(context) + nit.project.read_configuration() diff --git a/tests/helpers.py b/tests/helpers.py index e07d5965..6f45951e 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -353,10 +353,17 @@ def cli_run( compare(actual=actual, expected=expected) return self - def cli_ls(self, str_or_lines: StrOrList, exit_code: int = None): + def cli_ls(self, str_or_lines: StrOrList, *, exit_code: int = None) -> "ProjectMock": """Run the ls command and assert the output.""" result, actual, expected = self._simulate_cli("ls", str_or_lines, exit_code=exit_code) compare(actual=actual, expected=expected, prefix=f"Result: {result}") + return self + + def cli_init(self, str_or_lines: StrOrList, *, exit_code: int = None) -> "ProjectMock": + """Run the init command and assert the output.""" + result, actual, expected = self._simulate_cli("init", str_or_lines, exit_code=exit_code) + compare(actual=actual, expected=expected, prefix=f"Result: {result}") + return self def assert_file_contents(self, *name_contents: Union[PathOrStr, str]): """Assert the file has the expected contents.""" diff --git a/tests/test_cli.py b/tests/test_cli.py index 6651f7bf..51083fb6 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,7 @@ """CLI tests.""" +import pytest + +from nitpick.constants import DOT_NITPICK_TOML, PYPROJECT_TOML from tests.helpers import XFAIL_ON_WINDOWS, ProjectMock @@ -28,3 +31,16 @@ def test_simple_error(tmp_path): line-length = 100 """ ) + + +@pytest.mark.parametrize("config_file", [DOT_NITPICK_TOML, PYPROJECT_TOML]) +def test_config_file_already_exists(tmp_path, config_file): + """Test if .nitpick.toml already exists.""" + project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=True).save_file(config_file, "") + project.cli_init(f"A config file already exists: {config_file}") + + +# def test_create_basic_dot_nitpick_toml(tmp_path): +# """If no config file is found, create a basic .nitpick.toml.""" +# project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=True) +# project.cli_init() From 14eaf18de575e22e662520d437829e35f6dd1d7f Mon Sep 17 00:00:00 2001 From: "Augusto W. Andreoli" Date: Tue, 6 Apr 2021 01:45:59 +0200 Subject: [PATCH 2/4] test: a file already exists --- docs/cli.rst | 18 ++++++++++++++++-- docs/generate_rst.py | 1 + src/nitpick/cli.py | 7 +++++-- tests/test_cli.py | 3 ++- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 9a7f2a26..4c14585c 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -45,8 +45,9 @@ Main options --help Show this message and exit. Commands: - ls List of files configured in the Nitpick style. - run Apply suggestions to configuration files. + init Initialise Nitpick configuration. + ls List of files configured in the Nitpick style. + run Apply suggestions to configuration files. ``run``: Apply style to files ----------------------------- @@ -88,3 +89,16 @@ At the end of execution, this command displays: Options: --help Show this message and exit. + +``init``: Initialise a configuration file +----------------------------------------- + + +.. code-block:: + + Usage: nitpick init [OPTIONS] + + Initialise Nitpick configuration. + + Options: + --help Show this message and exit. diff --git a/docs/generate_rst.py b/docs/generate_rst.py index 7b23b241..0182da1b 100644 --- a/docs/generate_rst.py +++ b/docs/generate_rst.py @@ -64,6 +64,7 @@ """, ), ("ls", "List configures files", ""), + ("init", "Initialise a configuration file", ""), ] nit = Nitpick.singleton().init() diff --git a/src/nitpick/cli.py b/src/nitpick/cli.py index 48022bd2..162f154a 100644 --- a/src/nitpick/cli.py +++ b/src/nitpick/cli.py @@ -118,7 +118,10 @@ def ls(context, files): # pylint: disable=invalid-name @nitpick_cli.command() @click.pass_context -def ls(context): +def init(context): """Initialise Nitpick configuration.""" nit = get_nitpick(context) - nit.project.read_configuration() + config = nit.project.read_configuration() + if config.file: + click.secho(f"A config file already exists: {config.file.name}", fg="yellow") + raise Exit(1) diff --git a/tests/test_cli.py b/tests/test_cli.py index 51083fb6..4df00a38 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -37,9 +37,10 @@ def test_simple_error(tmp_path): def test_config_file_already_exists(tmp_path, config_file): """Test if .nitpick.toml already exists.""" project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=True).save_file(config_file, "") - project.cli_init(f"A config file already exists: {config_file}") + project.cli_init(f"A config file already exists: {config_file}", exit_code=1) +# FIXME[AA]: # def test_create_basic_dot_nitpick_toml(tmp_path): # """If no config file is found, create a basic .nitpick.toml.""" # project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=True) From 5abd805c7950ebc5d35134eea06b9ee61728abf1 Mon Sep 17 00:00:00 2001 From: "Augusto W. Andreoli" Date: Tue, 6 Apr 2021 22:21:29 +0200 Subject: [PATCH 3/4] test: create basic config file --- src/nitpick/cli.py | 5 ++++- src/nitpick/project.py | 33 +++++++++++++++++++++++++++++++++ tests/test_cli.py | 20 ++++++++++++++------ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/nitpick/cli.py b/src/nitpick/cli.py index 162f154a..a8bcdbbb 100644 --- a/src/nitpick/cli.py +++ b/src/nitpick/cli.py @@ -19,7 +19,7 @@ from click.exceptions import Exit from loguru import logger -from nitpick.constants import PROJECT_NAME +from nitpick.constants import DOT_NITPICK_TOML, PROJECT_NAME from nitpick.core import Nitpick from nitpick.enums import OptionEnum from nitpick.exceptions import QuitComplainingError @@ -125,3 +125,6 @@ def init(context): if config.file: click.secho(f"A config file already exists: {config.file.name}", fg="yellow") raise Exit(1) + + nit.project.create_configuration() + click.secho(f"Config file created: {DOT_NITPICK_TOML}", fg="green") diff --git a/src/nitpick/project.py b/src/nitpick/project.py index 2e7b8836..46f1a4e9 100644 --- a/src/nitpick/project.py +++ b/src/nitpick/project.py @@ -3,6 +3,7 @@ from dataclasses import dataclass from functools import lru_cache from pathlib import Path +from textwrap import dedent from typing import Iterable, Iterator, List, Optional, Set import pluggy @@ -14,10 +15,12 @@ from nitpick import fields, plugins from nitpick.constants import ( CONFIG_FILES, + DOT_NITPICK_TOML, MANAGE_PY, NITPICK_MINIMUM_VERSION_JMEX, PROJECT_NAME, PYPROJECT_TOML, + READ_THE_DOCS_URL, ROOT_FILES, ROOT_PYTHON_FILES, TOOL_NITPICK, @@ -233,3 +236,33 @@ def merge_styles(self, offline: bool) -> Iterator[Fuss]: self.nitpick_section = self.style_dict.get("nitpick", {}) self.nitpick_files_section = self.nitpick_section.get("files", {}) + + def create_configuration(self) -> None: + """Create a configuration file.""" + from nitpick.style import Style # pylint: disable=import-outside-toplevel + + generated_by = "This file was generated by the `nitpick init` command" + more_info = f"More info at {READ_THE_DOCS_URL}configuration.html" + + # 1. This commented code generates an empty "[tool]" section above "[tool.nitpick]": + # doc = document() + # doc.add(comment(generated_by)) + # doc.add(comment(more_info)) + # doc["tool"] = table().add("nitpick", table().add("style", [Style.get_default_style_url()])) + + # 2. Using doc["tool.nitpick"] or doc.add("tool.nitpick", ...) will generate a section with quotes instead: + # ["tool.nitpick"] + + # So... giving up on tomlkit for now. ¯\_(ツ)_/¯ + + template = dedent( + f""" + # {generated_by} + # {more_info} + [{TOOL_NITPICK}] + style = ["{Style.get_default_style_url()}"] + """ + ) + + path: Path = self.root / DOT_NITPICK_TOML + path.write_text(template.lstrip()) diff --git a/tests/test_cli.py b/tests/test_cli.py index 4df00a38..054b66dc 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,7 +1,8 @@ """CLI tests.""" import pytest -from nitpick.constants import DOT_NITPICK_TOML, PYPROJECT_TOML +from nitpick.constants import DOT_NITPICK_TOML, PYPROJECT_TOML, READ_THE_DOCS_URL, TOOL_NITPICK +from nitpick.style import Style from tests.helpers import XFAIL_ON_WINDOWS, ProjectMock @@ -40,8 +41,15 @@ def test_config_file_already_exists(tmp_path, config_file): project.cli_init(f"A config file already exists: {config_file}", exit_code=1) -# FIXME[AA]: -# def test_create_basic_dot_nitpick_toml(tmp_path): -# """If no config file is found, create a basic .nitpick.toml.""" -# project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=True) -# project.cli_init() +def test_create_basic_dot_nitpick_toml(tmp_path): + """If no config file is found, create a basic .nitpick.toml.""" + project = ProjectMock(tmp_path, pyproject_toml=False, setup_py=True) + project.cli_init(f"Config file created: {DOT_NITPICK_TOML}").assert_file_contents( + DOT_NITPICK_TOML, + f""" + # This file was generated by the `nitpick init` command + # More info at {READ_THE_DOCS_URL}configuration.html + [{TOOL_NITPICK}] + style = ["{Style.get_default_style_url()}"] + """, + ) From 86108732d63d2fc27540adb9a965f9eff35f5fab Mon Sep 17 00:00:00 2001 From: "Augusto W. Andreoli" Date: Wed, 7 Apr 2021 01:42:12 +0200 Subject: [PATCH 4/4] refactor: create the file with tomlkit --- docs/cli.rst | 12 ++++++++++-- docs/configuration.rst | 4 +++- docs/generate_rst.py | 5 ++++- src/nitpick/cli.py | 2 +- src/nitpick/project.py | 31 +++++++------------------------ tests/test_cli.py | 1 + 6 files changed, 26 insertions(+), 29 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 4c14585c..80b492c9 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -27,6 +27,8 @@ The available commands are described below. .. auto-generated-from-here +.. _cli_cmd: + Main options ------------ @@ -45,10 +47,12 @@ Main options --help Show this message and exit. Commands: - init Initialise Nitpick configuration. + init Create a configuration file if it doesn't exist already. ls List of files configured in the Nitpick style. run Apply suggestions to configuration files. +.. _cli_cmd_run: + ``run``: Apply style to files ----------------------------- @@ -74,6 +78,8 @@ At the end of execution, this command displays: -v, --verbose Verbose logging --help Show this message and exit. +.. _cli_cmd_ls: + ``ls``: List configures files ----------------------------- @@ -90,6 +96,8 @@ At the end of execution, this command displays: Options: --help Show this message and exit. +.. _cli_cmd_init: + ``init``: Initialise a configuration file ----------------------------------------- @@ -98,7 +106,7 @@ At the end of execution, this command displays: Usage: nitpick init [OPTIONS] - Initialise Nitpick configuration. + Create a configuration file if it doesn't exist already. Options: --help Show this message and exit. diff --git a/docs/configuration.rst b/docs/configuration.rst index 72d1918d..90ef755a 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -16,7 +16,9 @@ Possible configuration files (in order of precedence): The first file found will be used; the other files will be ignored. -You can configure your own style like this: +Run the ``nipick init`` CLI command to create a config file (:ref:`cli_cmd_init`). + +To configure your own style: .. code-block:: toml diff --git a/docs/generate_rst.py b/docs/generate_rst.py index 0182da1b..75bc3e6e 100644 --- a/docs/generate_rst.py +++ b/docs/generate_rst.py @@ -198,6 +198,8 @@ def generate_plugins(filename: str) -> int: def generate_cli(filename: str) -> int: """Generate CLI docs.""" template = """ + .. _cli_cmd{anchor}: + {header} {dashes} {long} @@ -210,6 +212,7 @@ def generate_cli(filename: str) -> int: blocks = [] for command, short, long in CLI_MAPPING: + anchor = f"_{command}" if command else "" header = f"``{command}``: {short}" if command else short blocks.append("") parts = ["nitpick"] @@ -220,7 +223,7 @@ def generate_cli(filename: str) -> int: output = check_output(parts).decode().strip() # nosec blocks.append( clean_template.format( - header=header, dashes="-" * len(header), long=dedent(long), help=indent(output, " ") + anchor=anchor, header=header, dashes="-" * len(header), long=dedent(long), help=indent(output, " ") ) ) diff --git a/src/nitpick/cli.py b/src/nitpick/cli.py index a8bcdbbb..50c44618 100644 --- a/src/nitpick/cli.py +++ b/src/nitpick/cli.py @@ -119,7 +119,7 @@ def ls(context, files): # pylint: disable=invalid-name @nitpick_cli.command() @click.pass_context def init(context): - """Initialise Nitpick configuration.""" + """Create a configuration file if it doesn't exist already.""" nit = get_nitpick(context) config = nit.project.read_configuration() if config.file: diff --git a/src/nitpick/project.py b/src/nitpick/project.py index 46f1a4e9..3ba8dc71 100644 --- a/src/nitpick/project.py +++ b/src/nitpick/project.py @@ -3,7 +3,6 @@ from dataclasses import dataclass from functools import lru_cache from pathlib import Path -from textwrap import dedent from typing import Iterable, Iterator, List, Optional, Set import pluggy @@ -11,6 +10,8 @@ from loguru import logger from marshmallow_polyfield import PolyField from pluggy import PluginManager +from tomlkit import comment, document, dumps, table +from tomlkit.items import Key, KeyType from nitpick import fields, plugins from nitpick.constants import ( @@ -241,28 +242,10 @@ def create_configuration(self) -> None: """Create a configuration file.""" from nitpick.style import Style # pylint: disable=import-outside-toplevel - generated_by = "This file was generated by the `nitpick init` command" - more_info = f"More info at {READ_THE_DOCS_URL}configuration.html" - - # 1. This commented code generates an empty "[tool]" section above "[tool.nitpick]": - # doc = document() - # doc.add(comment(generated_by)) - # doc.add(comment(more_info)) - # doc["tool"] = table().add("nitpick", table().add("style", [Style.get_default_style_url()])) - - # 2. Using doc["tool.nitpick"] or doc.add("tool.nitpick", ...) will generate a section with quotes instead: - # ["tool.nitpick"] - - # So... giving up on tomlkit for now. ¯\_(ツ)_/¯ - - template = dedent( - f""" - # {generated_by} - # {more_info} - [{TOOL_NITPICK}] - style = ["{Style.get_default_style_url()}"] - """ - ) + doc = document() + doc.add(comment("This file was generated by the `nitpick init` command")) + doc.add(comment(f"More info at {READ_THE_DOCS_URL}configuration.html")) + doc.add(Key(TOOL_NITPICK, KeyType.Bare), table().add("style", [Style.get_default_style_url()])) path: Path = self.root / DOT_NITPICK_TOML - path.write_text(template.lstrip()) + path.write_text(dumps(doc, sort_keys=True)) diff --git a/tests/test_cli.py b/tests/test_cli.py index 054b66dc..82796aaa 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -49,6 +49,7 @@ def test_create_basic_dot_nitpick_toml(tmp_path): f""" # This file was generated by the `nitpick init` command # More info at {READ_THE_DOCS_URL}configuration.html + [{TOOL_NITPICK}] style = ["{Style.get_default_style_url()}"] """,