Skip to content

Commit b5ba1d6

Browse files
committed
CM-46563 - Migrate to rich Console, add help rich panels, add syntax highlighting for light themes, fix progress bar
1 parent ba16609 commit b5ba1d6

26 files changed

+250
-137
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/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/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/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:

cycode/cli/apps/scan/repository/repository_command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,6 @@ def repository_command(
6363
perform_pre_scan_documents_actions(ctx, scan_type, documents_to_scan)
6464

6565
logger.debug('Found all relevant files for scanning %s', {'path': path, 'branch': branch})
66-
scan_documents(ctx, documents_to_scan, get_scan_parameters(ctx, (path,)))
66+
scan_documents(ctx, documents_to_scan, get_scan_parameters(ctx, (str(path),)))
6767
except Exception as e:
6868
handle_scan_exception(ctx, e)

cycode/cli/apps/scan/scan_ci/ci_integrations.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import click
44

5+
from cycode.cli.console import console
6+
57

68
def github_action_range() -> str:
79
before_sha = os.getenv('BEFORE_SHA')
@@ -11,7 +13,7 @@ def github_action_range() -> str:
1113
head_sha = os.getenv('GITHUB_SHA')
1214
ref = os.getenv('GITHUB_REF')
1315

14-
click.echo(f'{before_sha}, {push_base_sha}, {pr_base_sha}, {default_branch}, {head_sha}, {ref}')
16+
console.print(f'{before_sha}, {push_base_sha}, {pr_base_sha}, {default_branch}, {head_sha}, {ref}')
1517
if before_sha and before_sha != NO_COMMITS:
1618
return f'{before_sha}...'
1719

@@ -26,7 +28,7 @@ def circleci_range() -> str:
2628
before_sha = os.getenv('BEFORE_SHA')
2729
current_sha = os.getenv('CURRENT_SHA')
2830
commit_range = f'{before_sha}...{current_sha}'
29-
click.echo(f'commit range: {commit_range}')
31+
console.print(f'commit range: {commit_range}')
3032

3133
if not commit_range.startswith('...'):
3234
return commit_range

cycode/cli/apps/scan/scan_command.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
from cycode.cli.utils.get_api_client import get_scan_cycode_client
1313
from cycode.cli.utils.sentry import add_breadcrumb
1414

15+
_AUTH_RICH_HELP_PANEL = 'Authentication options'
16+
_SCA_RICH_HELP_PANEL = 'SCA options'
17+
1518

1619
def scan_command(
1720
ctx: typer.Context,
@@ -28,14 +31,14 @@ def scan_command(
2831
Optional[str],
2932
typer.Option(
3033
help='Specify a Cycode client secret for this specific scan execution.',
31-
rich_help_panel='Authentication options',
34+
rich_help_panel=_AUTH_RICH_HELP_PANEL,
3235
),
3336
] = None,
3437
client_id: Annotated[
3538
Optional[str],
3639
typer.Option(
3740
help='Specify a Cycode client ID for this specific scan execution.',
38-
rich_help_panel='Authentication options',
41+
rich_help_panel=_AUTH_RICH_HELP_PANEL,
3942
),
4043
] = None,
4144
show_secret: Annotated[bool, typer.Option('--show-secret', help='Show Secrets in plain text.')] = False,
@@ -65,15 +68,15 @@ def scan_command(
6568
List[ScaScanTypeOption],
6669
typer.Option(
6770
help='Specify the type of SCA scan you wish to execute.',
68-
rich_help_panel='SCA options',
71+
rich_help_panel=_SCA_RICH_HELP_PANEL,
6972
),
7073
] = (ScaScanTypeOption.PACKAGE_VULNERABILITIES, ScaScanTypeOption.LICENSE_COMPLIANCE),
7174
monitor: Annotated[
7275
bool,
7376
typer.Option(
7477
'--monitor',
7578
help='When specified, the scan results are recorded in the Discovery module.',
76-
rich_help_panel='SCA options',
79+
rich_help_panel=_SCA_RICH_HELP_PANEL,
7780
),
7881
] = False,
7982
no_restore: Annotated[
@@ -82,7 +85,7 @@ def scan_command(
8285
'--no-restore',
8386
help='When specified, Cycode will not run restore command. '
8487
'Will scan direct dependencies [bold]only[/bold]!',
85-
rich_help_panel='SCA options',
88+
rich_help_panel=_SCA_RICH_HELP_PANEL,
8689
),
8790
] = False,
8891
gradle_all_sub_projects: Annotated[
@@ -91,7 +94,7 @@ def scan_command(
9194
'--gradle-all-sub-projects',
9295
help='When specified, Cycode will run gradle restore command for all sub projects. '
9396
'Should run from root project directory [bold]only[/bold]!',
94-
rich_help_panel='SCA options',
97+
rich_help_panel=_SCA_RICH_HELP_PANEL,
9598
),
9699
] = False,
97100
) -> None:

0 commit comments

Comments
 (0)