Marco Ricci commited on 2024-12-07 08:50:03
Zeige 1 geänderte Dateien mit 99 Einfügungen und 95 Löschungen.
We expect to add new option categories that apply to multiple super- and subcommands, not just the "vault" subcommand.
| ... | ... |
@@ -57,6 +57,105 @@ _NO_USABLE_KEYS = 'No usable SSH keys were found' |
| 57 | 57 |
_EMPTY_SELECTION = 'Empty selection' |
| 58 | 58 |
|
| 59 | 59 |
|
| 60 |
+# Option parsing and grouping |
|
| 61 |
+# =========================== |
|
| 62 |
+ |
|
| 63 |
+ |
|
| 64 |
+class OptionGroupOption(click.Option): |
|
| 65 |
+ """A [`click.Option`][] with an associated group name and group epilog. |
|
| 66 |
+ |
|
| 67 |
+ Used by [`CommandWithHelpGroups`][] to print help sections. Each |
|
| 68 |
+ subclass contains its own group name and epilog. |
|
| 69 |
+ |
|
| 70 |
+ Attributes: |
|
| 71 |
+ option_group_name: |
|
| 72 |
+ The name of the option group. Used as a heading on the help |
|
| 73 |
+ text for options in this section. |
|
| 74 |
+ epilog: |
|
| 75 |
+ An epilog to print after listing the options in this |
|
| 76 |
+ section. |
|
| 77 |
+ |
|
| 78 |
+ """ |
|
| 79 |
+ |
|
| 80 |
+ option_group_name: str = '' |
|
| 81 |
+ """""" |
|
| 82 |
+ epilog: str = '' |
|
| 83 |
+ """""" |
|
| 84 |
+ |
|
| 85 |
+ def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401 |
|
| 86 |
+ if self.__class__ == __class__: # type: ignore[name-defined] |
|
| 87 |
+ raise NotImplementedError |
|
| 88 |
+ super().__init__(*args, **kwargs) |
|
| 89 |
+ |
|
| 90 |
+ |
|
| 91 |
+class CommandWithHelpGroups(click.Command): |
|
| 92 |
+ """A [`click.Command`][] with support for help/option groups. |
|
| 93 |
+ |
|
| 94 |
+ Inspired by [a comment on `pallets/click#373`][CLICK_ISSUE], and |
|
| 95 |
+ further modified to support group epilogs. |
|
| 96 |
+ |
|
| 97 |
+ [CLICK_ISSUE]: https://github.com/pallets/click/issues/373#issuecomment-515293746 |
|
| 98 |
+ |
|
| 99 |
+ """ |
|
| 100 |
+ |
|
| 101 |
+ def format_options( |
|
| 102 |
+ self, |
|
| 103 |
+ ctx: click.Context, |
|
| 104 |
+ formatter: click.HelpFormatter, |
|
| 105 |
+ ) -> None: |
|
| 106 |
+ r"""Format options on the help listing, grouped into sections. |
|
| 107 |
+ |
|
| 108 |
+ This is a callback for [`click.Command.get_help`][] that |
|
| 109 |
+ implements the `--help` listing, by calling appropriate methods |
|
| 110 |
+ of the `formatter`. We list all options (like the base |
|
| 111 |
+ implementation), but grouped into sections according to the |
|
| 112 |
+ concrete [`click.Option`][] subclass being used. If the option |
|
| 113 |
+ is an instance of some subclass of [`OptionGroupOption`][], then |
|
| 114 |
+ the section heading and the epilog are taken from the |
|
| 115 |
+ [`option_group_name`] [OptionGroupOption.option_group_name] and |
|
| 116 |
+ [`epilog`] [OptionGroupOption.epilog] attributes; otherwise, the |
|
| 117 |
+ section heading is "Options" (or "Other options" if there are |
|
| 118 |
+ other option groups) and the epilog is empty. |
|
| 119 |
+ |
|
| 120 |
+ Args: |
|
| 121 |
+ ctx: |
|
| 122 |
+ The click context. |
|
| 123 |
+ formatter: |
|
| 124 |
+ The formatter for the `--help` listing. |
|
| 125 |
+ |
|
| 126 |
+ """ |
|
| 127 |
+ help_records: dict[str, list[tuple[str, str]]] = {}
|
|
| 128 |
+ epilogs: dict[str, str] = {}
|
|
| 129 |
+ params = self.params[:] |
|
| 130 |
+ if ( # pragma: no branch |
|
| 131 |
+ (help_opt := self.get_help_option(ctx)) is not None |
|
| 132 |
+ and help_opt not in params |
|
| 133 |
+ ): |
|
| 134 |
+ params.append(help_opt) |
|
| 135 |
+ for param in params: |
|
| 136 |
+ rec = param.get_help_record(ctx) |
|
| 137 |
+ if rec is not None: |
|
| 138 |
+ if isinstance(param, OptionGroupOption): |
|
| 139 |
+ group_name = param.option_group_name |
|
| 140 |
+ epilogs.setdefault(group_name, param.epilog) |
|
| 141 |
+ else: |
|
| 142 |
+ group_name = '' |
|
| 143 |
+ help_records.setdefault(group_name, []).append(rec) |
|
| 144 |
+ default_group = help_records.pop('')
|
|
| 145 |
+ default_group_name = ( |
|
| 146 |
+ 'Other Options' if len(default_group) > 1 else 'Options' |
|
| 147 |
+ ) |
|
| 148 |
+ help_records[default_group_name] = default_group |
|
| 149 |
+ for group_name, records in help_records.items(): |
|
| 150 |
+ with formatter.section(group_name): |
|
| 151 |
+ formatter.write_dl(records) |
|
| 152 |
+ epilog = inspect.cleandoc(epilogs.get(group_name, '')) |
|
| 153 |
+ if epilog: |
|
| 154 |
+ formatter.write_paragraph() |
|
| 155 |
+ with formatter.indentation(): |
|
| 156 |
+ formatter.write_text(epilog) |
|
| 157 |
+ |
|
| 158 |
+ |
|
| 60 | 159 |
# Top-level |
| 61 | 160 |
# ========= |
| 62 | 161 |
|
| ... | ... |
@@ -767,101 +866,6 @@ def _check_for_misleading_passphrase( |
| 767 | 866 |
) |
| 768 | 867 |
|
| 769 | 868 |
|
| 770 |
-class OptionGroupOption(click.Option): |
|
| 771 |
- """A [`click.Option`][] with an associated group name and group epilog. |
|
| 772 |
- |
|
| 773 |
- Used by [`CommandWithHelpGroups`][] to print help sections. Each |
|
| 774 |
- subclass contains its own group name and epilog. |
|
| 775 |
- |
|
| 776 |
- Attributes: |
|
| 777 |
- option_group_name: |
|
| 778 |
- The name of the option group. Used as a heading on the help |
|
| 779 |
- text for options in this section. |
|
| 780 |
- epilog: |
|
| 781 |
- An epilog to print after listing the options in this |
|
| 782 |
- section. |
|
| 783 |
- |
|
| 784 |
- """ |
|
| 785 |
- |
|
| 786 |
- option_group_name: str = '' |
|
| 787 |
- """""" |
|
| 788 |
- epilog: str = '' |
|
| 789 |
- """""" |
|
| 790 |
- |
|
| 791 |
- def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: ANN401 |
|
| 792 |
- if self.__class__ == __class__: # type: ignore[name-defined] |
|
| 793 |
- raise NotImplementedError |
|
| 794 |
- super().__init__(*args, **kwargs) |
|
| 795 |
- |
|
| 796 |
- |
|
| 797 |
-class CommandWithHelpGroups(click.Command): |
|
| 798 |
- """A [`click.Command`][] with support for help/option groups. |
|
| 799 |
- |
|
| 800 |
- Inspired by [a comment on `pallets/click#373`][CLICK_ISSUE], and |
|
| 801 |
- further modified to support group epilogs. |
|
| 802 |
- |
|
| 803 |
- [CLICK_ISSUE]: https://github.com/pallets/click/issues/373#issuecomment-515293746 |
|
| 804 |
- |
|
| 805 |
- """ |
|
| 806 |
- |
|
| 807 |
- def format_options( |
|
| 808 |
- self, |
|
| 809 |
- ctx: click.Context, |
|
| 810 |
- formatter: click.HelpFormatter, |
|
| 811 |
- ) -> None: |
|
| 812 |
- r"""Format options on the help listing, grouped into sections. |
|
| 813 |
- |
|
| 814 |
- This is a callback for [`click.Command.get_help`][] that |
|
| 815 |
- implements the `--help` listing, by calling appropriate methods |
|
| 816 |
- of the `formatter`. We list all options (like the base |
|
| 817 |
- implementation), but grouped into sections according to the |
|
| 818 |
- concrete [`click.Option`][] subclass being used. If the option |
|
| 819 |
- is an instance of some subclass of [`OptionGroupOption`][], then |
|
| 820 |
- the section heading and the epilog are taken from the |
|
| 821 |
- [`option_group_name`] [OptionGroupOption.option_group_name] and |
|
| 822 |
- [`epilog`] [OptionGroupOption.epilog] attributes; otherwise, the |
|
| 823 |
- section heading is "Options" (or "Other options" if there are |
|
| 824 |
- other option groups) and the epilog is empty. |
|
| 825 |
- |
|
| 826 |
- Args: |
|
| 827 |
- ctx: |
|
| 828 |
- The click context. |
|
| 829 |
- formatter: |
|
| 830 |
- The formatter for the `--help` listing. |
|
| 831 |
- |
|
| 832 |
- """ |
|
| 833 |
- help_records: dict[str, list[tuple[str, str]]] = {}
|
|
| 834 |
- epilogs: dict[str, str] = {}
|
|
| 835 |
- params = self.params[:] |
|
| 836 |
- if ( # pragma: no branch |
|
| 837 |
- (help_opt := self.get_help_option(ctx)) is not None |
|
| 838 |
- and help_opt not in params |
|
| 839 |
- ): |
|
| 840 |
- params.append(help_opt) |
|
| 841 |
- for param in params: |
|
| 842 |
- rec = param.get_help_record(ctx) |
|
| 843 |
- if rec is not None: |
|
| 844 |
- if isinstance(param, OptionGroupOption): |
|
| 845 |
- group_name = param.option_group_name |
|
| 846 |
- epilogs.setdefault(group_name, param.epilog) |
|
| 847 |
- else: |
|
| 848 |
- group_name = '' |
|
| 849 |
- help_records.setdefault(group_name, []).append(rec) |
|
| 850 |
- default_group = help_records.pop('')
|
|
| 851 |
- default_group_name = ( |
|
| 852 |
- 'Other Options' if len(default_group) > 1 else 'Options' |
|
| 853 |
- ) |
|
| 854 |
- help_records[default_group_name] = default_group |
|
| 855 |
- for group_name, records in help_records.items(): |
|
| 856 |
- with formatter.section(group_name): |
|
| 857 |
- formatter.write_dl(records) |
|
| 858 |
- epilog = inspect.cleandoc(epilogs.get(group_name, '')) |
|
| 859 |
- if epilog: |
|
| 860 |
- formatter.write_paragraph() |
|
| 861 |
- with formatter.indentation(): |
|
| 862 |
- formatter.write_text(epilog) |
|
| 863 |
- |
|
| 864 |
- |
|
| 865 | 869 |
# Concrete option groups used by this command-line interface. |
| 866 | 870 |
class PasswordGenerationOption(OptionGroupOption): |
| 867 | 871 |
"""Password generation options for the CLI.""" |
| 868 | 872 |