Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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] <command> ...

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
<command>
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
Expand Down
20 changes: 13 additions & 7 deletions cyclonedx_py/_internal/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -62,7 +63,9 @@ def make_argument_parser(cls, sco: ArgumentParser, **kwargs: Any) -> ArgumentPar
sp = p.add_subparsers(metavar='<command>', 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'
Expand Down Expand Up @@ -124,21 +127,24 @@ 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,
parents=[spp, op, sco],
formatter_class=p.formatter_class,
allow_abbrev=p.allow_abbrev,
).set_defaults(_bbc=scbbc)
# endregion SubCommand

return p

Expand Down
2 changes: 1 addition & 1 deletion cyclonedx_py/_internal/poetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
# > `<vcs_tool>+<transport>://<host_name>[/<path_to_repository>][@<revision_tag_or_branch>][#<sub_path>]`
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
Expand Down
20 changes: 12 additions & 8 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ The full documentation can be issued by running with ``--help``:

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


Example usage: save SBOM in CycloneDX 1.6 XML format, generated from current python environment

Expand All @@ -41,10 +43,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``:

Expand Down
10 changes: 7 additions & 3 deletions tests/integration/test_cli_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down