diff --git a/README.md b/README.md index 94dff011..cf64443e 100644 --- a/README.md +++ b/README.md @@ -71,20 +71,21 @@ python3 -m cyclonedx_py # call python module CLI ```shellSession $ cyclonedx-py --help -usage: cyclonedx-py [-h] [--version] command ... +usage: cyclonedx-py [-h] [--version] ... Creates CycloneDX Software Bill of Materials (SBOM) from Python projects and environments. positional arguments: - command - environment Build an SBOM from Python (virtual) environment - requirements Build an SBOM from Pip requirements - pipenv Build an SBOM from Pipenv manifest - poetry Build an SBOM from Poetry project + + environment (env, venv) + Build an SBOM from Python (virtual) environment + requirements Build an SBOM from Pip requirements + pipenv Build an SBOM from Pipenv manifest + poetry Build an SBOM from Poetry project options: - -h, --help show this help message and exit - --version show program's version number and exit + -h, --help show this help message and exit + --version show program's version number and exit ``` ### Advanced usage and details diff --git a/cyclonedx_py/_internal/cli.py b/cyclonedx_py/_internal/cli.py index b6d1ad7b..15193ec3 100644 --- a/cyclonedx_py/_internal/cli.py +++ b/cyclonedx_py/_internal/cli.py @@ -19,7 +19,7 @@ import sys from argparse import ArgumentParser, FileType, RawDescriptionHelpFormatter from itertools import chain -from typing import TYPE_CHECKING, Any, Dict, NoReturn, Optional, Sequence, TextIO, Type, Union +from typing import TYPE_CHECKING, Any, Dict, List, NoReturn, Optional, Sequence, TextIO, Type, Union from cyclonedx.model import Property from cyclonedx.output import make_outputter @@ -53,6 +53,7 @@ class Command: @classmethod def make_argument_parser(cls, sco: ArgumentParser, **kwargs: Any) -> ArgumentParser: + # region Command p = ArgumentParser( description='Creates CycloneDX Software Bill of Materials (SBOM) from Python projects and environments.', formatter_class=RawDescriptionHelpFormatter, @@ -62,7 +63,9 @@ def make_argument_parser(cls, sco: ArgumentParser, **kwargs: Any) -> ArgumentPar sp = p.add_subparsers(metavar='', dest='command', # not required. if omitted: show help and exit required=False) + # region Command + # region SubCOmmand op = ArgumentParser(add_help=False) op.add_argument('--short-PURLs', help='Omit all qualifiers from PackageURLs.\n' @@ -124,14 +127,16 @@ def make_argument_parser(cls, sco: ArgumentParser, **kwargs: Any) -> ArgumentPar action='store_false') scbbc: Type['BomBuilder'] - for sct, scbbc in ( - ('environment', EnvironmentBB), - ('requirements', RequirementsBB), - ('pipenv', PipenvBB), - ('poetry', PoetryBB), + sct: str + scta: List[str] + for scbbc, sct, *scta in ( + (EnvironmentBB, 'environment', 'env', 'venv'), + (RequirementsBB, 'requirements'), + (PipenvBB, 'pipenv'), + (PoetryBB, 'poetry'), ): spp = scbbc.make_argument_parser(add_help=False) - sp.add_parser(sct, + sp.add_parser(sct, aliases=scta, help=(spp.description or '').split('\n')[0].strip('. '), description=spp.description, epilog=spp.epilog, @@ -139,6 +144,7 @@ def make_argument_parser(cls, sco: ArgumentParser, **kwargs: Any) -> ArgumentPar formatter_class=p.formatter_class, allow_abbrev=p.allow_abbrev, ).set_defaults(_bbc=scbbc) + # endregion SubCommand return p diff --git a/cyclonedx_py/_internal/poetry.py b/cyclonedx_py/_internal/poetry.py index ce52d34e..1587c0d0 100644 --- a/cyclonedx_py/_internal/poetry.py +++ b/cyclonedx_py/_internal/poetry.py @@ -444,7 +444,7 @@ def __purl_qualifiers4lock(self, package: 'T_NameDict') -> 'T_NameDict': # > For version-controlled files, the VCS location syntax is similar to a URL and has the: # > `+://[/][@][#]` qs['vcs_url'] = f'{source_type}+{redact_auth_from_url(source["url"])}@' + \ - source.get('resolved_reference', source.get('reference', '')) + source.get('resolved_reference', source.get('reference', '')) elif source_type == 'url': if '://files.pythonhosted.org/' not in source['url']: # skip PURL bloat, do not add implicit information diff --git a/docs/usage.rst b/docs/usage.rst index 3c2ece95..068f47c3 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -41,10 +41,12 @@ For Python (virtual) environment **subcommand:** ``environment`` +**aliases:** ``env``, ``venv`` + .. TODO: describe what an environment is... -This will produce the most accurate and complete CycloneDX BOM as it analyses the actually installed packages. -It will include metadata, licenses, dependency graph, and more in the generated CycloneDX SBOM. +By analyzing the actually installed packages, this will produce the most accurate and complete CycloneDX BOM. +The generated CycloneDX SBOM will include metadata, licenses, dependency graph, and more. The full documentation can be issued by running with ``environment --help``: diff --git a/tests/integration/test_cli_environment.py b/tests/integration/test_cli_environment.py index 6aa5285d..2f3af3bd 100644 --- a/tests/integration/test_cli_environment.py +++ b/tests/integration/test_cli_environment.py @@ -25,7 +25,7 @@ from unittest import TestCase, skipIf from cyclonedx.schema import OutputFormat, SchemaVersion -from ddt import ddt, named_data +from ddt import data, ddt, named_data from tests import INFILES_DIRECTORY, INIT_TESTBEDS, SUPPORTED_OF_SV, SnapshotMixin, make_comparable from tests.integration import run_cli @@ -45,8 +45,12 @@ def test_data_file_filter(s: str) -> Generator[Any, None, None]: @ddt class TestCliEnvironment(TestCase, SnapshotMixin): - def test_help(self) -> None: - res, out, err = run_cli('environment', '--help') + @data( + 'environment', + 'env', 'venv' # aliases + ) + def test_help(self, subcommand: str) -> None: + res, out, err = run_cli(subcommand, '--help') self.assertEqual(0, res, '\n'.join((out, err))) @classmethod