@@ -109,6 +109,40 @@ def addContentToDictMappings(self, content: SecurityContentObject):
109
109
self .uuid_to_content_map [content .id ] = content
110
110
111
111
112
+ class Colors :
113
+ HEADER = "\033 [95m"
114
+ BLUE = "\033 [94m"
115
+ CYAN = "\033 [96m"
116
+ GREEN = "\033 [92m"
117
+ YELLOW = "\033 [93m"
118
+ RED = "\033 [91m"
119
+ BOLD = "\033 [1m"
120
+ UNDERLINE = "\033 [4m"
121
+ END = "\033 [0m"
122
+ MAGENTA = "\033 [35m"
123
+ BRIGHT_MAGENTA = "\033 [95m"
124
+
125
+ # Add fallback symbols for Windows
126
+ CHECK_MARK = "✓" if sys .platform != "win32" else "*"
127
+ WARNING = "⚠️" if sys .platform != "win32" else "!"
128
+ ERROR = "❌" if sys .platform != "win32" else "X"
129
+ ARROW = "🎯" if sys .platform != "win32" else ">"
130
+ TOOLS = "🛠️" if sys .platform != "win32" else "#"
131
+ DOCS = "📚" if sys .platform != "win32" else "?"
132
+ BULB = "💡" if sys .platform != "win32" else "i"
133
+ SEARCH = "🔍" if sys .platform != "win32" else "@"
134
+ SPARKLE = "✨" if sys .platform != "win32" else "*"
135
+ ZAP = "⚡" if sys .platform != "win32" else "!"
136
+
137
+
138
+ class ValidationFailedError (Exception ):
139
+ """Custom exception for validation failures that already have formatted output."""
140
+
141
+ def __init__ (self , message : str ):
142
+ self .message = message
143
+ super ().__init__ (message )
144
+
145
+
112
146
class Director :
113
147
input_dto : validate
114
148
output_dto : DirectorOutputDto
@@ -268,18 +302,101 @@ def createSecurityContent(
268
302
end = "" ,
269
303
flush = True ,
270
304
)
271
- print ("Done!" )
272
305
273
306
if len (validation_errors ) > 0 :
274
- errors_string = "\n \n " .join (
275
- [
276
- f"File: { e_tuple [0 ]} \n Error: { str (e_tuple [1 ])} "
277
- for e_tuple in validation_errors
278
- ]
307
+ if sys .platform == "win32" :
308
+ sys .stdout .reconfigure (encoding = "utf-8" )
309
+
310
+ print ("\n " ) # Clean separation
311
+ print (f"{ Colors .BOLD } { Colors .BRIGHT_MAGENTA } ╔{ '═' * 60 } ╗{ Colors .END } " )
312
+ print (
313
+ f"{ Colors .BOLD } { Colors .BRIGHT_MAGENTA } ║{ Colors .BLUE } { f'{ Colors .SEARCH } Content Validation Summary' :^59} { Colors .BRIGHT_MAGENTA } ║{ Colors .END } "
279
314
)
280
- # print(f"The following {len(validation_errors)} error(s) were found during validation:\n\n{errors_string}\n\nVALIDATION FAILED")
281
- # We quit after validation a single type/group of content because it can cause significant cascading errors in subsequent
282
- # types of content (since they may import or otherwise use it)
283
- raise Exception (
284
- f"The following { len (validation_errors )} error(s) were found during validation:\n \n { errors_string } \n \n VALIDATION FAILED"
315
+ print (f"{ Colors .BOLD } { Colors .BRIGHT_MAGENTA } ╚{ '═' * 60 } ╝{ Colors .END } \n " )
316
+
317
+ print (
318
+ f"{ Colors .BOLD } { Colors .GREEN } { Colors .SPARKLE } Validation Completed{ Colors .END } – Issues detected in { Colors .RED } { Colors .BOLD } { len (validation_errors )} { Colors .END } files.\n "
285
319
)
320
+
321
+ for index , entry in enumerate (validation_errors , 1 ):
322
+ file_path , error = entry
323
+ width = max (70 , len (str (file_path )) + 15 )
324
+
325
+ # File header with numbered emoji
326
+ number_emoji = f"{ index } ️⃣"
327
+ print (f"{ Colors .YELLOW } ┏{ '━' * width } ┓{ Colors .END } " )
328
+ print (
329
+ f"{ Colors .YELLOW } ┃{ Colors .BOLD } { number_emoji } File: { Colors .CYAN } { file_path } { Colors .END } { ' ' * (width - len (str (file_path )) - 9 )} { Colors .YELLOW } ┃{ Colors .END } "
330
+ )
331
+ print (f"{ Colors .YELLOW } ┗{ '━' * width } ┛{ Colors .END } " )
332
+
333
+ print (
334
+ f" { Colors .RED } { Colors .BOLD } { Colors .ZAP } Validation Issues:{ Colors .END } "
335
+ )
336
+
337
+ if isinstance (error , ValidationError ):
338
+ for err in error .errors ():
339
+ error_msg = err .get ("msg" , "" )
340
+ if "https://errors.pydantic.dev" in error_msg :
341
+ # Unfortunately, this is a catch-all for untyped errors. We will still need to emit this
342
+ # This is harder to read, but the other option is suppressing it which we cannot do as
343
+ # it makes troubleshooting extremelt difficult
344
+ print (
345
+ f" { Colors .RED } { Colors .ERROR } { error_msg } { Colors .END } "
346
+ )
347
+
348
+ # Clean error categorization
349
+ elif "Field required" in error_msg :
350
+ print (
351
+ f" { Colors .YELLOW } { Colors .WARNING } Field Required: { err .get ('loc' , ['' ])[0 ]} { Colors .END } "
352
+ )
353
+ elif "Input should be" in error_msg :
354
+ print (
355
+ f" { Colors .MAGENTA } { Colors .ARROW } Invalid Value for { err .get ('loc' , ['' ])[0 ]} { Colors .END } "
356
+ )
357
+ if err .get ("ctx" , {}).get ("expected" , None ) is not None :
358
+ print (
359
+ f" Valid options: { err .get ('ctx' , {}).get ('expected' , None )} "
360
+ )
361
+ elif "Extra inputs" in error_msg :
362
+ print (
363
+ f" { Colors .BLUE } { Colors .ERROR } Unexpected Field: { err .get ('loc' , ['' ])[0 ]} { Colors .END } "
364
+ )
365
+ elif "Failed to find" in error_msg :
366
+ print (
367
+ f" { Colors .RED } { Colors .SEARCH } Missing Reference: { error_msg } { Colors .END } "
368
+ )
369
+ else :
370
+ print (
371
+ f" { Colors .RED } { Colors .ERROR } { error_msg } { Colors .END } "
372
+ )
373
+ else :
374
+ print (f" { Colors .RED } { Colors .ERROR } { str (error )} { Colors .END } " )
375
+ print ("" )
376
+
377
+ # Clean footer with next steps
378
+ max_width = max (60 , max (len (str (e [0 ])) + 15 for e in validation_errors ))
379
+ print (f"{ Colors .BOLD } { Colors .CYAN } ╔{ '═' * max_width } ╗{ Colors .END } " )
380
+ print (
381
+ f"{ Colors .BOLD } { Colors .CYAN } ║{ Colors .BLUE } { Colors .ARROW + ' Next Steps' :^{max_width - 1 }} { Colors .CYAN } ║{ Colors .END } "
382
+ )
383
+ print (f"{ Colors .BOLD } { Colors .CYAN } ╚{ '═' * max_width } ╝{ Colors .END } \n " )
384
+
385
+ print (
386
+ f"{ Colors .GREEN } { Colors .TOOLS } Fix the validation issues in the listed files{ Colors .END } "
387
+ )
388
+ print (
389
+ f"{ Colors .YELLOW } { Colors .DOCS } Check the documentation: { Colors .UNDERLINE } https://github.com/splunk/contentctl{ Colors .END } "
390
+ )
391
+ print (
392
+ f"{ Colors .BLUE } { Colors .BULB } Use --verbose for detailed error information{ Colors .END } \n "
393
+ )
394
+
395
+ raise ValidationFailedError (
396
+ f"Validation failed with { len (validation_errors )} error(s)"
397
+ )
398
+
399
+ # Success case
400
+ print (
401
+ f"\r { f'{ contentCartegoryName } Progress' .rjust (23 )} : [{ progress_percent :3.0f} %]... { Colors .GREEN } { Colors .CHECK_MARK } Done!{ Colors .END } "
402
+ )
0 commit comments