Skip to content

Commit

Permalink
Merge branch 'main' into 162/pytest-assert-error
Browse files Browse the repository at this point in the history
  • Loading branch information
lengau authored Nov 19, 2024
2 parents 0f842af + 042d83a commit 97a709c
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 73 deletions.
18 changes: 15 additions & 3 deletions .github/workflows/release-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ jobs:
uses: actions/checkout@v4
- name: Fetch tag annotations
run: |
# Note: we fetch the tags here instead of using actions/checkout's "fetch-tags"
# because of https://github.com/actions/checkout/issues/1467
git fetch --force --tags --depth 1
git describe --dirty --long --match '[0-9]*.[0-9]*.[0-9]*' --exclude '*[^0-9.]*'
- name: Setup Python
Expand All @@ -37,24 +39,34 @@ jobs:
pypi:
needs: ["source-wheel"]
runs-on: [self-hosted, jammy]
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
- name: Get packages
uses: actions/download-artifact@v4
with:
name: pypi-packages
path: dist/
- name: Publish to pypi
# Note: this action uses PyPI's support for Trusted Publishers
# It needs a configuration on the PyPI project - see:
# https://docs.pypi.org/trusted-publishers/adding-a-publisher/#github-actions
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
github-release:
needs: ["source-wheel"]
runs-on: [self-hosted, jammy]
steps:
- name: Get pypi artifacts
uses: actions/download-artifact@v4
with:
name: pypi-packages
- name: Release
uses: softprops/action-gh-release@v2
with:
# Generate release notes on the new GH release
generate_release_notes: true
# Add wheel and source tarball
files: |
**
*.whl
*.tar.gz
17 changes: 17 additions & 0 deletions .github/workflows/security-scan.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Security scan
on:
pull_request:
push:
branches:
- main
- hotfix/*
- work/secscan # For development

jobs:
python-scans:
name: Scan Python project
uses: canonical/starflow/.github/workflows/scan-python.yaml@main
with:
packages: python-apt-dev
# Ignore this requirements file because it does relative path things.
requirements-find-args: "! -path './docs/.sphinx/*'"
23 changes: 15 additions & 8 deletions craft_cli/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,19 +209,22 @@ class Dispatcher:
:param extra_global_args: other automatic global arguments than the ones
provided automatically
:param default_command: the command to run if none was specified in the command line
:param docs_base_url: The base address of the documentation, for help messages.
"""

def __init__(
def __init__( # noqa: PLR0913 (too-many-arguments)
self,
appname: str,
commands_groups: list[CommandGroup],
*,
summary: str = "",
extra_global_args: list[GlobalArgument] | None = None,
default_command: type[BaseCommand] | None = None,
docs_base_url: str | None = None,
) -> None:
self._default_command = default_command
self._help_builder = HelpBuilder(appname, summary, commands_groups)
self._docs_base_url = docs_base_url
self._help_builder = HelpBuilder(appname, summary, commands_groups, docs_base_url)

self.global_arguments = _DEFAULT_GLOBAL_ARGS[:]
if extra_global_args is not None:
Expand Down Expand Up @@ -281,7 +284,7 @@ def _build_usage_exc(self, text: str) -> ArgumentParsingError:
return ArgumentParsingError(self._help_builder.get_usage_message(text))

def _get_requested_help( # noqa: PLR0912 (too many branches)
self, parameters: list[str]
self, parameters: list[str], app_config: Any
) -> str:
"""Produce the requested help depending on the rest of the command line params."""
if len(parameters) == 0:
Expand Down Expand Up @@ -332,7 +335,7 @@ def _get_requested_help( # noqa: PLR0912 (too many branches)
raise self._build_usage_exc(msg) from None

# instantiate the command and fill its arguments
command = cmd_class(None)
command = cmd_class(app_config)
parser = _CustomArgumentParser(self._help_builder, prog=command.name, add_help=False)
command.fill_parser(parser)

Expand All @@ -356,7 +359,9 @@ def _get_requested_help( # noqa: PLR0912 (too many branches)

def _build_no_command_error(self, missing_command: str) -> str:
"""Build the error help text for missing command, providing options."""
all_alternatives = self.commands.keys()
all_alternatives = [
name for (name, command) in self.commands.items() if not command.hidden
]
similar = difflib.get_close_matches(missing_command, all_alternatives)
if len(similar) == 0:
extra_similar = ""
Expand Down Expand Up @@ -413,7 +418,7 @@ def _parse_options( # noqa: PLR0912 (too many branches)
filtered_sysargs.append(sysarg)
return global_args, filtered_sysargs

def pre_parse_args(self, sysargs: list[str]) -> dict[str, Any]:
def pre_parse_args(self, sysargs: list[str], app_config: Any = None) -> dict[str, Any]:
"""Pre-parse sys args.
Several steps:
Expand All @@ -423,6 +428,8 @@ def pre_parse_args(self, sysargs: list[str]) -> dict[str, Any]:
- validate global options and apply them
- validate that command is correct (NOT loading and parsing its arguments)
If provided, ``app_config`` is passed to the command to be validated.
"""
global_args, filtered_sysargs = self._parse_options(self.global_arguments, sysargs)

Expand All @@ -448,7 +455,7 @@ def pre_parse_args(self, sysargs: list[str]) -> dict[str, Any]:

# handle requested help through -h/--help options
if global_args["help"]:
help_text = self._get_requested_help(filtered_sysargs)
help_text = self._get_requested_help(filtered_sysargs, app_config)
raise ProvideHelpException(help_text)

if not filtered_sysargs or filtered_sysargs[0].startswith("-"):
Expand All @@ -466,7 +473,7 @@ def pre_parse_args(self, sysargs: list[str]) -> dict[str, Any]:

# handle requested help through implicit "help" command
if command == "help":
help_text = self._get_requested_help(cmd_args)
help_text = self._get_requested_help(cmd_args, app_config)
raise ProvideHelpException(help_text)

self._command_args = cmd_args
Expand Down
77 changes: 49 additions & 28 deletions craft_cli/helptexts.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,25 @@ class HelpBuilder:
"""Produce the different help texts."""

def __init__(
self, appname: str, general_summary: str, command_groups: list[CommandGroup]
self,
appname: str,
general_summary: str,
command_groups: list[CommandGroup],
docs_base_url: str | None = None,
) -> None:
"""Initialize the help builder.
:param appname: The name of the application.
:param general_summary: A summary of the application.
:param command_groups: The CommandGroups for the application.
:param docs_base_url: The base URL for the documentation.
"""
self.appname = appname
self.general_summary = general_summary
self.command_groups = command_groups
self._docs_base_url = docs_base_url
if docs_base_url and docs_base_url.endswith("/"):
self._docs_base_url = docs_base_url[:-1]

def get_usage_message(self, error_message: str, command: str = "") -> str:
"""Build a usage and error message.
Expand Down Expand Up @@ -156,7 +170,7 @@ def get_full_help(self, global_options: list[tuple[str, str]]) -> str:
- summary
- common commands listed and described shortly
- all commands grouped, just listed
- more help
- more help and documentation
"""
textblocks = []

Expand Down Expand Up @@ -203,14 +217,18 @@ def get_full_help(self, global_options: list[tuple[str, str]]) -> str:
)
textblocks.append("\n".join(grouped_lines))

textblocks.append(
textwrap.dedent(
f"""
more_help_text = textwrap.dedent(
f"""
For more information about a command, run '{self.appname} help <command>'.
For a summary of all commands, run '{self.appname} help --all'.
"""
)
For a summary of all commands, run '{self.appname} help --all'."""
)
# append documentation links to block for more help
if self._docs_base_url:
more_help_text += (
f"\nFor more information about {self.appname}, "
f"check out: {self._docs_base_url}"
)
textblocks.append(more_help_text)

# join all stripped blocks, leaving ONE empty blank line between
return "\n\n".join(block.strip() for block in textblocks) + "\n"
Expand All @@ -227,7 +245,7 @@ def get_detailed_help(self, global_options: list[tuple[str, str]]) -> str:
- summary
- global options
- all commands shown with description, grouped
- more help
- more help and documentation
"""
textblocks = []

Expand Down Expand Up @@ -261,21 +279,25 @@ def get_detailed_help(self, global_options: list[tuple[str, str]]) -> str:
group_lines.extend(_build_item_plain(cmd.name, cmd.help_msg, max_title_len))
textblocks.append("\n".join(group_lines))

textblocks.append(
textwrap.dedent(
f"""
For more information about a specific command, run '{self.appname} help <command>'.
"""
)
more_help_text = (
f"For more information about a specific command, run '{self.appname} "
"help <command>'."
""
)
if self._docs_base_url:
more_help_text += (
f"\nFor more information about {self.appname}, "
f"check out: {self._docs_base_url}"
)
textblocks.append(more_help_text)

# join all stripped blocks, leaving ONE empty blank line between
return "\n\n".join(block.strip() for block in textblocks) + "\n"

def _build_plain_command_help(
self,
command: BaseCommand,
usage: str,
overview: str,
parameters: list[tuple[str, str]],
options: list[tuple[str, str]],
other_command_names: list[str],
Expand All @@ -289,7 +311,7 @@ def _build_plain_command_help(
- positional arguments (only if parameters are not empty)
- options
- other related commands
- footer
- help for all commands and documentation
"""
textblocks = []

Expand All @@ -302,7 +324,7 @@ def _build_plain_command_help(
)
)

overview = textwrap.indent(overview, " ")
overview = textwrap.indent(command.overview, " ")
textblocks.append(f"Summary:{overview}")

# column alignment is dictated by longest options title
Expand All @@ -326,19 +348,19 @@ def _build_plain_command_help(
see_also_block.extend((" " + name) for name in sorted(other_command_names))
textblocks.append("\n".join(see_also_block))

# footer
textblocks.append(
f"""
For a summary of all commands, run '{self.appname} help --all'.
"""
)
# help for all commands
more_help_text = f"For a summary of all commands, run '{self.appname} help --all'."
if self._docs_base_url:
command_url = f"{self._docs_base_url}/reference/commands/{command.name}"
more_help_text += f"\nFor more information, check out: {command_url}"
textblocks.append(more_help_text)

return textblocks

def _build_markdown_command_help(
self,
command: BaseCommand,
usage: str,
overview: str,
parameters: list[tuple[str, str]],
options: list[tuple[str, str]],
other_command_names: list[str],
Expand All @@ -352,7 +374,6 @@ def _build_markdown_command_help(
- positional arguments (only if parameters are not empty)
- options
- other related commands
- footer
"""
textblocks = []

Expand All @@ -367,7 +388,7 @@ def _build_markdown_command_help(
)
)

overview = process_overview_for_markdown(overview)
overview = process_overview_for_markdown(command.overview)
textblocks.append(f"## Summary:\n\n{overview}")

if parameters:
Expand Down Expand Up @@ -444,7 +465,7 @@ def get_command_help(
builder = self._build_markdown_command_help
else:
builder = self._build_plain_command_help
textblocks = builder(usage, command.overview, parameters, options, other_command_names)
textblocks = builder(command, usage, parameters, options, other_command_names)

# join all stripped blocks, leaving ONE empty blank line between
return "\n\n".join(block.strip() for block in textblocks) + "\n"
3 changes: 3 additions & 0 deletions craft_cli/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,9 @@ def get_mode(self) -> EmitterMode:
@_active_guard()
def set_mode(self, mode: EmitterMode) -> None:
"""Set the mode of the emitter."""
if mode == self._mode:
return

self._mode = mode
self._log_handler.mode = mode

Expand Down
25 changes: 25 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
:tocdepth: 2

***************
Changelog
***************

See the `Releases page`_ on GitHub for a complete list of commits that are
included in each version.

2.10.1 (2024-Nov-11)
--------------------

- Fix an issue where setting an ``Emitter`` to the same mode multiple times
resulted in multiple greetings.
- Hidden commands can no longer show up as suggested alternatives when an
invalid command is entered by the user.

2.10.0 (2024-Oct-31)
--------------------
- Support adding a link to documentation in help messages.

2.9.0 (2024-Oct-22)
-------------------

- The ``Dispatcher.pre_parse_args()`` method now accepts an ``app_config``
parameter, which is used to instantiate the command that will be validated.

2.8.0 (2024-Oct-10)
-------------------
- Positional arguments are now displayed in 'help' outputs.
- The terminal cursor is now hidden during execution.

2.7.0 (2024-Sep-05)
-------------------
- Add a new ``CraftCommandError`` class for errors that wrap command output
Expand Down
Loading

0 comments on commit 97a709c

Please sign in to comment.