From afe3001224a5e772604faf5287fd893c5517e733 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Sat, 25 Nov 2023 11:35:17 -0600 Subject: [PATCH 1/8] Add pre-commit hooks --- .pre-commit-config.yaml | 2 ++ .pre-commit-hooks.yaml | 11 ++++++++++ README.rst | 20 +++++++++++++++++++ ...31125_112723_kurtmckee_pre_commit_hook.rst | 4 ++++ src/chipshot/cli.py | 9 +++++++++ 5 files changed, 46 insertions(+) create mode 100644 .pre-commit-hooks.yaml create mode 100644 changelog.d/20231125_112723_kurtmckee_pre_commit_hook.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 205b0cf..886656f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,6 +47,8 @@ repos: rev: '2.7.3' hooks: - id: 'editorconfig-checker' + # The README contains YAML that isn't indented using 4 spaces. + exclude: 'README.rst' # - repo: 'https://github.com/python-jsonschema/check-jsonschema' # rev: '0.27.1' diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..1ea82a3 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,11 @@ +- description: 'Check file headers are correct using Chipshot.' + id: 'check-headers' + name: 'Chipshot - Check headers' + entry: 'chipshot' + language: 'python' + +- description: 'Update file headers, if needed, using Chipshot.' + id: 'update-headers' + name: 'Chipshot - Update headers' + entry: 'chipshot --update' + language: 'python' diff --git a/README.rst b/README.rst index ae1964d..4c1fdcf 100644 --- a/README.rst +++ b/README.rst @@ -49,3 +49,23 @@ Next, add the following configuration to ``pyproject.toml``: You can then run ``chipshot path1 path2`` to see what files will be modified. If you're satisfied, run ``chipshot --update path1 path2`` to update the files. + + +Pre-commit hooks +================ + +Chipshot offers two pre-commit hooks to help you manage your projects: + +* ``check-headers`` +* ``update-headers`` + +Here's a sample configuration for ensuring your files have correct headers: + +.. code-block:: yaml + + # .pre-commit-config.yaml + repos: + - repo: 'https://github.com/kurtmckee/chipshot' + rev: 'main' + hooks: + - id: 'update-headers' diff --git a/changelog.d/20231125_112723_kurtmckee_pre_commit_hook.rst b/changelog.d/20231125_112723_kurtmckee_pre_commit_hook.rst new file mode 100644 index 0000000..d3f12f8 --- /dev/null +++ b/changelog.d/20231125_112723_kurtmckee_pre_commit_hook.rst @@ -0,0 +1,4 @@ +Added +----- + +* Add two pre-commit hooks: ``check-headers`` and ``update-headers``. diff --git a/src/chipshot/cli.py b/src/chipshot/cli.py index 4158c85..67a0071 100644 --- a/src/chipshot/cli.py +++ b/src/chipshot/cli.py @@ -10,6 +10,7 @@ import typing import click +import click.exceptions from . import compare, config, logger, reader, render, writer @@ -48,6 +49,8 @@ def run(config_file: str | None, update: bool, debug: bool, paths: tuple[str]) -> None: """Chipshot -- Set up game-winning headers!""" + files_updated = False + # Set up logging. logger.setup(enable_debug=debug) log = logging.getLogger(__name__) @@ -76,6 +79,7 @@ def run(config_file: str | None, update: bool, debug: bool, paths: tuple[str]) - # If this is a net-new header, log that information. if info.header and not info.original_header: log.info(f"{path}: Adding header (no original header found)") + files_updated = True # If there is an existing header, it might be kept or replaced. else: @@ -84,14 +88,19 @@ def run(config_file: str | None, update: bool, debug: bool, paths: tuple[str]) - # If the headers are sufficiently similar, replace the existing header. if similarity > 0.90: log.info(f"{path}: Updating header ({percentage} similarity)") + files_updated = True info.original_header = "" # The headers are sufficiently different. Keep the original header. else: log.info(f"{path}: Adding header ({percentage} similarity)") + files_updated = True if update: writer.write(info) + if files_updated: + raise click.exceptions.Exit(1) + def _get_files( paths: tuple[str], configuration: dict[str, typing.Any] From 21c253cc39050828798297a08888e24dd65f5e33 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Sun, 26 Nov 2023 13:47:38 -0600 Subject: [PATCH 2/8] Allow template literals in the config file --- README.rst | 9 ++-- assets/headers/global.txt | 3 -- ...704_kurtmckee_allow_template_in_config.rst | 6 +++ pyproject.toml | 7 +++- src/chipshot/exceptions.py | 4 ++ src/chipshot/render.py | 42 ++++++++++++++----- tests/conftest.py | 3 +- tests/test_render.py | 7 +--- 8 files changed, 56 insertions(+), 25 deletions(-) delete mode 100644 assets/headers/global.txt create mode 100644 changelog.d/20231126_120704_kurtmckee_allow_template_in_config.rst diff --git a/README.rst b/README.rst index 4c1fdcf..30e73a4 100644 --- a/README.rst +++ b/README.rst @@ -39,13 +39,16 @@ You can use ``{{ year }}`` as a stand-in for the current year. Released under the terms of the MIT license. SPDX-License-Identifier: MIT -Next, add the following configuration to ``pyproject.toml``: +Then, add the following configuration to ``pyproject.toml``: .. code-block:: toml [tool.chipshot] - template_root = "assets/headers" - template = "global.txt" + template = """ + Copyright 2021-{{ date }} Developer or Company + Released under the terms of the MIT license. + SPDX-License-Identifier: MIT + """ You can then run ``chipshot path1 path2`` to see what files will be modified. If you're satisfied, run ``chipshot --update path1 path2`` to update the files. diff --git a/assets/headers/global.txt b/assets/headers/global.txt deleted file mode 100644 index 6000b02..0000000 --- a/assets/headers/global.txt +++ /dev/null @@ -1,3 +0,0 @@ -This file is a part of Chipshot -Copyright 2022-{{ year }} Kurt McKee -SPDX-License-Identifier: MIT diff --git a/changelog.d/20231126_120704_kurtmckee_allow_template_in_config.rst b/changelog.d/20231126_120704_kurtmckee_allow_template_in_config.rst new file mode 100644 index 0000000..d21dcdf --- /dev/null +++ b/changelog.d/20231126_120704_kurtmckee_allow_template_in_config.rst @@ -0,0 +1,6 @@ +Changed +------- + +* Allow template literals in the config file using the ``template`` key. + + Paths to template files can be defined in the ``template_path`` key. diff --git a/pyproject.toml b/pyproject.toml index d591669..538c3e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,8 +37,11 @@ chipshot = "chipshot.cli:run" # -------- [tool.chipshot] -template_root = "assets/headers" -template = "global.txt" +template = """ +This file is a part of Chipshot +Copyright 2022-{{ year }} Kurt McKee +SPDX-License-Identifier: MIT +""" # Coverage diff --git a/src/chipshot/exceptions.py b/src/chipshot/exceptions.py index ea837d4..e91592b 100644 --- a/src/chipshot/exceptions.py +++ b/src/chipshot/exceptions.py @@ -15,6 +15,10 @@ class ConfigNotFound(ChipshotError): """A config file was successfully loaded but no Chipshot configuration was found.""" +class NoTemplateDefined(ChipshotError): + """No *template* or *template_path* was found.""" + + class FileDecodeError(ChipshotError): """A file could not be decoded.""" diff --git a/src/chipshot/render.py b/src/chipshot/render.py index e8bb49e..c943511 100644 --- a/src/chipshot/render.py +++ b/src/chipshot/render.py @@ -10,6 +10,8 @@ import jinja2 +import chipshot.exceptions + def render_header(file: pathlib.Path, configuration: dict[str, t.Any]) -> str: """Render a (possibly file-specific) template into a header.""" @@ -21,21 +23,41 @@ def render_header(file: pathlib.Path, configuration: dict[str, t.Any]) -> str: suffix_config = configuration["extension"].get(suffix, {}) # Look for the correct template to load. - template_root = configuration.get("template_root", "") - template_path = ( - suffixes_config.get("template") - or suffix_config.get("template") - or configuration["template"] - ) - template = pathlib.Path(template_root) / template_path + jinja_loader: jinja2.DictLoader | jinja2.FileSystemLoader + # Check for a literal template or a template path for the file's suffixes config. + if "template" in suffixes_config: + template_name = "literal" + jinja_loader = jinja2.DictLoader({"literal": suffixes_config["template"]}) + elif "template_path" in suffixes_config: + template_path = pathlib.Path(suffixes_config["template_path"]) + template_name = template_path.name + jinja_loader = jinja2.FileSystemLoader(template_path.parent) + # Check for a literal template or a template path for the file's suffix config. + elif "template" in suffix_config: + template_name = "literal" + jinja_loader = jinja2.DictLoader({"literal": suffix_config["template"]}) + elif "template_path" in suffix_config: + template_path = pathlib.Path(suffix_config["template_path"]) + template_name = template_path.name + jinja_loader = jinja2.FileSystemLoader(template_path.parent) + # Check for a literal template or a template path in the global config. + elif "template" in configuration: + template_name = "literal" + jinja_loader = jinja2.DictLoader({"literal": configuration["template"]}) + elif "template_path" in configuration: + template_path = pathlib.Path(configuration["template_path"]) + template_name = template_path.name + jinja_loader = jinja2.FileSystemLoader(template_path.parent) + else: + raise chipshot.exceptions.NoTemplateDefined # Render the template. env = jinja2.Environment( - loader=jinja2.FileSystemLoader(template.parent), + loader=jinja_loader, undefined=jinja2.StrictUndefined, ) - template = env.get_template(template.name) - rendered_template: bytes = template.render( + template = env.get_template(template_name) + rendered_template: str = template.render( { "year": datetime.datetime.now().strftime("%Y"), }, diff --git a/tests/conftest.py b/tests/conftest.py index 187089b..35e2c3b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,7 +9,8 @@ @pytest.fixture(scope="session") def _load_default_config_once(): - return chipshot.config.load() + defaults = chipshot.config._load_default_config() + return chipshot.config._normalize_config(defaults) @pytest.fixture diff --git a/tests/test_render.py b/tests/test_render.py index 888fc26..ac492de 100644 --- a/tests/test_render.py +++ b/tests/test_render.py @@ -14,12 +14,7 @@ @pytest.mark.parametrize("rendered_file", rendered_files) def test_render_header(default_config, rendered_file: pathlib.Path): - default_config.update( - { - "template_root": str(rendered_file.parent), - "template": "template.txt", - } - ) + default_config.update({"template_path": str(rendered_file.parent / "template.txt")}) expected = rendered_file.read_text() rendered = csr.render_header(rendered_file, default_config) fail_message = textwrap.dedent( From 70921a15672e32eaf3d9cc66a09bcf8986571cbe Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Sun, 26 Nov 2023 14:09:56 -0600 Subject: [PATCH 3/8] Load `.chipshot.toml` as the first default config file --- ...652_kurtmckee_allow_template_in_config.rst | 7 ++++ src/chipshot/cli.py | 4 ++- src/chipshot/config.py | 36 +++++++++++++++---- 3 files changed, 39 insertions(+), 8 deletions(-) create mode 100644 changelog.d/20231126_140652_kurtmckee_allow_template_in_config.rst diff --git a/changelog.d/20231126_140652_kurtmckee_allow_template_in_config.rst b/changelog.d/20231126_140652_kurtmckee_allow_template_in_config.rst new file mode 100644 index 0000000..119b7c2 --- /dev/null +++ b/changelog.d/20231126_140652_kurtmckee_allow_template_in_config.rst @@ -0,0 +1,7 @@ +Changed +------- + +* When no configuration file is specified, try to load ``.chipshot.toml`` first. + + ``pyproject.toml`` will still be loaded as a fallback + if ``.chipshot.toml`` doesn't exist. diff --git a/src/chipshot/cli.py b/src/chipshot/cli.py index 67a0071..d5d6f0c 100644 --- a/src/chipshot/cli.py +++ b/src/chipshot/cli.py @@ -25,7 +25,9 @@ """ The Chipshot configuration file to use. - If unspecified, 'pyproject.toml' in the current directory will be loaded. + If unspecified, '.chipshot.toml' in the current directory will be loaded. + If that doesn't exist, 'pyproject.toml' will be tried next. + Chipshot's default values will always be loaded first. """ ), diff --git a/src/chipshot/config.py b/src/chipshot/config.py index 9e934dd..da83e0c 100644 --- a/src/chipshot/config.py +++ b/src/chipshot/config.py @@ -28,25 +28,47 @@ def load(path: str | pathlib.Path | None = None) -> dict[str, t.Any]: """Load the default configuration and any specified config, if any.""" - config = _load_default_config() + # Try to load '.chipshot.toml' if no path was specified. + if path is None: + chipshot_toml = pathlib.Path(".chipshot.toml") + if chipshot_toml.is_file(): + log.debug("Defaulting to load a '.chipshot.toml' file") + path = chipshot_toml + + # If no path was specified and '.chipshot.toml' doesn't exist, + # try to load 'pyproject.toml'. if path is None: - # Try to load 'pyproject.toml'. pyproject_toml = pathlib.Path("pyproject.toml") if pyproject_toml.is_file(): - config.update(_load_toml(pyproject_toml)) - else: + log.debug("Defaulting to load a 'pyproject.toml' file") + path = pyproject_toml + + # If *path* is a string, convert it to a Path object. + if isinstance(path, str): path = pathlib.Path(path) - config.update(_load_toml(path)) + + custom_config = {} + if path is not None: + log.debug(f"Loading config file '{path}'") + custom_config = _load_toml(path) + + config = _load_default_config() + config.update(custom_config) return _normalize_config(config) def _load_toml(path: pathlib.Path) -> dict[str, t.Any]: - """Load configuration from a `pyproject.toml` file.""" + """Load configuration from a TOML file.""" toml = tomllib.loads(path.read_text("utf-8")) try: - config = toml["tool"]["chipshot"] + if path.name == "pyproject.toml": + log.debug("Looking for config in the 'tool.chipshot' key") + config = toml["tool"]["chipshot"] + else: # All other configuration files use a top-level "chipshot" key. + log.debug("Looking for config in the 'chipshot' key") + config = toml["chipshot"] if not isinstance(config, dict): raise exceptions.BadConfig(f"'tool.chipshot' in {path} is not a valid type") except KeyError: From 4697fa74457c9a8c8f743b9ead9ae7ebbf1b0341 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Sun, 26 Nov 2023 14:18:47 -0600 Subject: [PATCH 4/8] Rename the `--debug` flag to `--verbose` --- ..._141734_kurtmckee_allow_template_in_config.rst | 4 ++++ src/chipshot/cli.py | 15 +++++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 changelog.d/20231126_141734_kurtmckee_allow_template_in_config.rst diff --git a/changelog.d/20231126_141734_kurtmckee_allow_template_in_config.rst b/changelog.d/20231126_141734_kurtmckee_allow_template_in_config.rst new file mode 100644 index 0000000..b0e0439 --- /dev/null +++ b/changelog.d/20231126_141734_kurtmckee_allow_template_in_config.rst @@ -0,0 +1,4 @@ +Changed +------- + +* Rename the ``--debug`` flag to ``--verbose``. diff --git a/src/chipshot/cli.py b/src/chipshot/cli.py index d5d6f0c..4232e75 100644 --- a/src/chipshot/cli.py +++ b/src/chipshot/cli.py @@ -34,27 +34,30 @@ type=click.Path(exists=True, file_okay=True, dir_okay=False), ) @click.option( - "--debug", + "--update", is_flag=True, - help="Enable logging of debug messages.", + help="Update files in-place.", ) @click.option( - "--update", + "-v", + "--verbose", is_flag=True, - help="Update files in-place.", + help="Enable verbose output.", ) @click.argument( "paths", nargs=-1, type=click.Path(exists=True, file_okay=True, dir_okay=True), ) -def run(config_file: str | None, update: bool, debug: bool, paths: tuple[str]) -> None: +def run( + config_file: str | None, update: bool, verbose: bool, paths: tuple[str] +) -> None: """Chipshot -- Set up game-winning headers!""" files_updated = False # Set up logging. - logger.setup(enable_debug=debug) + logger.setup(enable_debug=verbose) log = logging.getLogger(__name__) # Load the configuration. From b3dd05fc9d468ed94fd9b498838c0f0aaab81e5a Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Sun, 26 Nov 2023 14:34:27 -0600 Subject: [PATCH 5/8] Allow the test suite to pass --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 538c3e1..0f561c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ source = [ [tool.coverage.report] skip_covered = true -fail_under = 82 +fail_under = 76 # Mypy From 5429b53225f303c5588040a0957892f2c07ac0f8 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Sun, 26 Nov 2023 14:53:37 -0600 Subject: [PATCH 6/8] Rename Poetry groups and tox environments --- poetry.lock | 2 +- pyproject.toml | 4 ++-- requirements/{type-linting.txt => mypy.txt} | 0 requirements/{testing.txt => test.txt} | 0 tox.ini | 12 ++++++------ 5 files changed, 9 insertions(+), 9 deletions(-) rename requirements/{type-linting.txt => mypy.txt} (100%) rename requirements/{testing.txt => test.txt} (100%) diff --git a/poetry.lock b/poetry.lock index 8e90868..c90b4ea 100644 --- a/poetry.lock +++ b/poetry.lock @@ -402,4 +402,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "3c9c9ed49319e674375823cd48bb1b9be4b5a26f8befd537b1b32f5213d868fc" +content-hash = "f7135456067a118442a49ec0aec7b1f7e1363d1ba5a609391d08893fa3828b82" diff --git a/pyproject.toml b/pyproject.toml index 0f561c2..b3226cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,13 +20,13 @@ importlib_resources = { version = "*", python = "<3.9" } jinja2 = "*" tomli = { version = "*", python = "<3.11" } -[tool.poetry.group.testing.dependencies] +[tool.poetry.group.test.dependencies] coverage = { version = "*", extras = ["toml"] } pyfakefs = "*" pytest = "*" pytest-randomly = "*" -[tool.poetry.group.type-linting.dependencies] +[tool.poetry.group.mypy.dependencies] mypy = "*" [tool.poetry.scripts] diff --git a/requirements/type-linting.txt b/requirements/mypy.txt similarity index 100% rename from requirements/type-linting.txt rename to requirements/mypy.txt diff --git a/requirements/testing.txt b/requirements/test.txt similarity index 100% rename from requirements/testing.txt rename to requirements/test.txt diff --git a/tox.ini b/tox.ini index df37392..2d0fd8b 100644 --- a/tox.ini +++ b/tox.ini @@ -3,13 +3,13 @@ envlist = coverage-erase py{3.12, 3.11, 3.10, 3.9, 3.8} coverage-report - type-linting + mypy isolated_build = True [testenv] depends = py{3.12, 3.11, 3.10, 3.9, 3.8}: coverage-erase -deps = -rrequirements/testing.txt +deps = -rrequirements/test.txt commands = coverage run -m pytest [testenv:coverage-erase] @@ -31,8 +31,8 @@ commands = commands_post = coverage html --fail-under=0 -[testenv:type-linting] -deps = -rrequirements/type-linting.txt +[testenv:mypy] +deps = -rrequirements/mypy.txt setenv = MYPY_FORCE_COLOR=1 commands = mypy @@ -46,8 +46,8 @@ deps = commands = # Update poetry.lock, and the testing dependencies. poetry update - poetry export --only=testing --without-hashes --output requirements/testing.txt - poetry export --only=type-linting --without-hashes --output requirements/type-linting.txt + poetry export --only=test --without-hashes --output requirements/test.txt + poetry export --only=mypy --without-hashes --output requirements/mypy.txt # Update the pre-commit hooks and additional dependencies. pre-commit autoupdate From 8654b38a6ae60f0433c9b3af3225c162e9d44c0f Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Sun, 26 Nov 2023 23:31:12 -0600 Subject: [PATCH 7/8] Add initial documentation --- .gitignore | 1 + .pre-commit-config.yaml | 8 +- .readthedocs.yaml | 14 + README.rst | 19 +- TODO.rst | 14 + assets/banner.svg | 186 +++++++++ assets/openmoji-soccer-ball-26BD-2023.svg | 23 ++ .../20231128_092055_kurtmckee_add_docs.rst | 4 + docs/_static/banner.png | Bin 0 -> 36193 bytes docs/_static/custom.css | 17 + docs/_static/logo.png | Bin 0 -> 7056 bytes docs/conf.py | 35 ++ docs/index.rst | 78 ++++ docs/reference/boms.rst | 25 ++ docs/tutorial/configuring.rst | 47 +++ docs/tutorial/installing.rst | 71 ++++ docs/tutorial/overview.rst | 162 ++++++++ poetry.lock | 390 +++++++++++++++++- pyproject.toml | 3 + requirements/docs.txt | 23 ++ src/chipshot/cli.py | 7 + tox.ini | 10 + 22 files changed, 1116 insertions(+), 21 deletions(-) create mode 100644 .readthedocs.yaml create mode 100644 TODO.rst create mode 100644 assets/banner.svg create mode 100644 assets/openmoji-soccer-ball-26BD-2023.svg create mode 100644 changelog.d/20231128_092055_kurtmckee_add_docs.rst create mode 100644 docs/_static/banner.png create mode 100644 docs/_static/custom.css create mode 100644 docs/_static/logo.png create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/reference/boms.rst create mode 100644 docs/tutorial/configuring.rst create mode 100644 docs/tutorial/installing.rst create mode 100644 docs/tutorial/overview.rst create mode 100644 requirements/docs.txt diff --git a/.gitignore b/.gitignore index 78467a3..ad2fbdd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.tox/ /.pytest_cache/ +/build/ /dist/ /venv/ /.coverage.* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 886656f..df9e5d1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,9 +50,9 @@ repos: # The README contains YAML that isn't indented using 4 spaces. exclude: 'README.rst' -# - repo: 'https://github.com/python-jsonschema/check-jsonschema' -# rev: '0.27.1' -# hooks: + - repo: 'https://github.com/python-jsonschema/check-jsonschema' + rev: '0.27.2' + hooks: + - id: 'check-readthedocs' # - id: 'check-dependabot' # - id: 'check-github-workflows' -# - id: 'check-readthedocs' diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..1d49f34 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,14 @@ +version: 2 + +build: + os: 'ubuntu-22.04' + tools: + python: '3.12' + +sphinx: + configuration: 'docs/conf.py' + fail_on_warning: true + +python: + install: + - requirements: 'requirements/docs.txt' diff --git a/README.rst b/README.rst index 30e73a4..0d0ce99 100644 --- a/README.rst +++ b/README.rst @@ -26,26 +26,13 @@ but can be configured to support those, too. Sample configuration ==================== -Create a directory that will contain your header template. -For example, the directory might be named ``assets/headers``. - -Then, create a text file that will contain your header template, -such as ``global.txt``. -You can use ``{{ year }}`` as a stand-in for the current year. - -.. code-block:: text - - Copyright 2021-{{ date }} Developer or Company - Released under the terms of the MIT license. - SPDX-License-Identifier: MIT - -Then, add the following configuration to ``pyproject.toml``: +Create a file named ``.chipshot.toml`` with the following content: .. code-block:: toml - [tool.chipshot] + [chipshot] template = """ - Copyright 2021-{{ date }} Developer or Company + Copyright 2021-{{ year }} Developer or Company Released under the terms of the MIT license. SPDX-License-Identifier: MIT """ diff --git a/TODO.rst b/TODO.rst new file mode 100644 index 0000000..3e41ddf --- /dev/null +++ b/TODO.rst @@ -0,0 +1,14 @@ +* add CI +* write documentation +* publish to readthedocs +* catch exceptions and report them +* allow configuring different headers for different file types. + this allows for a license for docs and a license for code +* allow file paths (for example, ``scripts/*`` or ``script-without-suffix``) +* allow custom encodings +* detect encodings using ``.editorconfig`` +* ignore files using ``.gitignore`` +* use multiprocessing for faster work +* update how similarity is calculated + (it doesn't consider all words and reports 100% similarity) +* test against major repos diff --git a/assets/banner.svg b/assets/banner.svg new file mode 100644 index 0000000..6f20d14 --- /dev/null +++ b/assets/banner.svg @@ -0,0 +1,186 @@ + + + +Set up game-winning headers!chipshot diff --git a/assets/openmoji-soccer-ball-26BD-2023.svg b/assets/openmoji-soccer-ball-26BD-2023.svg new file mode 100644 index 0000000..7a55b8a --- /dev/null +++ b/assets/openmoji-soccer-ball-26BD-2023.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/changelog.d/20231128_092055_kurtmckee_add_docs.rst b/changelog.d/20231128_092055_kurtmckee_add_docs.rst new file mode 100644 index 0000000..4a9ec49 --- /dev/null +++ b/changelog.d/20231128_092055_kurtmckee_add_docs.rst @@ -0,0 +1,4 @@ +Documentation +------------- + +* Add initial documentation. diff --git a/docs/_static/banner.png b/docs/_static/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..89309dda1d5ceb2197207b797e38f46a9df839f2 GIT binary patch literal 36193 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 literal 0 HcmV?d00001 diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..836db48 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,17 @@ +/* + * The CSS code sample comes from: + * https://www.a11yproject.com/posts/how-to-hide-content/ + * + * The CSS selector comes from: + * https://github.com/pallets/pallets-sphinx-themes/blob/1512b53b/src/pallets_sphinx_themes/themes/pocoo/static/pocoo.css#L486-L498 + */ + +.visually-hidden > h1:first-child { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} diff --git a/docs/_static/logo.png b/docs/_static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f584449d7f0bfc864a4fc6605aa97ac4e7e4ada6 GIT binary patch literal 7056 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 + SPDX-License-Identifier: MIT + """ + +Headers will be rendered as comments based on the file extension. + +.. rubric:: Python +.. code-block:: python + + # Copyright 2022-2023 Company Name + # SPDX-License-Identifier: MIT + +.. rubric:: C +.. code-block:: c + + /* + * Copyright 2022-2023 Company Name + * SPDX-License-Identifier: MIT + */ + + +Getting Started +=============== + +.. toctree:: + :maxdepth: 1 + + tutorial/overview + tutorial/installing + tutorial/configuring + +* Default comment styles gallery + + +How-To Guides +============= + +* How to customize comment styles +* How to integrate Chipshot in your everyday development + + +Reference +========= + +.. toctree:: + :maxdepth: 1 + + reference/boms + +* Configuration file format +* Pre-commit hooks +* CLI options diff --git a/docs/reference/boms.rst b/docs/reference/boms.rst new file mode 100644 index 0000000..216e217 --- /dev/null +++ b/docs/reference/boms.rst @@ -0,0 +1,25 @@ +Byte Order Marks (BOMs) +####################### + +Chipshot initially reads all files as binary. +If the first bytes correspond to a known byte order mark (BOM), +Chipshot will decode the file using the encoding scheme indicated by the BOM. + +The encoding associated with the BOM is always respected, +even if Chipshot is configured to expect a different encoding. +If the file cannot be decoded using the BOM-indicated encoding, +Chipshot will not try to use any other encoding. + +If Chipshot updates the file, the BOM will be retained. + +These are the BOMs that Chipshot understands, +and the associated encoding that will be used if a BOM is encountered. + +.. csv-table:: + :header: "BOM characters", "Encoding" + + "``00 00 fe ff``", "UTF-32 (Big Endian)" + "``ff fe 00 00``", "UTF-32 (Little Endian)" + "``fe ff``", "UTF-16 (Big Endian)" + "``ff fe``", "UTF-16 (Little Endian)" + "``ef bb bf``", "UTF-8" diff --git a/docs/tutorial/configuring.rst b/docs/tutorial/configuring.rst new file mode 100644 index 0000000..10eb4fb --- /dev/null +++ b/docs/tutorial/configuring.rst @@ -0,0 +1,47 @@ +Configuring Chipshot +#################### + +Chipshot needs to know what header text to put at the tops of your files. +This is configured in one of two files by default: + +* ``.chipshot.toml`` +* ``pyproject.toml`` + +This section of the documentation will show you +how to configure Chipshot using ``.chipshot.toml``. + + +Header Templates +================ + +Open your favorite text editor and put this text into it: + +.. code-block:: toml + + [chipshot] + template = """ + Copyright {{ year }} Developer + """ + +Save the file as ``.chipshot.toml``. +Then, at the command line, run Chipshot +and pass a directory containing source code files as the first argument. +By default, Chipshot won't update any files; +it will simply tell you what it believes it should do with the file. + +.. code-block:: console + + $ chipshot src/ + INFO: src/no_header.py: Adding header (no original header found) + INFO: src/has_comments_but_no_header.py: Adding header (29.46% similarity) + INFO: src/has_header_with_different_email.py: Updating header (96.46% similarity) + +If the output looks correct to you, +add the ``--update`` flag to have Chipshot make changes to the files. + +.. code-block:: console + + $ chipshot --update src/ + INFO: src/no_header.py: Adding header (no original header found) + INFO: src/has_comments_but_no_header.py: Adding header (29.46% similarity) + INFO: src/has_header_with_different_email.py: Updating header (96.46% similarity) diff --git a/docs/tutorial/installing.rst b/docs/tutorial/installing.rst new file mode 100644 index 0000000..159c890 --- /dev/null +++ b/docs/tutorial/installing.rst @@ -0,0 +1,71 @@ +Installing Chipshot +################### + +Chipshot is published to the Python Package Index (PyPI). +If you have a preferred way to install Python packages, +feel free to use that method. + + +pipx +==== + +`pipx`_ is an excellent way to install Python applications. +If you already have pipx installed on your system, +you can use it to install Chipshot. + +.. code-block:: console + + $ pipx install chipshot + $ chipshot --version + + +pip +=== + +If you have Python installed, then you have pip installed. +However, you should avoid installing Chipshot into your system Python's packages. + +There are two methods you can follow: + +#. Create a virtual environment +#. Install Chipshot in your Python user packages + +The virtual environment method is preferable +because it will help isolate Chipshot's Python dependencies +and reduce the possibility of a conflict in dependency versions, +but you will have to activate the virtual environment +each time you want to use Chipshot. + +Virtual Environment Method +-------------------------- + +.. rubric:: Linux/macOS +.. code-block:: console + + $ python -m venv venv + $ . venv/bin/activate + (venv) $ python -m pip install chipshot + (venv) $ chipshot --version + +.. rubric:: Windows +.. code-block:: doscon + + C:\Users\Me> python -m venv venv + C:\Users\Me> venv\Scripts\activate.bat + (venv) C:\Users\Me> python -m pip install chipshot + (venv) C:\Users\Me> chipshot --version + +Python User Directory Method +---------------------------- + +.. rubric:: Linux/macOS/Windows +.. code-block:: console + + $ python -m pip install --user chipshot + $ chipshot --version + + +.. Links +.. ----- +.. +.. _pipx: https://pypa.github.io/pipx/ diff --git a/docs/tutorial/overview.rst b/docs/tutorial/overview.rst new file mode 100644 index 0000000..cb499b1 --- /dev/null +++ b/docs/tutorial/overview.rst @@ -0,0 +1,162 @@ +An overview of Chipshot's features +################################## + +Chipshot helps ensure source code headers are consistent. +You tell it what the header text should be using a *template*, +and it will render the header as a comment based on the file extension. + +Not all file formats use the same types of comment styles or conventions, +so Chipshot works to respect: + +* :ref:`comment-styles` +* :ref:`prologues` +* :ref:`boms` +* :ref:`newlines` + +In addition, Chipshot is flexible, and supports a great deal of customization: + +* `multiple-templates` +* :ref:`custom-styles` + + +Throughout this overview, the following header template will be used: + +.. code-block:: text + + Copyright 2022-{{ year }} Company Name + Licensed under the terms of the MIT license. + +Note that ``{{ year }}`` will be rendered as ``2023`` in this document. + + +.. _comment-styles: + +Comment styles +============== + +Different programming and markup languages have different comment styles. +Here is a small sample of comment styles that Chipshot supports by default. + +.. rubric:: Python +.. code-block:: python + + # Copyright 2022-2023 Company Name + # Licensed under the terms of the MIT license. + +.. rubric:: ReStructuredText +.. code-block:: rst + + .. + Copyright 2022-2023 Company Name + Licensed under the terms of the MIT license. + +.. rubric:: SQL +.. code-block:: sql + + -- Copyright 2022-2023 Company Name + -- Licensed under the terms of the MIT license. + +.. rubric:: XML +.. code-block:: xml + + + + +.. _prologues: + +Document Prologues +================== + +Some files have mandatory *prologues* -- content that MUST appear on the first line or lines. +For example, many executable script files must start with a hashbang line +so the operating system knows how to execute the file. + +If Chipshot finds a hashbang in a file format that supports it, +Chipshot will always add or update headers after the hashbang: + +.. rubric:: Example: A hashbang in a NodeJS shell script +.. code-block:: js + + #!/usr/bin/env node + + // Copyright 2022-2023 Company Name + // Licensed under the terms of the MIT license. + + console.log("Hello, World!"); + +Executable scripts aren't the only place where the first line is important; +XML requires declarations to appear before comments, too. + +.. rubric:: Example: An XML declaration +.. code-block:: xml + + + + + + +.. _boms: + +Unicode Byte Order Marks +======================== + +Some programming languages and file formats rely on Unicode byte order marks (BOMs) +to enable Unicode features. For example, AutoHotkey relies on UTF-8 BOMs +to indicate when it should switch from ANSI to Unicode. + +When Chipshot encounters a byte order mark in a file +it will always decode the file using the encoding indicated by the BOM. +If Chipshot adds or updates a header, +it will always retain the original BOM. + +.. note:: + + Chipshot does not add nor remove BOMs; + it only retains existing BOMs. + + +.. _newlines: + +Newlines +======== + +Different files in your repository may use different newline styles. +When Chipshot updates headers, it respects each file's existing newline style. + +.. note:: + + Chipshot does not standardize newlines; + it only retains the existing newline style. + + +.. _custom-styles: + +Custom Styles +============= + +Chipshot allows custom comment styles, +so it's possible to add new styles as needed. + +.. rubric:: Example: A custom style in ``.chipshot.toml`` for PHP scripts +.. code-block:: toml + + [chipshot.extension.php] + block_prefix = "" + +This will result in a header rendered like this: + +.. rubric:: Example: A rendered PHP header +.. code-block:: php + + diff --git a/poetry.lock b/poetry.lock index c90b4ea..d560d6f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,143 @@ # This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "babel" +version = "2.13.1" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, + {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, +] + +[package.dependencies] +setuptools = {version = "*", markers = "python_version >= \"3.12\""} + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "certifi" +version = "2023.11.17" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + [[package]] name = "click" version = "8.1.7" @@ -92,6 +230,17 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1 [package.extras] toml = ["tomli"] +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + [[package]] name = "exceptiongroup" version = "1.2.0" @@ -106,6 +255,28 @@ files = [ [package.extras] test = ["pytest (>=6)"] +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + [[package]] name = "importlib-metadata" version = "6.8.0" @@ -325,6 +496,21 @@ files = [ {file = "pyfakefs-5.3.1.tar.gz", hash = "sha256:dd1fb374039fadccf35d3f3df7aa5d239482e0650dcd240e053d3b9e78740918"}, ] +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + [[package]] name = "pytest" version = "7.4.3" @@ -362,6 +548,192 @@ files = [ importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} pytest = "*" +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "69.0.2" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, + {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "sphinx" +version = "7.2.6" +description = "Python documentation generator" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, + {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.21" +imagesize = ">=1.3" +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.14" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.7" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-1.0.7-py3-none-any.whl", hash = "sha256:094c4d56209d1734e7d252f6e0b3ccc090bd52ee56807a5d9315b19c122ab15d"}, + {file = "sphinxcontrib_applehelp-1.0.7.tar.gz", hash = "sha256:39fdc8d762d33b01a7d8f026a3b7d71563ea3b72787d5f00ad8465bd9d6dfbfa"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.5" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-1.0.5-py3-none-any.whl", hash = "sha256:fe8009aed765188f08fcaadbb3ea0d90ce8ae2d76710b7e29ea7d047177dae2f"}, + {file = "sphinxcontrib_devhelp-1.0.5.tar.gz", hash = "sha256:63b41e0d38207ca40ebbeabcf4d8e51f76c03e78cd61abe118cf4435c73d4212"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.4" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.0.4-py3-none-any.whl", hash = "sha256:8001661c077a73c29beaf4a79968d0726103c5605e27db92b9ebed8bab1359e9"}, + {file = "sphinxcontrib_htmlhelp-2.0.4.tar.gz", hash = "sha256:6c26a118a05b76000738429b724a0568dbde5b72391a688577da08f11891092a"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.6" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-1.0.6-py3-none-any.whl", hash = "sha256:bf76886ee7470b934e363da7a954ea2825650013d367728588732c7350f49ea4"}, + {file = "sphinxcontrib_qthelp-1.0.6.tar.gz", hash = "sha256:62b9d1a186ab7f5ee3356d906f648cacb7a6bdb94d201ee7adf26db55092982d"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.9" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-1.1.9-py3-none-any.whl", hash = "sha256:9b36e503703ff04f20e9675771df105e58aa029cfcbc23b8ed716019b7416ae1"}, + {file = "sphinxcontrib_serializinghtml-1.1.9.tar.gz", hash = "sha256:0c64ff898339e1fac29abd2bf5f11078f3ec413cfe9c046d3120d7ca65530b54"}, +] + +[package.dependencies] +Sphinx = ">=5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + [[package]] name = "tomli" version = "2.0.1" @@ -384,6 +756,22 @@ files = [ {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] +[[package]] +name = "urllib3" +version = "2.1.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "zipp" version = "3.17.0" @@ -402,4 +790,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "f7135456067a118442a49ec0aec7b1f7e1363d1ba5a609391d08893fa3828b82" +content-hash = "02f834911323a7b9bc641bab64832e9efd1a5cab6c14dc3152b3e35ae804b991" diff --git a/pyproject.toml b/pyproject.toml index b3226cb..42d7ba6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,9 @@ pytest-randomly = "*" [tool.poetry.group.mypy.dependencies] mypy = "*" +[tool.poetry.group.docs.dependencies] +sphinx = { version = "*", python = "3.12" } + [tool.poetry.scripts] chipshot = "chipshot.cli:run" diff --git a/requirements/docs.txt b/requirements/docs.txt new file mode 100644 index 0000000..db63035 --- /dev/null +++ b/requirements/docs.txt @@ -0,0 +1,23 @@ +alabaster==0.7.13 ; python_version == "3.12" +babel==2.13.1 ; python_version == "3.12" +certifi==2023.11.17 ; python_version == "3.12" +charset-normalizer==3.3.2 ; python_version == "3.12" +colorama==0.4.6 ; python_version == "3.12" and sys_platform == "win32" +docutils==0.20.1 ; python_version == "3.12" +idna==3.6 ; python_version == "3.12" +imagesize==1.4.1 ; python_version == "3.12" +jinja2==3.1.2 ; python_version == "3.12" +markupsafe==2.1.3 ; python_version == "3.12" +packaging==23.2 ; python_version == "3.12" +pygments==2.17.2 ; python_version == "3.12" +requests==2.31.0 ; python_version == "3.12" +setuptools==69.0.2 ; python_version == "3.12" +snowballstemmer==2.2.0 ; python_version == "3.12" +sphinx==7.2.6 ; python_version == "3.12" +sphinxcontrib-applehelp==1.0.7 ; python_version == "3.12" +sphinxcontrib-devhelp==1.0.5 ; python_version == "3.12" +sphinxcontrib-htmlhelp==2.0.4 ; python_version == "3.12" +sphinxcontrib-jsmath==1.0.1 ; python_version == "3.12" +sphinxcontrib-qthelp==1.0.6 ; python_version == "3.12" +sphinxcontrib-serializinghtml==1.1.9 ; python_version == "3.12" +urllib3==2.1.0 ; python_version == "3.12" diff --git a/src/chipshot/cli.py b/src/chipshot/cli.py index 4232e75..3abefa3 100644 --- a/src/chipshot/cli.py +++ b/src/chipshot/cli.py @@ -17,6 +17,13 @@ @click.command() @click.help_option("-h", "--help") +@click.version_option( + None, + "-V", + "--version", + prog_name="Chipshot", + message="%(prog)s v%(version)s", +) @click.option( "-c", "--config", diff --git a/tox.ini b/tox.ini index 2d0fd8b..3710a61 100644 --- a/tox.ini +++ b/tox.ini @@ -4,6 +4,8 @@ envlist = py{3.12, 3.11, 3.10, 3.9, 3.8} coverage-report mypy + docs + isolated_build = True [testenv] @@ -37,6 +39,13 @@ setenv = MYPY_FORCE_COLOR=1 commands = mypy +[testenv:docs] +base_python = py3.12 +skip_install = true +deps = -rrequirements/docs.txt +commands = + sphinx-build -aWEnqb html docs/ build/docs + [testenv:update] skip_install = true deps = @@ -48,6 +57,7 @@ commands = poetry update poetry export --only=test --without-hashes --output requirements/test.txt poetry export --only=mypy --without-hashes --output requirements/mypy.txt + poetry export --only=docs --without-hashes --output requirements/docs.txt # Update the pre-commit hooks and additional dependencies. pre-commit autoupdate From fdd668701a11c7597ee294fb04ebfbdb3d6c4db2 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 28 Nov 2023 09:34:46 -0600 Subject: [PATCH 8/8] Update project metadata --- .pre-commit-config.yaml | 2 +- CHANGELOG.rst | 30 +++++++++++++++++++ ...31125_112723_kurtmckee_pre_commit_hook.rst | 4 --- ...704_kurtmckee_allow_template_in_config.rst | 6 ---- ...652_kurtmckee_allow_template_in_config.rst | 7 ----- ...734_kurtmckee_allow_template_in_config.rst | 4 --- .../20231128_092055_kurtmckee_add_docs.rst | 4 --- pyproject.toml | 4 ++- 8 files changed, 34 insertions(+), 27 deletions(-) delete mode 100644 changelog.d/20231125_112723_kurtmckee_pre_commit_hook.rst delete mode 100644 changelog.d/20231126_120704_kurtmckee_allow_template_in_config.rst delete mode 100644 changelog.d/20231126_140652_kurtmckee_allow_template_in_config.rst delete mode 100644 changelog.d/20231126_141734_kurtmckee_allow_template_in_config.rst delete mode 100644 changelog.d/20231128_092055_kurtmckee_add_docs.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index df9e5d1..2d58e90 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,7 +41,7 @@ repos: hooks: - id: 'flake8' additional_dependencies: - - 'flake8-bugbear==23.9.16' + - 'flake8-bugbear==23.11.26' - repo: 'https://github.com/editorconfig-checker/editorconfig-checker.python' rev: '2.7.3' diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3b9d121..bc7a371 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,36 @@ Please see the fragment files in the `changelog.d directory`_. .. scriv-insert-here +.. _changelog-0.2.0: + +0.2.0 - 2023-11-28 +================== + +Added +----- + +* Add two pre-commit hooks: ``check-headers`` and ``update-headers``. + +Changed +------- + +* Allow template literals in the config file using the ``template`` key. + + Paths to template files can be defined in the ``template_path`` key. + +* When no configuration file is specified, + ``.chipshot.toml`` will be loaded first (if it exists). + + ``pyproject.toml`` will still be loaded as a fallback + if ``.chipshot.toml`` doesn't exist. + +* Rename the ``--debug`` flag to ``--verbose``. + +Documentation +------------- + +* Add initial documentation. + .. _changelog-0.1.0: 0.1.0 - 2023-11-24 diff --git a/changelog.d/20231125_112723_kurtmckee_pre_commit_hook.rst b/changelog.d/20231125_112723_kurtmckee_pre_commit_hook.rst deleted file mode 100644 index d3f12f8..0000000 --- a/changelog.d/20231125_112723_kurtmckee_pre_commit_hook.rst +++ /dev/null @@ -1,4 +0,0 @@ -Added ------ - -* Add two pre-commit hooks: ``check-headers`` and ``update-headers``. diff --git a/changelog.d/20231126_120704_kurtmckee_allow_template_in_config.rst b/changelog.d/20231126_120704_kurtmckee_allow_template_in_config.rst deleted file mode 100644 index d21dcdf..0000000 --- a/changelog.d/20231126_120704_kurtmckee_allow_template_in_config.rst +++ /dev/null @@ -1,6 +0,0 @@ -Changed -------- - -* Allow template literals in the config file using the ``template`` key. - - Paths to template files can be defined in the ``template_path`` key. diff --git a/changelog.d/20231126_140652_kurtmckee_allow_template_in_config.rst b/changelog.d/20231126_140652_kurtmckee_allow_template_in_config.rst deleted file mode 100644 index 119b7c2..0000000 --- a/changelog.d/20231126_140652_kurtmckee_allow_template_in_config.rst +++ /dev/null @@ -1,7 +0,0 @@ -Changed -------- - -* When no configuration file is specified, try to load ``.chipshot.toml`` first. - - ``pyproject.toml`` will still be loaded as a fallback - if ``.chipshot.toml`` doesn't exist. diff --git a/changelog.d/20231126_141734_kurtmckee_allow_template_in_config.rst b/changelog.d/20231126_141734_kurtmckee_allow_template_in_config.rst deleted file mode 100644 index b0e0439..0000000 --- a/changelog.d/20231126_141734_kurtmckee_allow_template_in_config.rst +++ /dev/null @@ -1,4 +0,0 @@ -Changed -------- - -* Rename the ``--debug`` flag to ``--verbose``. diff --git a/changelog.d/20231128_092055_kurtmckee_add_docs.rst b/changelog.d/20231128_092055_kurtmckee_add_docs.rst deleted file mode 100644 index 4a9ec49..0000000 --- a/changelog.d/20231128_092055_kurtmckee_add_docs.rst +++ /dev/null @@ -1,4 +0,0 @@ -Documentation -------------- - -* Add initial documentation. diff --git a/pyproject.toml b/pyproject.toml index 42d7ba6..84f39df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,11 +7,13 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "chipshot" -version = "0.1.0" +version = "0.2.0" description = "Set up game-winning headers!" readme = "README.rst" authors = ["Kurt McKee "] license = "MIT" +repository = "https://github.com/kurtmckee/chipshot" +documentation = "https://chipshot.readthedocs.io" [tool.poetry.dependencies] python = ">=3.8"