@@ -49,7 +49,7 @@ def start_run():
4949
5050 logging .info ("run started at %s" , _config .transient .starttime_iso )
5151 # print("ASSIGN UUID", args)
52- if _config .system .lite and "probes" not in _config .transient .cli_args and not _config .transient .cli_args .list_probes and not _config .transient .cli_args .list_detectors and not _config .transient .cli_args .list_generators and not _config .transient .cli_args .list_buffs and not _config .transient .cli_args .list_config and not _config .transient .cli_args .plugin_info and not _config .run .interactive : # type: ignore
52+ if _config .system .lite and "probes" not in _config .transient .cli_args and _config .transient .cli_args .list_probes is None and not _config .transient .cli_args .list_detectors and not _config .transient .cli_args .list_generators and not _config .transient .cli_args .list_buffs and not _config .transient .cli_args .list_config and not _config .transient .cli_args .plugin_info and not _config .run .interactive : # type: ignore
5353 hint (
5454 "The current/default config is optimised for speed rather than thoroughness. Try e.g. --config full for a stronger test, or specify some probes." ,
5555 logging = logging ,
@@ -159,17 +159,49 @@ def end_run():
159159 logging .info (msg )
160160
161161
162- def print_plugins (prefix : str , color , selected_plugins = None ):
162+ def _tier_name (tier_value ):
163+ """Convert a tier int value to its enum name string."""
164+ try :
165+ from garak .probes ._tier import Tier
166+ return Tier (int (tier_value )).name
167+ except (ValueError , TypeError ):
168+ return ""
169+
170+
171+ def _truncate (text , max_len = 80 ):
172+ """Truncate text to max_len, appending ellipsis if needed."""
173+ if len (text ) > max_len :
174+ return text [:max_len - 1 ] + "…"
175+ return text
176+
177+
178+ # Column definitions per plugin type for verbose table output.
179+ # Each entry is (column_name, extractor_fn(info_dict) -> str).
180+ # "name" and "active" are always included and handled separately.
181+ _PLUGIN_TABLE_COLUMNS = {
182+ "probes" : [
183+ ("tier" , lambda info : _tier_name (info .get ("tier" )) if info .get ("tier" ) is not None else "" ),
184+ ("description" , lambda info : _truncate (info .get ("description" , "" ))),
185+ ],
186+ # Future plugin types can define their own extra columns here, e.g.:
187+ # "detectors": [
188+ # ("description", lambda info: _truncate(info.get("description", ""))),
189+ # ],
190+ }
191+
192+
193+ def print_plugins (prefix : str , color , selected_plugins = None , verbose : int = 0 ):
163194 """
164195 Print plugins for a category (probes/detectors/generators/buffs).
165196
166197 Args:
167198 prefix: Plugin category (probes/detectors/generators/buffs)
168199 color: Color for output formatting
169200 selected_plugins: Optional list of specific plugins to show. If None, shows all.
201+ verbose: Verbosity level. 0 = plain list, >=1 = markdown table with metadata.
170202 """
171203 from colorama import Style
172- from garak ._plugins import enumerate_plugins , PLUGIN_TYPES
204+ from garak ._plugins import enumerate_plugins , plugin_info as get_plugin_info , PLUGIN_TYPES
173205
174206 if prefix not in PLUGIN_TYPES :
175207 raise ValueError (f"Requested prefix '{ prefix } ' is not a valid plugin type" )
@@ -184,26 +216,75 @@ def print_plugins(prefix: str, color, selected_plugins=None):
184216 else :
185217 print (f"No { prefix } match the provided filter" )
186218 return
187- short = [(p .replace (f"{ prefix } ." , "" ), a ) for p , a in rows ]
219+
220+ short = [(p .replace (f"{ prefix } ." , "" ), a , p ) for p , a , * _ in [(pn , ac , pn ) for pn , ac in rows ]]
188221 if selected_plugins is None :
189- module_names = set ([( m .split ("." )[0 ], True ) for m , a in short ])
222+ module_names = {( m .split ("." )[0 ], True , None ) for m , a , _ in short }
190223 short += module_names
191224
192- # print output
193- for plugin_name , active in sorted (short ):
194- print (f"{ Style .BRIGHT } { color } { prefix } : { Style .RESET_ALL } " , end = "" )
195- print (plugin_name , end = "" )
196- if "." not in plugin_name :
197- print (" 🌟" , end = "" )
198- if not active :
199- print (" 💤" , end = "" )
200- print ()
225+ sorted_items = sorted (short , key = lambda x : x [0 ])
201226
227+ if verbose >= 1 and prefix in _PLUGIN_TABLE_COLUMNS :
228+ _print_plugins_table (sorted_items , prefix )
229+ else :
230+ # plain text output (default)
231+ for item in sorted_items :
232+ plugin_name , active = item [0 ], item [1 ]
233+ print (f"{ Style .BRIGHT } { color } { prefix } : { Style .RESET_ALL } " , end = "" )
234+ print (plugin_name , end = "" )
235+ if "." not in plugin_name :
236+ print (" 🌟" , end = "" )
237+ if not active :
238+ print (" 💤" , end = "" )
239+ print ()
240+
241+
242+ def _print_plugins_table (sorted_items , prefix ):
243+ """Render plugins as a markdown table with name, active, and type-specific columns."""
244+ from py_markdown_table .markdown_table import markdown_table
245+ from garak ._plugins import plugin_info as get_plugin_info
246+
247+ extra_columns = _PLUGIN_TABLE_COLUMNS .get (prefix , [])
248+
249+ table_data = []
250+ for item in sorted_items :
251+ plugin_name , active = item [0 ], item [1 ]
252+ full_name = item [2 ] if len (item ) > 2 else None
253+
254+ is_module_header = "." not in plugin_name
255+
256+ row = {"name" : plugin_name }
257+
258+ if is_module_header :
259+ row ["active" ] = "🌟"
260+ for col_name , _ in extra_columns :
261+ row [col_name ] = ""
262+ else :
263+ row ["active" ] = "✅" if active else "💤"
264+ info = get_plugin_info (full_name ) if full_name else {}
265+ for col_name , extractor in extra_columns :
266+ row [col_name ] = extractor (info )
267+
268+ table_data .append (row )
269+
270+ print (f"{ prefix } :" )
271+ print (
272+ markdown_table (table_data )
273+ .set_params (row_sep = "markdown" , padding_width = 1 , padding_weight = "centerleft" , quote = False )
274+ .get_markdown ()
275+ )
202276
203- def print_probes (selected_probes = None ):
277+
278+ def print_probes (selected_probes = None , verbose = 0 ):
279+ """Print available probes.
280+
281+ Args:
282+ selected_probes: Optional list of specific probes to show.
283+ verbose: Verbosity level. 0 = plain list, >=1 = markdown table.
284+ """
204285 from colorama import Fore
205286
206- print_plugins ("probes" , Fore .LIGHTYELLOW_EX , selected_probes )
287+ print_plugins ("probes" , Fore .LIGHTYELLOW_EX , selected_probes , verbose = verbose )
207288
208289
209290def print_detectors (selected_detectors = None ):
0 commit comments