Shift option parsing and grouping machinery to a separate section
Marco Ricci

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