Skip to content

Commit 355d1c0

Browse files
authored
CM-46563 - Migrate to rich Console, add help rich panels, add syntax highlighting for light themes, fix progress bar, fix traceback printing, fix repository scan (#293)
1 parent ba16609 commit 355d1c0

29 files changed

+264
-147
lines changed

cycode/cli/app.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Annotated, Optional
33

44
import typer
5+
from typer.completion import install_callback, show_callback
56

67
from cycode import __version__
78
from cycode.cli.apps import ai_remediation, auth, configure, ignore, report, scan, status
@@ -20,6 +21,7 @@
2021
pretty_exceptions_short=True,
2122
context_settings=CLI_CONTEXT_SETTINGS,
2223
rich_markup_mode='rich',
24+
add_completion=False, # we add it manually to control the rich help panel
2325
)
2426

2527
app.add_typer(ai_remediation.app)
@@ -39,9 +41,10 @@ def check_latest_version_on_close(ctx: typer.Context) -> None:
3941

4042
# we always want to check the latest version for "version" and "status" commands
4143
should_use_cache = ctx.invoked_subcommand not in {'version', 'status'}
42-
version_checker.check_and_notify_update(
43-
current_version=__version__, use_color=ctx.color, use_cache=should_use_cache
44-
)
44+
version_checker.check_and_notify_update(current_version=__version__, use_cache=should_use_cache)
45+
46+
47+
_COMPLETION_RICH_HELP_PANEL = 'Completion options'
4548

4649

4750
@app.callback()
@@ -61,6 +64,28 @@ def app_callback(
6164
Optional[str],
6265
typer.Option(hidden=True, help='Characteristic JSON object that lets servers identify the application.'),
6366
] = None,
67+
_: Annotated[
68+
Optional[bool],
69+
typer.Option(
70+
'--install-completion',
71+
callback=install_callback,
72+
is_eager=True,
73+
expose_value=False,
74+
help='Install completion for the current shell.',
75+
rich_help_panel=_COMPLETION_RICH_HELP_PANEL,
76+
),
77+
] = False,
78+
__: Annotated[
79+
Optional[bool],
80+
typer.Option(
81+
'--show-completion',
82+
callback=show_callback,
83+
is_eager=True,
84+
expose_value=False,
85+
help='Show completion for the current shell, to copy it or customize the installation.',
86+
rich_help_panel=_COMPLETION_RICH_HELP_PANEL,
87+
),
88+
] = False,
6489
) -> None:
6590
init_sentry()
6691
add_breadcrumb('cycode')

cycode/cli/apps/ai_remediation/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@
33
from cycode.cli.apps.ai_remediation.ai_remediation_command import ai_remediation_command
44

55
app = typer.Typer()
6-
app.command(name='ai_remediation', short_help='Get AI remediation (INTERNAL).', hidden=True)(ai_remediation_command)
6+
app.command(name='ai-remediation', short_help='Get AI remediation (INTERNAL).', hidden=True)(ai_remediation_command)
7+
8+
# backward compatibility
9+
app.command(hidden=True, name='ai_remediation')(ai_remediation_command)

cycode/cli/apps/ai_remediation/print_remediation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import typer
2-
from rich.console import Console
32
from rich.markdown import Markdown
43

4+
from cycode.cli.console import console
55
from cycode.cli.models import CliResult
66
from cycode.cli.printers import ConsolePrinter
77

@@ -12,4 +12,4 @@ def print_remediation(ctx: typer.Context, remediation_markdown: str, is_fix_avai
1212
data = {'remediation': remediation_markdown, 'is_fix_available': is_fix_available}
1313
printer.print_result(CliResult(success=True, message='Remediation fetched successfully', data=data))
1414
else: # text or table
15-
Console().print(Markdown(remediation_markdown))
15+
console.print(Markdown(remediation_markdown))

cycode/cli/apps/configure/configure_command.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
from typing import Optional
22

3-
import typer
4-
53
from cycode.cli.apps.configure.consts import CONFIGURATION_MANAGER, CREDENTIALS_MANAGER
64
from cycode.cli.apps.configure.messages import get_credentials_update_result_message, get_urls_update_result_message
75
from cycode.cli.apps.configure.prompts import (
@@ -10,6 +8,7 @@
108
get_client_id_input,
119
get_client_secret_input,
1210
)
11+
from cycode.cli.console import console
1312
from cycode.cli.utils.sentry import add_breadcrumb
1413

1514

@@ -52,6 +51,6 @@ def configure_command() -> None:
5251
CREDENTIALS_MANAGER.update_credentials(client_id, client_secret)
5352

5453
if config_updated:
55-
typer.echo(get_urls_update_result_message())
54+
console.print(get_urls_update_result_message())
5655
if credentials_updated:
57-
typer.echo(get_credentials_update_result_message())
56+
console.print(get_credentials_update_result_message())

cycode/cli/apps/ignore/ignore_command.py

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,45 +12,62 @@
1212
from cycode.cli.utils.sentry import add_breadcrumb
1313
from cycode.cli.utils.string_utils import hash_string_to_sha256
1414

15+
_FILTER_BY_RICH_HELP_PANEL = 'Filter options'
16+
_SECRETS_FILTER_BY_RICH_HELP_PANEL = 'Secrets filter options'
17+
_SCA_FILTER_BY_RICH_HELP_PANEL = 'SCA filter options'
18+
1519

1620
def _is_package_pattern_valid(package: str) -> bool:
1721
return re.search('^[^@]+@[^@]+$', package) is not None
1822

1923

2024
def ignore_command( # noqa: C901
21-
by_value: Annotated[
22-
Optional[str], typer.Option(help='Ignore a specific value while scanning for Secrets.', show_default=False)
25+
by_path: Annotated[
26+
Optional[str],
27+
typer.Option(
28+
help='Ignore a specific file or directory while scanning.',
29+
show_default=False,
30+
rich_help_panel=_FILTER_BY_RICH_HELP_PANEL,
31+
),
2332
] = None,
24-
by_sha: Annotated[
33+
by_rule: Annotated[
2534
Optional[str],
2635
typer.Option(
27-
help='Ignore a specific SHA512 representation of a string while scanning for Secrets.', show_default=False
36+
help='Ignore scanning a specific Secrets rule ID or IaC rule ID.',
37+
show_default=False,
38+
rich_help_panel=_FILTER_BY_RICH_HELP_PANEL,
2839
),
2940
] = None,
30-
by_path: Annotated[
41+
by_value: Annotated[
3142
Optional[str],
32-
typer.Option(help='Avoid scanning a specific path. You`ll need to specify the scan type.', show_default=False),
43+
typer.Option(
44+
help='Ignore a specific value.',
45+
show_default=False,
46+
rich_help_panel=_SECRETS_FILTER_BY_RICH_HELP_PANEL,
47+
),
3348
] = None,
34-
by_rule: Annotated[
49+
by_sha: Annotated[
3550
Optional[str],
3651
typer.Option(
37-
help='Ignore scanning a specific secret rule ID or IaC rule ID. You`ll to specify the scan type.',
52+
help='Ignore a specific SHA512 representation of a string.',
3853
show_default=False,
54+
rich_help_panel=_SECRETS_FILTER_BY_RICH_HELP_PANEL,
3955
),
4056
] = None,
4157
by_package: Annotated[
4258
Optional[str],
4359
typer.Option(
44-
help='Ignore scanning a specific package version while running an SCA scan. '
45-
'Expected pattern: name@version.',
60+
help='Ignore scanning a specific package version. Expected pattern: [cyan]name@version[/cyan].',
4661
show_default=False,
62+
rich_help_panel=_SCA_FILTER_BY_RICH_HELP_PANEL,
4763
),
4864
] = None,
4965
by_cve: Annotated[
5066
Optional[str],
5167
typer.Option(
52-
help='Ignore scanning a specific CVE while running an SCA scan. Expected pattern: CVE-YYYY-NNN.',
68+
help='Ignore scanning a specific CVE. Expected pattern: [cyan]CVE-YYYY-NNN[/cyan].',
5369
show_default=False,
70+
rich_help_panel=_SCA_FILTER_BY_RICH_HELP_PANEL,
5471
),
5572
] = None,
5673
scan_type: Annotated[

cycode/cli/apps/report/sbom/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
app = typer.Typer(name='sbom')
88
app.callback(short_help='Generate SBOM report for remote repository by url or local directory by path.')(sbom_command)
99
app.command(name='path', short_help='Generate SBOM report for provided path in the command.')(path_command)
10-
app.command(name='repository_url', short_help='Generate SBOM report for provided repository URI in the command.')(
10+
app.command(name='repository-url', short_help='Generate SBOM report for provided repository URI in the command.')(
1111
repository_url_command
1212
)
13+
14+
# backward compatibility
15+
app.command(hidden=True, name='repository_url')(repository_url_command)

cycode/cli/apps/report/sbom/sbom_command.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from cycode.cli.utils.sentry import add_breadcrumb
99
from cycode.cyclient.report_client import ReportParameters
1010

11+
_OUTPUT_RICH_HELP_PANEL = 'Output options'
12+
1113

1214
def sbom_command(
1315
ctx: typer.Context,
@@ -27,6 +29,7 @@ def sbom_command(
2729
'--output-format',
2830
'-o',
2931
help='Specify the output file format.',
32+
rich_help_panel=_OUTPUT_RICH_HELP_PANEL,
3033
),
3134
] = SbomOutputFormatOption.JSON,
3235
output_file: Annotated[
@@ -36,6 +39,7 @@ def sbom_command(
3639
show_default='Autogenerated filename saved to the current directory',
3740
dir_okay=False,
3841
writable=True,
42+
rich_help_panel=_OUTPUT_RICH_HELP_PANEL,
3943
),
4044
] = None,
4145
include_vulnerabilities: Annotated[

cycode/cli/apps/report/sbom/sbom_report_file.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
import re
44
from typing import Optional
55

6-
import click
6+
import typer
7+
8+
from cycode.cli.console import console
79

810

911
class SbomReportFile:
@@ -21,14 +23,14 @@ def is_exists(self) -> bool:
2123
return self._file_path.exists()
2224

2325
def _prompt_overwrite(self) -> bool:
24-
return click.confirm(f'File {self._file_path} already exists. Save with a different filename?', default=True)
26+
return typer.confirm(f'File {self._file_path} already exists. Save with a different filename?', default=True)
2527

2628
def _write(self, content: str) -> None:
2729
with open(self._file_path, 'w', encoding='UTF-8') as f:
2830
f.write(content)
2931

3032
def _notify_about_saved_file(self) -> None:
31-
click.echo(f'Report saved to {self._file_path}')
33+
console.print(f'Report saved to {self._file_path}')
3234

3335
def _find_and_set_unique_filename(self) -> None:
3436
attempt_no = 0

cycode/cli/apps/scan/__init__.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,22 @@
1616

1717
app.command(name='path', short_help='Scan the files in the paths provided in the command.')(path_command)
1818
app.command(name='repository', short_help='Scan the Git repository included files.')(repository_command)
19-
app.command(name='commit_history', short_help='Scan all the commits history in this git repository.')(
19+
app.command(name='commit-history', short_help='Scan all the commits history in this git repository.')(
2020
commit_history_command
2121
)
22-
2322
app.command(
24-
name='pre_commit',
23+
name='pre-commit',
2524
short_help='Use this command in pre-commit hook to scan any content that was not committed yet.',
2625
rich_help_panel='Automation commands',
2726
)(pre_commit_command)
2827
app.command(
29-
name='pre_receive',
28+
name='pre-receive',
3029
short_help='Use this command in pre-receive hook '
3130
'to scan commits on the server side before pushing them to the repository.',
3231
rich_help_panel='Automation commands',
3332
)(pre_receive_command)
33+
34+
# backward compatibility
35+
app.command(hidden=True, name='commit_history')(commit_history_command)
36+
app.command(hidden=True, name='pre_commit')(pre_commit_command)
37+
app.command(hidden=True, name='pre_receive')(pre_receive_command)

cycode/cli/apps/scan/code_scanner.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from cycode.cli import consts
1313
from cycode.cli.cli_types import SeverityOption
1414
from cycode.cli.config import configuration_manager
15+
from cycode.cli.console import console
1516
from cycode.cli.exceptions import custom_exceptions
1617
from cycode.cli.exceptions.handle_scan_errors import handle_scan_exception
1718
from cycode.cli.files_collector.excluder import exclude_irrelevant_documents_to_scan
@@ -83,7 +84,7 @@ def scan_sca_commit_range(ctx: typer.Context, path: str, commit_range: str) -> N
8384
scan_commit_range_documents(ctx, from_commit_documents, to_commit_documents, scan_parameters=scan_parameters)
8485

8586

86-
def scan_disk_files(ctx: click.Context, paths: Tuple[str]) -> None:
87+
def scan_disk_files(ctx: typer.Context, paths: Tuple[str]) -> None:
8788
scan_type = ctx.obj['scan_type']
8889
progress_bar = ctx.obj['progress_bar']
8990

@@ -642,7 +643,7 @@ def parse_pre_receive_input() -> str:
642643
return pre_receive_input.splitlines()[0]
643644

644645

645-
def _get_default_scan_parameters(ctx: click.Context) -> dict:
646+
def _get_default_scan_parameters(ctx: typer.Context) -> dict:
646647
return {
647648
'monitor': ctx.obj.get('monitor'),
648649
'report': ctx.obj.get('report'),
@@ -916,7 +917,7 @@ def _try_get_report_url_if_needed(
916917
logger.debug('Failed to get report URL', exc_info=e)
917918

918919

919-
def _set_aggregation_report_url(ctx: click.Context, aggregation_report_url: Optional[str] = None) -> None:
920+
def _set_aggregation_report_url(ctx: typer.Context, aggregation_report_url: Optional[str] = None) -> None:
920921
ctx.obj['aggregation_report_url'] = aggregation_report_url
921922

922923

@@ -1007,7 +1008,7 @@ def _normalize_file_path(path: str) -> str:
10071008

10081009
def perform_post_pre_receive_scan_actions(ctx: typer.Context) -> None:
10091010
if scan_utils.is_scan_failed(ctx):
1010-
click.echo(consts.PRE_RECEIVE_REMEDIATION_MESSAGE)
1011+
console.print(consts.PRE_RECEIVE_REMEDIATION_MESSAGE)
10111012

10121013

10131014
def enable_verbose_mode(ctx: typer.Context) -> None:

0 commit comments

Comments
 (0)