1
1
import math
2
+ import urllib .parse
2
3
from typing import TYPE_CHECKING , Dict , List , Optional
3
4
4
- import click
5
5
import typer
6
+ from rich .console import Console
7
+ from rich .markup import escape
8
+ from rich .syntax import Syntax
6
9
10
+ from cycode .cli .cli_types import SeverityOption
7
11
from cycode .cli .consts import COMMIT_RANGE_BASED_COMMAND_SCAN_TYPES , SECRET_SCAN_TYPE
8
12
from cycode .cli .models import CliError , CliResult , Detection , Document , DocumentDetections
9
13
from cycode .cli .printers .printer_base import PrinterBase
@@ -25,73 +29,80 @@ def print_result(self, result: CliResult) -> None:
25
29
if not result .success :
26
30
color = self .RED_COLOR_NAME
27
31
28
- click .secho (result .message , fg = color )
32
+ typer .secho (result .message , fg = color )
29
33
30
34
if not result .data :
31
35
return
32
36
33
- click .secho ('\n Additional data:' , fg = color )
37
+ typer .secho ('\n Additional data:' , fg = color )
34
38
for name , value in result .data .items ():
35
- click .secho (f'- { name } : { value } ' , fg = color )
39
+ typer .secho (f'- { name } : { value } ' , fg = color )
36
40
37
41
def print_error (self , error : CliError ) -> None :
38
- click .secho (error .message , fg = self .RED_COLOR_NAME )
42
+ typer .secho (error .message , fg = self .RED_COLOR_NAME )
39
43
40
44
def print_scan_results (
41
45
self , local_scan_results : List ['LocalScanResult' ], errors : Optional [Dict [str , 'CliError' ]] = None
42
46
) -> None :
43
47
if not errors and all (result .issue_detected == 0 for result in local_scan_results ):
44
- click .secho ('Good job! No issues were found!!! 👏👏👏' , fg = self .GREEN_COLOR_NAME )
48
+ typer .secho ('Good job! No issues were found!!! 👏👏👏' , fg = self .GREEN_COLOR_NAME )
45
49
return
46
50
47
51
for local_scan_result in local_scan_results :
48
52
for document_detections in local_scan_result .document_detections :
49
- self ._print_document_detections (document_detections , local_scan_result . scan_id )
53
+ self ._print_document_detections (document_detections )
50
54
51
55
report_urls = [scan_result .report_url for scan_result in local_scan_results if scan_result .report_url ]
52
56
53
57
self ._print_report_urls (report_urls , self .ctx .obj .get ('aggregation_report_url' ))
54
58
if not errors :
55
59
return
56
60
57
- click .secho (
61
+ typer .secho (
58
62
'Unfortunately, Cycode was unable to complete the full scan. '
59
63
'Please note that not all results may be available:' ,
60
64
fg = 'red' ,
61
65
)
62
66
for scan_id , error in errors .items ():
63
- click .echo (f'- { scan_id } : ' , nl = False )
67
+ typer .echo (f'- { scan_id } : ' , nl = False )
64
68
self .print_error (error )
65
69
66
- def _print_document_detections (self , document_detections : DocumentDetections , scan_id : str ) -> None :
70
+ def _print_document_detections (self , document_detections : DocumentDetections ) -> None :
67
71
document = document_detections .document
68
72
for detection in document_detections .detections :
69
- self ._print_detection_summary (detection , document .path , scan_id )
73
+ self ._print_detection_summary (detection , document .path )
74
+ self ._print_new_line ()
70
75
self ._print_detection_code_segment (detection , document )
76
+ self ._print_new_line ()
71
77
72
- def _print_detection_summary (self , detection : Detection , document_path : str , scan_id : str ) -> None :
78
+ @staticmethod
79
+ def _print_new_line () -> None :
80
+ typer .echo ()
81
+
82
+ def _print_detection_summary (self , detection : Detection , document_path : str ) -> None :
73
83
detection_name = detection .type if self .scan_type == SECRET_SCAN_TYPE else detection .message
74
- detection_name_styled = click .style (detection_name , fg = 'bright_red' , bold = True )
75
84
76
- detection_sha = detection .detection_details .get ('sha512' )
77
- detection_sha_message = f'\n Secret SHA: { detection_sha } ' if detection_sha else ''
85
+ detection_severity = detection .severity or 'N/A'
86
+ detection_severity_color = SeverityOption .get_member_color (detection_severity )
87
+ detection_severity = f'[{ detection_severity_color } ]{ detection_severity .upper ()} [/{ detection_severity_color } ]'
88
+
89
+ escaped_document_path = escape (urllib .parse .quote (document_path ))
90
+ clickable_document_path = f'[link file://{ escaped_document_path } ]{ document_path } '
78
91
79
- scan_id_message = f'\n Scan ID: { scan_id } '
80
92
detection_commit_id = detection .detection_details .get ('commit_id' )
81
93
detection_commit_id_message = f'\n Commit SHA: { detection_commit_id } ' if detection_commit_id else ''
82
94
83
95
company_guidelines = detection .detection_details .get ('custom_remediation_guidelines' )
84
96
company_guidelines_message = f'\n Company Guideline: { company_guidelines } ' if company_guidelines else ''
85
97
86
- click .echo (
87
- f'⛔ '
88
- f'Found issue of type: { detection_name_styled } '
89
- f'(rule ID: { detection .detection_rule_id } ) in file: { click .format_filename (document_path )} '
90
- f'{ detection_sha_message } '
91
- f'{ scan_id_message } '
98
+ Console ().print (
99
+ f':no_entry: '
100
+ f'Found { detection_severity } issue of type: [bright_red][bold]{ detection_name } [/bold][/bright_red] '
101
+ f'in file: { clickable_document_path } '
92
102
f'{ detection_commit_id_message } '
93
103
f'{ company_guidelines_message } '
94
- f' ⛔'
104
+ f' :no_entry:' ,
105
+ highlight = True ,
95
106
)
96
107
97
108
def _print_detection_code_segment (
@@ -109,145 +120,86 @@ def _print_report_urls(report_urls: List[str], aggregation_report_url: Optional[
109
120
if not report_urls and not aggregation_report_url :
110
121
return
111
122
if aggregation_report_url :
112
- click .echo (f'Report URL: { aggregation_report_url } ' )
123
+ typer .echo (f'Report URL: { aggregation_report_url } ' )
113
124
return
114
125
115
- click .echo ('Report URLs:' )
126
+ typer .echo ('Report URLs:' )
116
127
for report_url in report_urls :
117
- click .echo (f'- { report_url } ' )
128
+ typer .echo (f'- { report_url } ' )
118
129
119
130
@staticmethod
120
131
def _get_code_segment_start_line (detection_line : int , lines_to_display : int ) -> int :
121
132
start_line = detection_line - math .ceil (lines_to_display / 2 )
122
133
return 0 if start_line < 0 else start_line
123
134
124
- def _print_line_of_code_segment (
125
- self ,
126
- document : Document ,
127
- line : str ,
128
- line_number : int ,
129
- detection_position_in_line : int ,
130
- violation_length : int ,
131
- is_detection_line : bool ,
132
- ) -> None :
133
- if is_detection_line :
134
- self ._print_detection_line (document , line , line_number , detection_position_in_line , violation_length )
135
- else :
136
- self ._print_line (document , line , line_number )
137
-
138
- def _print_detection_line (
139
- self , document : Document , line : str , line_number : int , detection_position_in_line : int , violation_length : int
140
- ) -> None :
141
- detection_line = self ._get_detection_line_style (
142
- line , document .is_git_diff_format , detection_position_in_line , violation_length
143
- )
144
-
145
- click .echo (f'{ self ._get_line_number_style (line_number )} { detection_line } ' )
146
-
147
- def _print_line (self , document : Document , line : str , line_number : int ) -> None :
148
- line_no = self ._get_line_number_style (line_number )
149
- line = self ._get_line_style (line , document .is_git_diff_format )
150
-
151
- click .echo (f'{ line_no } { line } ' )
152
-
153
- def _get_detection_line_style (self , line : str , is_git_diff : bool , start_position : int , length : int ) -> str :
154
- line_color = self ._get_line_color (line , is_git_diff )
155
- if self .scan_type != SECRET_SCAN_TYPE or start_position < 0 or length < 0 :
156
- return self ._get_line_style (line , is_git_diff , line_color )
157
-
158
- violation = line [start_position : start_position + length ]
159
- if not self .show_secret :
160
- violation = obfuscate_text (violation )
161
-
162
- line_to_violation = line [0 :start_position ]
163
- line_from_violation = line [start_position + length :]
164
-
165
- return (
166
- f'{ self ._get_line_style (line_to_violation , is_git_diff , line_color )} '
167
- f'{ self ._get_line_style (violation , is_git_diff , line_color , underline = True )} '
168
- f'{ self ._get_line_style (line_from_violation , is_git_diff , line_color )} '
169
- )
170
-
171
- def _get_line_style (
172
- self , line : str , is_git_diff : bool , color : Optional [str ] = None , underline : bool = False
173
- ) -> str :
174
- if color is None :
175
- color = self ._get_line_color (line , is_git_diff )
176
-
177
- return click .style (line , fg = color , bold = False , underline = underline )
178
-
179
- def _get_line_color (self , line : str , is_git_diff : bool ) -> str :
180
- if not is_git_diff :
181
- return self .WHITE_COLOR_NAME
182
-
183
- if line .startswith ('+' ):
184
- return self .GREEN_COLOR_NAME
185
-
186
- if line .startswith ('-' ):
187
- return self .RED_COLOR_NAME
188
-
189
- return self .WHITE_COLOR_NAME
190
-
191
- def _get_line_number_style (self , line_number : int ) -> str :
135
+ def _get_detection_line (self , detection : Detection ) -> int :
192
136
return (
193
- f'{ click .style (str (line_number ), fg = self .WHITE_COLOR_NAME , bold = False )} '
194
- f'{ click .style ("|" , fg = self .RED_COLOR_NAME , bold = False )} '
137
+ detection .detection_details .get ('line' , - 1 )
138
+ if self .scan_type == SECRET_SCAN_TYPE
139
+ else detection .detection_details .get ('line_in_file' , - 1 ) - 1
195
140
)
196
141
197
142
def _print_detection_from_file (self , detection : Detection , document : Document , lines_to_display : int ) -> None :
198
143
detection_details = detection .detection_details
199
- detection_line = (
200
- detection_details .get ('line' , - 1 )
201
- if self .scan_type == SECRET_SCAN_TYPE
202
- else detection_details .get ('line_in_file' , - 1 )
203
- )
204
- detection_position = detection_details .get ('start_position' , - 1 )
144
+ detection_line = self ._get_detection_line (detection )
145
+ start_line_index = self ._get_code_segment_start_line (detection_line , lines_to_display )
146
+ detection_position = get_position_in_line (document .content , detection_details .get ('start_position' , - 1 ))
205
147
violation_length = detection_details .get ('length' , - 1 )
206
148
207
- file_content = document .content
208
- file_lines = file_content .splitlines ()
209
- start_line = self ._get_code_segment_start_line (detection_line , lines_to_display )
210
- detection_position_in_line = get_position_in_line (file_content , detection_position )
211
-
212
- click .echo ()
149
+ code_lines_to_render = []
150
+ document_content_lines = document .content .splitlines ()
213
151
for line_index in range (lines_to_display ):
214
- current_line_index = start_line + line_index
215
- if current_line_index >= len (file_lines ):
152
+ current_line_index = start_line_index + line_index
153
+ if current_line_index >= len (document_content_lines ):
216
154
break
217
155
218
- current_line = file_lines [current_line_index ]
219
- is_detection_line = current_line_index == detection_line
220
- self ._print_line_of_code_segment (
221
- document ,
222
- current_line ,
223
- current_line_index + 1 ,
224
- detection_position_in_line ,
225
- violation_length ,
226
- is_detection_line ,
156
+ line_content = document_content_lines [current_line_index ]
157
+
158
+ line_with_detection = current_line_index == detection_line
159
+ if self .scan_type == SECRET_SCAN_TYPE and line_with_detection and not self .show_secret :
160
+ violation = line_content [detection_position : detection_position + violation_length ]
161
+ code_lines_to_render .append (line_content .replace (violation , obfuscate_text (violation )))
162
+ else :
163
+ code_lines_to_render .append (line_content )
164
+
165
+ code_to_render = '\n ' .join (code_lines_to_render )
166
+ Console ().print (
167
+ Syntax (
168
+ code = code_to_render ,
169
+ lexer = Syntax .guess_lexer (document .path , code = code_to_render ),
170
+ line_numbers = True ,
171
+ dedent = True ,
172
+ tab_size = 2 ,
173
+ start_line = start_line_index + 1 ,
174
+ highlight_lines = {
175
+ detection_line + 1 ,
176
+ },
227
177
)
228
- click . echo ( )
178
+ )
229
179
230
180
def _print_detection_from_git_diff (self , detection : Detection , document : Document ) -> None :
231
181
detection_details = detection .detection_details
232
- detection_line_number = detection_details .get ('line' , - 1 )
233
- detection_line_number_in_original_file = detection_details .get ('line_in_file' , - 1 )
182
+ detection_line = self ._get_detection_line (detection )
234
183
detection_position = detection_details .get ('start_position' , - 1 )
235
184
violation_length = detection_details .get ('length' , - 1 )
236
185
237
- git_diff_content = document .content
238
- git_diff_lines = git_diff_content .splitlines ()
239
- detection_line = git_diff_lines [detection_line_number ]
240
- detection_position_in_line = get_position_in_line (git_diff_content , detection_position )
241
-
242
- click .echo ()
243
- self ._print_detection_line (
244
- document ,
245
- detection_line ,
246
- detection_line_number_in_original_file ,
247
- detection_position_in_line ,
248
- violation_length ,
186
+ line_content = document .content .splitlines ()[detection_line ]
187
+ detection_position_in_line = get_position_in_line (document .content , detection_position )
188
+ if self .scan_type == SECRET_SCAN_TYPE and not self .show_secret :
189
+ violation = line_content [detection_position_in_line : detection_position_in_line + violation_length ]
190
+ line_content = line_content .replace (violation , obfuscate_text (violation ))
191
+
192
+ Console ().print (
193
+ Syntax (
194
+ line_content ,
195
+ lexer = 'diff' ,
196
+ line_numbers = True ,
197
+ start_line = detection_line ,
198
+ dedent = True ,
199
+ tab_size = 2 ,
200
+ highlight_lines = {detection_line + 1 },
201
+ )
249
202
)
250
- click .echo ()
251
203
252
204
def _is_git_diff_based_scan (self ) -> bool :
253
205
return self .command_scan_type in COMMIT_RANGE_BASED_COMMAND_SCAN_TYPES and self .scan_type == SECRET_SCAN_TYPE
0 commit comments