|
46 | 46 | """ |
47 | 47 |
|
48 | 48 |
|
| 49 | +def _get_tag_plain_text(tag_obj): |
| 50 | + """Extract plain text from a tag object (typically ColorizedString). |
| 51 | +
|
| 52 | + ColorizedString objects store plain text in _message and add ANSI codes via __str__. |
| 53 | + For caching, we need plain text only. This function safely extracts it. |
| 54 | +
|
| 55 | + :param tag_obj: Tag object (ColorizedString or other) |
| 56 | + :return: Plain text string without ANSI codes |
| 57 | + """ |
| 58 | + # ColorizedString stores plain text in _message attribute |
| 59 | + if hasattr(tag_obj, '_message'): |
| 60 | + return tag_obj._message # pylint: disable=protected-access |
| 61 | + # Fallback for non-ColorizedString objects |
| 62 | + return str(tag_obj) |
| 63 | + |
| 64 | + |
| 65 | +def get_help_item_tags(item): |
| 66 | + """Extract status tags from a help item (group or command). |
| 67 | +
|
| 68 | + Returns a space-separated string of plain text tags like '[Deprecated] [Preview]'. |
| 69 | + """ |
| 70 | + tags = [] |
| 71 | + if hasattr(item, 'deprecate_info') and item.deprecate_info: |
| 72 | + tag_obj = item.deprecate_info.tag |
| 73 | + tags.append(_get_tag_plain_text(tag_obj)) |
| 74 | + if hasattr(item, 'preview_info') and item.preview_info: |
| 75 | + tag_obj = item.preview_info.tag |
| 76 | + tags.append(_get_tag_plain_text(tag_obj)) |
| 77 | + if hasattr(item, 'experimental_info') and item.experimental_info: |
| 78 | + tag_obj = item.experimental_info.tag |
| 79 | + tags.append(_get_tag_plain_text(tag_obj)) |
| 80 | + return ' '.join(tags) |
| 81 | + |
| 82 | + |
| 83 | +def extract_help_index_data(help_file): |
| 84 | + """Extract groups and commands from help file children for caching. |
| 85 | +
|
| 86 | + Processes help file children and builds dictionaries of groups and commands |
| 87 | + with their summaries and tags for top-level help display. |
| 88 | +
|
| 89 | + :param help_file: Help file with loaded children |
| 90 | + :return: Tuple of (groups_dict, commands_dict) |
| 91 | + """ |
| 92 | + groups = {} |
| 93 | + commands = {} |
| 94 | + |
| 95 | + for child in help_file.children: |
| 96 | + if hasattr(child, 'name') and hasattr(child, 'short_summary'): |
| 97 | + child_name = child.name |
| 98 | + # Only include top-level items (no spaces in name) |
| 99 | + if ' ' in child_name: |
| 100 | + continue |
| 101 | + |
| 102 | + tags = get_help_item_tags(child) |
| 103 | + item_data = { |
| 104 | + 'summary': child.short_summary, |
| 105 | + 'tags': tags |
| 106 | + } |
| 107 | + |
| 108 | + if child.type == 'group': |
| 109 | + groups[child_name] = item_data |
| 110 | + else: |
| 111 | + commands[child_name] = item_data |
| 112 | + |
| 113 | + return groups, commands |
| 114 | + |
| 115 | + |
49 | 116 | # PrintMixin class to decouple printing functionality from AZCLIHelp class. |
50 | 117 | # Most of these methods override print methods in CLIHelp |
51 | 118 | class CLIPrintMixin(CLIHelp): |
@@ -241,6 +308,107 @@ def update_loaders_with_help_file_contents(self, nouns): |
241 | 308 | def update_examples(help_file): |
242 | 309 | pass |
243 | 310 |
|
| 311 | + @staticmethod |
| 312 | + def _colorize_tag(tag_text, enable_color): |
| 313 | + """Add color to a plain text tag based on its content.""" |
| 314 | + if not enable_color or not tag_text: |
| 315 | + return tag_text |
| 316 | + |
| 317 | + from knack.util import color_map |
| 318 | + |
| 319 | + tag_lower = tag_text.lower() |
| 320 | + if 'preview' in tag_lower: |
| 321 | + color = color_map['preview'] |
| 322 | + elif 'experimental' in tag_lower: |
| 323 | + color = color_map['experimental'] |
| 324 | + elif 'deprecat' in tag_lower: |
| 325 | + color = color_map['deprecation'] |
| 326 | + else: |
| 327 | + return tag_text |
| 328 | + |
| 329 | + return f"{color}{tag_text}{color_map['reset']}" |
| 330 | + |
| 331 | + @staticmethod |
| 332 | + def _build_cached_help_items(data, enable_color=False): |
| 333 | + """Process help items from cache and return list with calculated line lengths.""" |
| 334 | + from knack.help import _get_line_len |
| 335 | + items = [] |
| 336 | + for name in sorted(data.keys()): |
| 337 | + item = data[name] |
| 338 | + plain_tags = item.get('tags', '') |
| 339 | + |
| 340 | + # Colorize each tag individually if needed |
| 341 | + if plain_tags and enable_color: |
| 342 | + # Split multiple tags and colorize each |
| 343 | + tag_parts = plain_tags.split() |
| 344 | + colored_tags = ' '.join(AzCliHelp._colorize_tag(tag, enable_color) for tag in tag_parts) |
| 345 | + else: |
| 346 | + colored_tags = plain_tags |
| 347 | + |
| 348 | + tags_len = len(plain_tags) |
| 349 | + line_len = _get_line_len(name, tags_len) |
| 350 | + items.append((name, colored_tags, line_len, item.get('summary', ''))) |
| 351 | + return items |
| 352 | + |
| 353 | + @staticmethod |
| 354 | + def _print_cached_help_section(items, header, max_line_len): |
| 355 | + """Display cached help items with consistent formatting.""" |
| 356 | + from knack.help import FIRST_LINE_PREFIX, _get_hanging_indent, _get_padding_len |
| 357 | + if not items: |
| 358 | + return |
| 359 | + print(f"\n{header}") |
| 360 | + indent = 1 |
| 361 | + LINE_FORMAT = '{name}{padding}{tags}{separator}{summary}' |
| 362 | + for name, tags, line_len, summary in items: |
| 363 | + layout = {'line_len': line_len, 'tags': tags} |
| 364 | + padding = ' ' * _get_padding_len(max_line_len, layout) |
| 365 | + line = LINE_FORMAT.format( |
| 366 | + name=name, |
| 367 | + padding=padding, |
| 368 | + tags=tags, |
| 369 | + separator=FIRST_LINE_PREFIX if summary else '', |
| 370 | + summary=summary |
| 371 | + ) |
| 372 | + _print_indent(line, indent, _get_hanging_indent(max_line_len, indent)) |
| 373 | + |
| 374 | + def show_cached_help(self, help_data, args=None): |
| 375 | + """Display help from cached help index without loading modules. |
| 376 | +
|
| 377 | + Args: |
| 378 | + help_data: Cached help data dictionary |
| 379 | + args: Original command line args. If empty/None, shows welcome banner. |
| 380 | + """ |
| 381 | + ran_before = self.cli_ctx.config.getboolean('core', 'first_run', fallback=False) |
| 382 | + if not ran_before: |
| 383 | + print(PRIVACY_STATEMENT) |
| 384 | + self.cli_ctx.config.set_value('core', 'first_run', 'yes') |
| 385 | + |
| 386 | + if not args: |
| 387 | + print(WELCOME_MESSAGE) |
| 388 | + |
| 389 | + print("\nGroup") |
| 390 | + print(" az") |
| 391 | + |
| 392 | + groups_data = help_data.get('groups', {}) |
| 393 | + commands_data = help_data.get('commands', {}) |
| 394 | + |
| 395 | + groups_items = self._build_cached_help_items(groups_data, self.cli_ctx.enable_color) |
| 396 | + commands_items = self._build_cached_help_items(commands_data, self.cli_ctx.enable_color) |
| 397 | + max_line_len = max( |
| 398 | + (line_len for _, _, line_len, _ in groups_items + commands_items), |
| 399 | + default=0 |
| 400 | + ) |
| 401 | + |
| 402 | + self._print_cached_help_section(groups_items, "Subgroups:", max_line_len) |
| 403 | + self._print_cached_help_section(commands_items, "Commands:", max_line_len) |
| 404 | + |
| 405 | + # Use same az find message as non-cached path |
| 406 | + print() # Blank line before the message |
| 407 | + self._print_az_find_message('') |
| 408 | + |
| 409 | + from azure.cli.core.util import show_updates_available |
| 410 | + show_updates_available(new_line_after=True) |
| 411 | + |
244 | 412 |
|
245 | 413 | class CliHelpFile(KnackHelpFile): |
246 | 414 |
|
|
0 commit comments