diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
deleted file mode 100644
index 9feaefa9..00000000
--- a/.github/workflows/documentation.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-name: Sphinx Documentation
-
-on:
- push:
- branches: ["dev"]
-
- # Allows you to run this workflow manually from the Actions tab
- workflow_dispatch:
-
-# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
-permissions:
- contents: read
- pages: write
- id-token: write
-
-# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
-# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
-concurrency:
- group: "pages"
- cancel-in-progress: false
-
-jobs:
- build:
- runs-on: ubuntu-latest
-
- environment:
- name: github-pages
- url: ${{ steps.deployment.outputs.page_url }}
-
- steps:
-
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- submodules: true
- fetch-tags: true
-
- - name: Install poetry
- run: pipx install poetry
-
- - name: Setup Python
- uses: actions/setup-python@v5
- with:
- python-version: 3.12
- cache: 'poetry'
-
- - run: poetry install
-
- - run: |
- poetry run make -C docs html
- echo "google-site-verification: google72e937875c790d13.html" > docs/_build/html/google72e937875c790d13.html
-
- - name: Setup Pages
- uses: actions/configure-pages@v4
-
- - name: Upload artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: 'docs/_build/html'
-
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
-
diff --git a/docs/changelogs/unreleased.rst b/docs/changelogs/unreleased.rst
index 572fe253..feb8be4b 100644
--- a/docs/changelogs/unreleased.rst
+++ b/docs/changelogs/unreleased.rst
@@ -9,5 +9,6 @@ Added
Fixed
:::::
-* UI: Fixed the topology UI where changing the state of a remote would cause the page to crash (#578, @ArthurD1).
+* DX: Add the ability to use --set x=y or --set x y (both equivalent) to all commands (@masterivanic, #590)
* UI: Correctly display header values containing a semicolon character (#577, @ArthurD1)
+* UI: Fixed the topology UI where changing the state of a remote would cause the page to crash (#578, @ArthurD1).
diff --git a/docs/contribute/intro-harp-start.excalidraw.png b/docs/contribute/intro-harp-start.excalidraw.png
new file mode 100644
index 00000000..704033d9
Binary files /dev/null and b/docs/contribute/intro-harp-start.excalidraw.png differ
diff --git a/docs/contribute/introduction.rst b/docs/contribute/introduction.rst
index b19e8ca7..a374fb21 100644
--- a/docs/contribute/introduction.rst
+++ b/docs/contribute/introduction.rst
@@ -53,7 +53,9 @@ environment.
Running
:::::::
-You can start your first HARP server based on your local working copy, we'll use one of the built-in examples:
+You can start your first HARP development server based on your local working copy.
+
+Here, we'll run one using one of the built-in examples (but the same applies to any configuration, of course):
.. code-block:: bash
@@ -61,6 +63,23 @@ You can start your first HARP server based on your local working copy, we'll use
Open your browser at http://localhost:4080 to have a look at the HARP dashboard.
+Using ``harp start`` (or ``poetry run harp start`` to let poetry manage the env, our preference) will spawn
+a bunch of processes, managed by `honcho `_, with some free cherries.
+
+The default processes are:
+
+- ``harp server``: the main server, python based, which will listen for incoming requests on different ports. By
+ default, it listens to the 4080 port for the dashboard. Wrapped using `watchfiles
+ `_ to restart on code changes.
+- the dashboard's devserver (`vite `_ based): listens to a high random unprivileged port by default,
+ and the main server will forward requests tto this port. Meaning that harp dashboard's goes through the http proxy.
+ Kinda meta, right?
+
+.. figure:: ./intro-harp-start.excalidraw.png
+ :scale: 40%
+
+You can read more about the various ``harp`` commands in the :doc:`Command Line Reference `.
+
Interfaces
::::::::::
diff --git a/harp/commandline/config.py b/harp/commandline/config.py
index 107fac67..9296b258 100644
--- a/harp/commandline/config.py
+++ b/harp/commandline/config.py
@@ -6,14 +6,13 @@
from rich.syntax import Syntax
from rich.tree import Tree
-from harp.commandline.options.server import CommonServerOptions, add_harp_server_click_options
+from harp.commandline.options.server import CommonServerOptions, server_command
from harp.config import ConfigurationBuilder
from harp.config.asdict import asdict
from harp.utils.commandline import click
-@click.command("config", short_help="Prints the current configuration.")
-@add_harp_server_click_options
+@server_command("config", short_help="Prints the current configuration.")
@click.option("--raw", is_flag=True, help="Prints the raw configuration as a dictionary.")
@click.option("--json", is_flag=True, help="Prints the raw configuration as JSON.")
@click.option(
diff --git a/harp/commandline/cookiecutters/project/LICENSE b/harp/commandline/cookiecutters/project/LICENSE
index 7d3841d0..7af87514 100644
--- a/harp/commandline/cookiecutters/project/LICENSE
+++ b/harp/commandline/cookiecutters/project/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 {{cookiecutter.author}}
+Copyright (c) 2024 {{cookiecutter.author_name}} <{{cookiecutter.author_email}}>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/harp/commandline/cookiecutters/project/cookiecutter.json b/harp/commandline/cookiecutters/project/cookiecutter.json
index ad277b3a..5f5661e7 100644
--- a/harp/commandline/cookiecutters/project/cookiecutter.json
+++ b/harp/commandline/cookiecutters/project/cookiecutter.json
@@ -2,7 +2,15 @@
"name": "HARP Project",
"__dir_name": "{{cookiecutter.name | slugify}}",
"__pkg_name": "{{cookiecutter.name | slugify(separator='_')}}",
- "author": "Smart Anonymous Harpist",
+ "author_name": "Smart Anonymous Harpist",
+ "author_email": "anonymous@harp-proxy.net",
"create_application": false,
- "create_config": true
+ "create_config": true,
+ "__prompts__": {
+ "name": "Project name",
+ "author_name": "Author name",
+ "author_email": "Author email",
+ "create_application": "Create application folder ?",
+ "create_config": "Create config file ?"
+ }
}
diff --git a/harp/commandline/cookiecutters/project/hooks/post_gen_project.sh b/harp/commandline/cookiecutters/project/hooks/post_gen_project.sh
index fb6c56d3..7219129b 100644
--- a/harp/commandline/cookiecutters/project/hooks/post_gen_project.sh
+++ b/harp/commandline/cookiecutters/project/hooks/post_gen_project.sh
@@ -1,12 +1,16 @@
#! /bin/bash
if [ "{{cookiecutter.create_application}}" == "False" ]; then
- echo "Removing application folder (create_application == False)."
rm -rf ./{{cookiecutter.__pkg_name}}
fi
if [ "{{cookiecutter.create_config}}" == "False" ]; then
- echo "Removing config file (create_config == False)."
rm -f ./config.yml
fi
+
+echo "Congratulations, your HARP project «{{cookiecutter.name}}» has been created in «{{cookiecutter.__dir_name}}»."
+echo
+echo "To install the project dependencies, run '(cd {{cookiecutter.__dir_name}} && make install)'."
+echo "To run the tests, run '(cd {{cookiecutter.__dir_name}} && make test)'."
+echo "To start your project, run '(cd {{cookiecutter.__dir_name}} && make)'."
diff --git a/harp/commandline/cookiecutters/project/{{cookiecutter.__dir_name}}/pyproject.toml b/harp/commandline/cookiecutters/project/{{cookiecutter.__dir_name}}/pyproject.toml
index 67c27232..5c99621d 100644
--- a/harp/commandline/cookiecutters/project/{{cookiecutter.__dir_name}}/pyproject.toml
+++ b/harp/commandline/cookiecutters/project/{{cookiecutter.__dir_name}}/pyproject.toml
@@ -2,14 +2,18 @@
name = "{{cookiecutter.__pkg_name}}"
version = "0.1.0"
description = ""
-authors = ["{{cookiecutter.author}}"]
+authors = [
+ "{{cookiecutter.author_name}} <{{cookiecutter.author_email}}>",
+]
readme = "README.rst"
+{% if not cookiecutter.create_application -%}
+package-mode = false
+{%- endif %}
[tool.poetry.dependencies]
python = "^3.12"
harp-proxy = "*"
-
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
diff --git a/harp/commandline/create.py b/harp/commandline/create.py
index d1000816..2a947da7 100644
--- a/harp/commandline/create.py
+++ b/harp/commandline/create.py
@@ -10,7 +10,10 @@ def create(template):
"""Creates a new project using cookiecutter."""
if not check_packages("cookiecutter"):
- raise click.UsageError("You need to install cookiecutter to use this command (or use the harp[dev] extra).")
+ raise click.UsageError(
+ "You need to install cookiecutter to use this command (or use the `dev` extra, for example using "
+ "`pip install harp-proxy[dev]` or `poetry install -E dev`)."
+ )
from cookiecutter.main import cookiecutter
diff --git a/harp/commandline/migrations.py b/harp/commandline/migrations.py
index 2ca5d9e9..a8bbaacd 100644
--- a/harp/commandline/migrations.py
+++ b/harp/commandline/migrations.py
@@ -9,7 +9,7 @@
from sqlalchemy.ext.asyncio import create_async_engine
from harp import get_logger
-from harp.commandline.options.server import add_harp_server_click_options
+from harp.commandline.options.server import server_command
from harp.utils.commandline import click
from harp_apps.storage.utils.migrations import (
create_alembic_config,
@@ -21,8 +21,7 @@
logger = get_logger(__name__)
-@click.command("db:migrate")
-@add_harp_server_click_options
+@server_command("db:migrate")
@click.argument("operation", nargs=1, type=click.Choice(["up", "down"]))
@click.argument("revision", nargs=1)
@click.option("--reset", is_flag=True, help="Reset the database (drop all before migrations).")
@@ -44,8 +43,7 @@ def migrate(*, operation, revision, reset=False, **kwargs):
migrate = cast(BaseCommand, migrate)
-@click.command("db:create-migration")
-@add_harp_server_click_options
+@server_command("db:create-migration")
@click.argument("message", nargs=1)
def create_migration(*, message, **kwargs):
settings = create_harp_settings_with_storage_from_command_line_options(kwargs)
@@ -56,8 +54,7 @@ def create_migration(*, message, **kwargs):
create_migration = cast(BaseCommand, create_migration)
-@click.command("db:merge")
-@add_harp_server_click_options
+@server_command("db:merge")
@click.argument("message", nargs=1)
@click.argument("revisions", nargs=-1)
def run_db_merge_command(*, message, revisions, **kwargs):
@@ -69,10 +66,9 @@ def run_db_merge_command(*, message, revisions, **kwargs):
run_db_merge_command = cast(BaseCommand, run_db_merge_command)
-@click.command("db:feature")
+@server_command("db:feature")
@click.argument("operation", nargs=1, type=click.Choice(["add", "remove"]))
@click.argument("features", nargs=-1)
-@add_harp_server_click_options
def feature(features, operation, **kwargs):
settings = create_harp_settings_with_storage_from_command_line_options(kwargs)
alembic_cfg = create_alembic_config(settings.get("storage").url)
@@ -96,8 +92,7 @@ def feature(features, operation, **kwargs):
feature = cast(BaseCommand, feature)
-@click.command("db:history")
-@add_harp_server_click_options
+@server_command("db:history")
def history(**kwargs):
settings = create_harp_settings_with_storage_from_command_line_options(kwargs)
alembic_cfg = create_alembic_config(settings.get("storage").url)
@@ -107,8 +102,7 @@ def history(**kwargs):
history = cast(BaseCommand, history)
-@click.command("db:reset")
-@add_harp_server_click_options
+@server_command("db:reset")
def reset(**kwargs):
settings = create_harp_settings_with_storage_from_command_line_options(kwargs)
alembic_cfg = create_alembic_config(settings.get("storage").url)
diff --git a/harp/commandline/options/server.py b/harp/commandline/options/server.py
index 669bb81f..58c56c6c 100644
--- a/harp/commandline/options/server.py
+++ b/harp/commandline/options/server.py
@@ -1,7 +1,10 @@
from dataclasses import dataclass, field
from itertools import chain
from shlex import quote
-from typing import Iterable
+from typing import Any, Callable, Iterable, List, Optional, Type, Union
+
+from click import Command
+from click.decorators import CmdType
from harp.utils.commandline import click, code
@@ -16,8 +19,8 @@ def __post_init__(self):
self.options = dict(map(lambda x: x.split("=", 1), self.options))
-def _parse_option(x):
- key, value = x.split("=", 1)
+def _parse_option(option: tuple[str, str]) -> tuple[str, Union[str, bool]]:
+ key, value = option
if value == "true":
value = True
elif value == "false":
@@ -57,7 +60,7 @@ def __post_init__(self):
self.endpoints = dict(map(lambda x: x.split("=", 1), self.endpoints))
-def add_harp_config_options(f):
+def _config_click_options(f):
"""
Decorate a click command to add configuration options, in the right order.
"""
@@ -83,7 +86,8 @@ def add_harp_config_options(f):
"--set",
"options",
multiple=True,
- help=f"Add configuration options (e.g. {code('--set foo=bar')}, can be used multiple times).",
+ type=(str, str),
+ help=f"Add configuration options (e.g. {code('--set foo=bar')} or {code('--set foo bar')}, can be used multiple times).",
),
]
@@ -95,7 +99,7 @@ def add_harp_config_options(f):
return f
-def add_harp_server_click_options(f):
+def _server_click_options(f):
"""
Decorate a click command to add common server options, in the right order.
"""
@@ -123,6 +127,64 @@ def add_harp_server_click_options(f):
for option in reversed(options):
f = option(f)
- f = add_harp_config_options(f)
+ f = _config_click_options(f)
return f
+
+
+class _EnhancedParserCommand(click.Command):
+ """
+ This class override parse_args click function parse args enter in cli when type command with space or = for --set
+ command:
+
+ eg: --set arg=value or arg value
+ """
+
+ def parse_args(self, ctx: click.Context, args: List[str]) -> List[str]:
+ index = 0
+ while index < len(args):
+ if args[index] == "--set":
+ if index + 1 < len(args):
+ # if an equal sign is present, we expand the argument into two separated arguments
+ if "=" in args[index + 1]:
+ args = args[: index + 1] + args[index + 1].split("=") + args[index + 2 :]
+ index += 1
+ index += 1
+ return super().parse_args(ctx, args)
+
+
+def server_command(
+ name: Union[Optional[str], Callable[..., Any]] = None,
+ cls: Type[CmdType] = _EnhancedParserCommand,
+ **attrs: Any,
+) -> Union[Command, Callable[[Callable[..., Any]], Union[Command, CmdType]]]:
+ """
+ Creates a click command with server options (--enable, --disable, --applications, --endpoint and all configuration
+ options).
+ """
+
+ def decorator(f: [Callable[..., Any]]) -> CmdType:
+ command_decorator = click.command(name=name, cls=cls, **attrs)
+ f = _server_click_options(f)
+ f = command_decorator(f)
+ return f
+
+ return decorator
+
+
+def config_command(
+ name: Union[Optional[str], Callable[..., Any]] = None,
+ cls: Type[CmdType] = _EnhancedParserCommand,
+ **attrs: Any,
+) -> Union[Command, Callable[[Callable[..., Any]], Union[Command, CmdType]]]:
+ """
+ Creates a click command with configuration options (--set, --example, --file).
+ """
+
+ def decorator(f: [Callable[..., Any]]) -> CmdType:
+ command_decorator = click.command(name=name, cls=cls, **attrs)
+ f = _config_click_options(f)
+ f = command_decorator(f)
+ return f
+
+ return decorator
diff --git a/harp/commandline/server.py b/harp/commandline/server.py
index 7af41735..508f6234 100644
--- a/harp/commandline/server.py
+++ b/harp/commandline/server.py
@@ -3,18 +3,16 @@
from click import BaseCommand
from harp import run
-from harp.commandline.options.server import CommonServerOptions, add_harp_server_click_options
+from harp.commandline.options.server import CommonServerOptions, server_command
from harp.config import ConfigurationBuilder
from harp.settings import USE_PROMETHEUS
-from harp.utils.commandline import click
-@click.command(
+@server_command(
short_help="Starts HARP server.",
help="""Starts HARP server, using the provided configuration. This is the main process and will be the only process
you need on a live server, it will serve both the proxy ports and the compiled frontend assets (dashboard).""",
)
-@add_harp_server_click_options
def server(**kwargs):
_info = None
if USE_PROMETHEUS:
diff --git a/harp/commandline/start.py b/harp/commandline/start.py
index db2e8809..14e3d660 100644
--- a/harp/commandline/start.py
+++ b/harp/commandline/start.py
@@ -1,7 +1,8 @@
import importlib.util
import sys
-from harp.commandline.server import CommonServerOptions, add_harp_server_click_options
+from harp.commandline.options.server import server_command
+from harp.commandline.server import CommonServerOptions
from harp.commandline.utils.manager import HARP_DASHBOARD_SERVICE
from harp.utils.commandline import click, code
@@ -22,7 +23,7 @@ def assert_development_packages_are_available():
assert_package_is_available("watchfiles")
-@click.command(
+@server_command(
short_help="Starts the local development environment.",
help=f"""Starts the local development environment, using honcho to spawn a configurable set of processes that you
can adapt to your needs. By default, it will starts the `dashboard` (frontend dev server) and `server` (python
@@ -51,7 +52,6 @@ def assert_development_packages_are_available():
multiple=True,
help="Add a server subprocess to the list of services to start (experimental, can be used multiple times).",
)
-@add_harp_server_click_options
@click.argument("services", nargs=-1)
def start(with_docs, with_ui, services, server_subprocesses, mock, **kwargs):
try:
diff --git a/harp/commandline/tests/test_options_server.py b/harp/commandline/tests/test_options_server.py
new file mode 100644
index 00000000..ac3836b0
--- /dev/null
+++ b/harp/commandline/tests/test_options_server.py
@@ -0,0 +1,77 @@
+import click
+import pytest
+from click import Context, UsageError
+
+from harp.commandline.options.server import _EnhancedParserCommand
+from harp.commandline.server import CommonServerOptions
+
+
+def test_default():
+ options = CommonServerOptions(options=(), files=(), enable=(), disable=())
+ assert options.as_list() == []
+
+
+def test_applications():
+ options = CommonServerOptions(options=(), files=(), enable=("foo", "bar"), disable=("baz", "blurp"))
+ assert options.as_list() == [
+ "--enable foo",
+ "--enable bar",
+ "--disable baz",
+ "--disable blurp",
+ ]
+
+
+def test_parse_args_default_behaviour():
+ @click.command(cls=_EnhancedParserCommand)
+ @click.option("--other")
+ def cmd(): ...
+
+ assert _call_command(cmd, "--other", "value") == {"other": "value"}
+ with pytest.raises(UsageError):
+ _call_command(cmd, "--other", "value", "unknown")
+
+
+def test_parse_args_set_behaviour():
+ @click.command(cls=_EnhancedParserCommand)
+ @click.option("--set", "options", multiple=True, type=(str, str))
+ def cmd(): ...
+
+ assert _call_command(cmd, "--set", "arg=value", "--set", "foo", "bar") == {
+ "options": (("arg", "value"), ("foo", "bar"))
+ }
+
+
+def test_parse_args_mixed_arguments_behaviour():
+ @click.command(cls=_EnhancedParserCommand)
+ @click.option("--foo")
+ @click.option("--bar")
+ @click.option("--set", "options", multiple=True, type=(str, str))
+ @click.argument("positional", nargs=-1)
+ def cmd(): ...
+
+ assert _call_command(
+ cmd,
+ "--foo",
+ "foo",
+ "something",
+ "--set",
+ "arg=value",
+ "completely",
+ "--set",
+ "foo",
+ "bar",
+ "different",
+ "--bar",
+ "bar",
+ ) == {
+ "foo": "foo",
+ "bar": "bar",
+ "options": (("arg", "value"), ("foo", "bar")),
+ "positional": ("something", "completely", "different"),
+ }
+
+
+def _call_command(cmd, *args):
+ ctx = Context(cmd)
+ cmd.parse_args(ctx, list(args))
+ return ctx.params
diff --git a/harp/commandline/tests/test_server_options.py b/harp/commandline/tests/test_server_options.py
deleted file mode 100644
index 2f02b0e5..00000000
--- a/harp/commandline/tests/test_server_options.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from harp.commandline.server import CommonServerOptions
-
-
-def test_default():
- options = CommonServerOptions(options=(), files=(), enable=(), disable=())
- assert options.as_list() == []
-
-
-def test_applications():
- options = CommonServerOptions(options=(), files=(), enable=("foo", "bar"), disable=("baz", "blurp"))
- assert options.as_list() == [
- "--enable foo",
- "--enable bar",
- "--disable baz",
- "--disable blurp",
- ]
diff --git a/harp_apps/rules/commandline/lint.py b/harp_apps/rules/commandline/lint.py
index e1146bb1..0bea40a6 100644
--- a/harp_apps/rules/commandline/lint.py
+++ b/harp_apps/rules/commandline/lint.py
@@ -1,15 +1,13 @@
from rich.syntax import Syntax
from rich.tree import Tree
-from harp.commandline.options.server import add_harp_config_options
-from harp.utils.commandline import click
+from harp.commandline.options.server import config_command
from harp.utils.console import console
from .utils.loaders import load_ruleset_from_files
-@click.command("lint")
-@add_harp_config_options
+@config_command("lint")
def lint_command(files, examples, options):
"""Lint the rules."""
diff --git a/harp_apps/rules/commandline/run.py b/harp_apps/rules/commandline/run.py
index 975f3ae1..f398f881 100644
--- a/harp_apps/rules/commandline/run.py
+++ b/harp_apps/rules/commandline/run.py
@@ -4,7 +4,7 @@
import click
from whistle import IAsyncEventDispatcher
-from harp.commandline.options.server import add_harp_config_options
+from harp.commandline.options.server import config_command
from harp.config import ConfigurationBuilder
from harp.event_dispatcher import LoggingAsyncEventDispatcher
from harp.http import HttpRequest
@@ -25,8 +25,7 @@
from .utils.subscribers import DebugRulesSubscriber
-@click.command("run")
-@add_harp_config_options
+@config_command("run")
@click.argument("endpoint")
@click.argument("method")
@click.argument("path")
diff --git a/poetry.lock b/poetry.lock
index 0419781c..ffeae22f 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -3208,29 +3208,29 @@ files = [
[[package]]
name = "ruff"
-version = "0.7.2"
+version = "0.7.3"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.7.2-py3-none-linux_armv6l.whl", hash = "sha256:b73f873b5f52092e63ed540adefc3c36f1f803790ecf2590e1df8bf0a9f72cb8"},
- {file = "ruff-0.7.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5b813ef26db1015953daf476202585512afd6a6862a02cde63f3bafb53d0b2d4"},
- {file = "ruff-0.7.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:853277dbd9675810c6826dad7a428d52a11760744508340e66bf46f8be9701d9"},
- {file = "ruff-0.7.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21aae53ab1490a52bf4e3bf520c10ce120987b047c494cacf4edad0ba0888da2"},
- {file = "ruff-0.7.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc7e0fc6e0cb3168443eeadb6445285abaae75142ee22b2b72c27d790ab60ba"},
- {file = "ruff-0.7.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd77877a4e43b3a98e5ef4715ba3862105e299af0c48942cc6d51ba3d97dc859"},
- {file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e00163fb897d35523c70d71a46fbaa43bf7bf9af0f4534c53ea5b96b2e03397b"},
- {file = "ruff-0.7.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3c54b538633482dc342e9b634d91168fe8cc56b30a4b4f99287f4e339103e88"},
- {file = "ruff-0.7.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b792468e9804a204be221b14257566669d1db5c00d6bb335996e5cd7004ba80"},
- {file = "ruff-0.7.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba53ed84ac19ae4bfb4ea4bf0172550a2285fa27fbb13e3746f04c80f7fa088"},
- {file = "ruff-0.7.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b19fafe261bf741bca2764c14cbb4ee1819b67adb63ebc2db6401dcd652e3748"},
- {file = "ruff-0.7.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28bd8220f4d8f79d590db9e2f6a0674f75ddbc3847277dd44ac1f8d30684b828"},
- {file = "ruff-0.7.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9fd67094e77efbea932e62b5d2483006154794040abb3a5072e659096415ae1e"},
- {file = "ruff-0.7.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:576305393998b7bd6c46018f8104ea3a9cb3fa7908c21d8580e3274a3b04b691"},
- {file = "ruff-0.7.2-py3-none-win32.whl", hash = "sha256:fa993cfc9f0ff11187e82de874dfc3611df80852540331bc85c75809c93253a8"},
- {file = "ruff-0.7.2-py3-none-win_amd64.whl", hash = "sha256:dd8800cbe0254e06b8fec585e97554047fb82c894973f7ff18558eee33d1cb88"},
- {file = "ruff-0.7.2-py3-none-win_arm64.whl", hash = "sha256:bb8368cd45bba3f57bb29cbb8d64b4a33f8415d0149d2655c5c8539452ce7760"},
- {file = "ruff-0.7.2.tar.gz", hash = "sha256:2b14e77293380e475b4e3a7a368e14549288ed2931fce259a6f99978669e844f"},
+ {file = "ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344"},
+ {file = "ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0"},
+ {file = "ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9"},
+ {file = "ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5"},
+ {file = "ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299"},
+ {file = "ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e"},
+ {file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29"},
+ {file = "ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5"},
+ {file = "ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67"},
+ {file = "ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2"},
+ {file = "ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d"},
+ {file = "ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2"},
+ {file = "ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2"},
+ {file = "ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16"},
+ {file = "ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc"},
+ {file = "ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088"},
+ {file = "ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c"},
+ {file = "ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313"},
]
[[package]]
diff --git a/pyproject.toml b/pyproject.toml
index a5699388..e7f77623 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -115,6 +115,9 @@ line-length = 120
[tool.ruff]
line-length = 120
+exclude = [
+ "harp/commandline/cookiecutters",
+]
[tool.ruff.format]
docstring-code-format = true