Convert version output string constants to enums
Marco Ricci

Marco Ricci commited on 2025-02-15 11:59:04
Zeige 4 geänderte Dateien mit 231 Einfügungen und 77 Löschungen.


This provides an obvious centralized location to change the phrasing of
the string constants, or add new related items.

Additionally, rename the `KNOWN_*` constants to `UNAVAILABLE_*` to make
their intent clearer.
... ...
@@ -28,7 +28,7 @@ import click
28 28
 import click.shell_completion
29 29
 from typing_extensions import Any, ParamSpec, override
30 30
 
31
-from derivepassphrase import _internals
31
+from derivepassphrase import _internals, _types
32 32
 from derivepassphrase._internals import cli_messages as _msg
33 33
 
34 34
 if TYPE_CHECKING:
... ...
@@ -1032,14 +1032,14 @@ def derivepassphrase_version_option_callback(
1032 1032
 ) -> None:
1033 1033
     if value and not ctx.resilient_parsing:
1034 1034
         common_version_output(ctx, param, value)
1035
-        derivation_schemes = {'vault': True}
1036
-        supported_subcommands = {'export', 'vault'}
1035
+        derivation_schemes = dict.fromkeys(_types.DerivationScheme, True)
1036
+        supported_subcommands = set(_types.Subcommand)
1037 1037
         click.echo()
1038
-        version_info_types = {
1038
+        version_info_types: dict[_msg.Label, list[str]] = {
1039 1039
             _msg.Label.SUPPORTED_DERIVATION_SCHEMES: [
1040 1040
                 k for k, v in derivation_schemes.items() if v
1041 1041
             ],
1042
-            _msg.Label.KNOWN_DERIVATION_SCHEMES: [
1042
+            _msg.Label.UNAVAILABLE_DERIVATION_SCHEMES: [
1043 1043
                 k for k, v in derivation_schemes.items() if not v
1044 1044
             ],
1045 1045
             _msg.Label.SUPPORTED_SUBCOMMANDS: sorted(supported_subcommands),
... ...
@@ -1055,15 +1055,15 @@ def export_version_option_callback(
1055 1055
 ) -> None:
1056 1056
     if value and not ctx.resilient_parsing:
1057 1057
         common_version_output(ctx, param, value)
1058
-        supported_subcommands = {'vault'}
1058
+        supported_subcommands = set(_types.ExportSubcommand)
1059 1059
         foreign_configuration_formats = {
1060
-            'vault storeroom': False,
1061
-            'vault v0.2': False,
1062
-            'vault v0.3': False,
1060
+            _types.ForeignConfigurationFormat.VAULT_STOREROOM: False,
1061
+            _types.ForeignConfigurationFormat.VAULT_V02: False,
1062
+            _types.ForeignConfigurationFormat.VAULT_V03: False,
1063 1063
         }
1064 1064
         click.echo()
1065
-        version_info_types = {
1066
-            _msg.Label.KNOWN_FOREIGN_CONFIGURATION_FORMATS: [
1065
+        version_info_types: dict[_msg.Label, list[str]] = {
1066
+            _msg.Label.UNAVAILABLE_FOREIGN_CONFIGURATION_FORMATS: [
1067 1067
                 k for k, v in foreign_configuration_formats.items() if not v
1068 1068
             ],
1069 1069
             _msg.Label.SUPPORTED_SUBCOMMANDS: sorted(supported_subcommands),
... ...
@@ -1080,36 +1080,36 @@ def export_vault_version_option_callback(
1080 1080
     if value and not ctx.resilient_parsing:
1081 1081
         common_version_output(ctx, param, value)
1082 1082
         foreign_configuration_formats = {
1083
-            'vault storeroom': False,
1084
-            'vault v0.2': False,
1085
-            'vault v0.3': False,
1083
+            _types.ForeignConfigurationFormat.VAULT_STOREROOM: False,
1084
+            _types.ForeignConfigurationFormat.VAULT_V02: False,
1085
+            _types.ForeignConfigurationFormat.VAULT_V03: False,
1086 1086
         }
1087 1087
         known_extras = {
1088
-            'export': False,
1088
+            _types.PEP508Extra.EXPORT: False,
1089 1089
         }
1090 1090
         try:
1091 1091
             from derivepassphrase.exporter import storeroom, vault_native  # noqa: I001,PLC0415
1092 1092
 
1093 1093
             foreign_configuration_formats[
1094
-                'vault storeroom'
1094
+                _types.ForeignConfigurationFormat.VAULT_STOREROOM
1095 1095
             ] = not storeroom.STUBBED
1096 1096
             foreign_configuration_formats[
1097
-                'vault v0.2'
1097
+                _types.ForeignConfigurationFormat.VAULT_V02
1098 1098
             ] = not vault_native.STUBBED
1099 1099
             foreign_configuration_formats[
1100
-                'vault v0.3'
1100
+                _types.ForeignConfigurationFormat.VAULT_V03
1101 1101
             ] = not vault_native.STUBBED
1102
-            known_extras['export'] = (
1102
+            known_extras[_types.PEP508Extra.EXPORT] = (
1103 1103
                 not storeroom.STUBBED and not vault_native.STUBBED
1104 1104
             )
1105 1105
         except ModuleNotFoundError:  # pragma: no cover
1106 1106
             pass
1107 1107
         click.echo()
1108
-        version_info_types = {
1108
+        version_info_types: dict[_msg.Label, list[str]] = {
1109 1109
             _msg.Label.SUPPORTED_FOREIGN_CONFIGURATION_FORMATS: [
1110 1110
                 k for k, v in foreign_configuration_formats.items() if v
1111 1111
             ],
1112
-            _msg.Label.KNOWN_FOREIGN_CONFIGURATION_FORMATS: [
1112
+            _msg.Label.UNAVAILABLE_FOREIGN_CONFIGURATION_FORMATS: [
1113 1113
                 k for k, v in foreign_configuration_formats.items() if not v
1114 1114
             ],
1115 1115
             _msg.Label.ENABLED_PEP508_EXTRAS: [
... ...
@@ -1128,14 +1128,14 @@ def vault_version_option_callback(
1128 1128
     if value and not ctx.resilient_parsing:
1129 1129
         common_version_output(ctx, param, value)
1130 1130
         features = {
1131
-            'master SSH key': hasattr(socket, 'AF_UNIX'),
1131
+            _types.Feature.SSH_KEY: hasattr(socket, 'AF_UNIX'),
1132 1132
         }
1133 1133
         click.echo()
1134
-        version_info_types = {
1134
+        version_info_types: dict[_msg.Label, list[str]] = {
1135 1135
             _msg.Label.SUPPORTED_FEATURES: [
1136 1136
                 k for k, v in features.items() if v
1137 1137
             ],
1138
-            _msg.Label.KNOWN_FEATURES: [
1138
+            _msg.Label.UNAVAILABLE_FEATURES: [
1139 1139
                 k for k, v in features.items() if not v
1140 1140
             ],
1141 1141
         }
... ...
@@ -1284,75 +1284,82 @@ class Label(enum.Enum):
1284 1284
     """"""
1285 1285
     ENABLED_PEP508_EXTRAS = commented(
1286 1286
         'This is part of the version output, emitting lists of enabled '
1287
-        'PEP 508 extras.  A comma-separated English list of items follows, '
1287
+        'PEP 508 extras.  '
1288
+        'A comma-separated English list of items follows, '
1288 1289
         'with standard English punctuation.',
1289 1290
     )(
1290 1291
         'Label :: Info Message:: Table row header',
1291 1292
         'PEP 508 extras:',
1292 1293
     )
1293 1294
     """"""
1294
-    KNOWN_DERIVATION_SCHEMES = commented(
1295
-        'This is part of the version output, emitting lists of known '
1296
-        'derivation schemes.  A comma-separated English list of items '
1297
-        'follows, with standard English punctuation.',
1295
+    SUPPORTED_DERIVATION_SCHEMES = commented(
1296
+        'This is part of the version output, emitting lists of supported '
1297
+        'derivation schemes.  '
1298
+        'A comma-separated English list of items follows, '
1299
+        'with standard English punctuation.',
1298 1300
     )(
1299 1301
         'Label :: Info Message:: Table row header',
1300
-        'Known derivation schemes:',
1302
+        'Supported derivation schemes:',
1301 1303
     )
1302 1304
     """"""
1303
-    KNOWN_FEATURES = commented(
1304
-        'This is part of the version output, emitting lists of known, '
1305
-        'unavailable features for this subcommand.  '
1305
+    SUPPORTED_FEATURES = commented(
1306
+        'This is part of the version output, emitting lists of supported '
1307
+        'features for this subcommand.  '
1306 1308
         'A comma-separated English list of items follows, '
1307 1309
         'with standard English punctuation.',
1308 1310
     )(
1309 1311
         'Label :: Info Message:: Table row header',
1310
-        'Known features:',
1312
+        'Supported features:',
1311 1313
     )
1312 1314
     """"""
1313
-    KNOWN_FOREIGN_CONFIGURATION_FORMATS = commented(
1314
-        'This is part of the version output, emitting lists of known '
1315
-        'foreign configuration formats.  A comma-separated English list '
1316
-        'of items follows, with standard English punctuation.',
1315
+    SUPPORTED_FOREIGN_CONFIGURATION_FORMATS = commented(
1316
+        'This is part of the version output, emitting lists of supported '
1317
+        'foreign configuration formats.  '
1318
+        'A comma-separated English list of items follows, '
1319
+        'with standard English punctuation.',
1317 1320
     )(
1318 1321
         'Label :: Info Message:: Table row header',
1319
-        'Known foreign configuration formats:',
1322
+        'Supported foreign configuration formats:',
1320 1323
     )
1321 1324
     """"""
1322
-    SUPPORTED_DERIVATION_SCHEMES = commented(
1325
+    SUPPORTED_SUBCOMMANDS = commented(
1323 1326
         'This is part of the version output, emitting lists of supported '
1324
-        'derivation schemes.  A comma-separated English list of items '
1325
-        'follows, with standard English punctuation.',
1327
+        'subcommands.  '
1328
+        'A comma-separated English list of items follows, '
1329
+        'with standard English punctuation.',
1326 1330
     )(
1327 1331
         'Label :: Info Message:: Table row header',
1328
-        'Supported derivation schemes:',
1332
+        'Supported subcommands:',
1329 1333
     )
1330 1334
     """"""
1331
-    SUPPORTED_FEATURES = commented(
1332
-        'This is part of the version output, emitting lists of supported '
1333
-        'features for this subcommand.  A comma-separated '
1334
-        'English list of items follows, with standard English punctuation.',
1335
+    UNAVAILABLE_DERIVATION_SCHEMES = commented(
1336
+        'This is part of the version output, emitting lists of known, '
1337
+        'unavailable derivation schemes.  '
1338
+        'A comma-separated English list of items follows, '
1339
+        'with standard English punctuation.',
1335 1340
     )(
1336 1341
         'Label :: Info Message:: Table row header',
1337
-        'Supported features:',
1342
+        'Known derivation schemes:',
1338 1343
     )
1339 1344
     """"""
1340
-    SUPPORTED_FOREIGN_CONFIGURATION_FORMATS = commented(
1341
-        'This is part of the version output, emitting lists of supported '
1342
-        'foreign configuration formats.  A comma-separated English list '
1343
-        'of items follows, with standard English punctuation.',
1345
+    UNAVAILABLE_FEATURES = commented(
1346
+        'This is part of the version output, emitting lists of known, '
1347
+        'unavailable features for this subcommand.  '
1348
+        'A comma-separated English list of items follows, '
1349
+        'with standard English punctuation.',
1344 1350
     )(
1345 1351
         'Label :: Info Message:: Table row header',
1346
-        'Supported foreign configuration formats:',
1352
+        'Known features:',
1347 1353
     )
1348 1354
     """"""
1349
-    SUPPORTED_SUBCOMMANDS = commented(
1350
-        'This is part of the version output, emitting lists of supported '
1351
-        'subcommands.  A comma-separated English list '
1352
-        'of items follows, with standard English punctuation.',
1355
+    UNAVAILABLE_FOREIGN_CONFIGURATION_FORMATS = commented(
1356
+        'This is part of the version output, emitting lists of known, '
1357
+        'unavailable foreign configuration formats.  '
1358
+        'A comma-separated English list of items follows, '
1359
+        'with standard English punctuation.',
1353 1360
     )(
1354 1361
         'Label :: Info Message:: Table row header',
1355
-        'Supported subcommands:',
1362
+        'Known foreign configuration formats:',
1356 1363
     )
1357 1364
     """"""
1358 1365
     CONFIRM_THIS_CHOICE_PROMPT_TEXT = commented(
... ...
@@ -755,3 +755,120 @@ class StoreroomMasterKeys(NamedTuple, Generic[T_Buffer]):
755 755
             encryption_key=bytes(self.encryption_key),
756 756
             signing_key=bytes(self.signing_key),
757 757
         )
758
+
759
+
760
+class PEP508Extra(str, enum.Enum):
761
+    """PEP 508 extras supported by `derivepassphrase`.
762
+
763
+    Attributes:
764
+        EXPORT:
765
+            The necessary dependencies to allow the `export` subcommand
766
+            to handle as many foreign configuration formats as possible.
767
+
768
+    """
769
+
770
+    EXPORT = 'export'
771
+    """"""
772
+
773
+    __str__ = str.__str__
774
+    __format__ = str.__format__  # type: ignore[assignment]
775
+
776
+
777
+class Feature(str, enum.Enum):
778
+    """Optional features supported by `derivepassphrase`.
779
+
780
+    Attributes:
781
+        SSH_KEY:
782
+            The `vault` subcommand supports using a master SSH key,
783
+            instead of a master passphrase, if an SSH agent is running
784
+            and the master SSH key is loaded into it.
785
+
786
+            This feature requires Python support for the SSH agent's
787
+            chosen communication channel technology.
788
+
789
+    """
790
+
791
+    SSH_KEY = 'master SSH key'
792
+    """"""
793
+
794
+    __str__ = str.__str__
795
+    __format__ = str.__format__  # type: ignore[assignment]
796
+
797
+
798
+class DerivationScheme(str, enum.Enum):
799
+    """Derivation schemes provided by `derivepassphrase`.
800
+
801
+    Attributes:
802
+        VAULT:
803
+            The derivation scheme used by James Coglan's `vault`.
804
+
805
+    """
806
+
807
+    VAULT = 'vault'
808
+    """"""
809
+
810
+    __str__ = str.__str__
811
+    __format__ = str.__format__  # type: ignore[assignment]
812
+
813
+
814
+class ForeignConfigurationFormat(str, enum.Enum):
815
+    """Configuration formats supported by `derivepassphrase export`.
816
+
817
+    Attributes:
818
+        VAULT_STOREROOM:
819
+            The vault "storeroom" format for the `export vault`
820
+            subcommand.
821
+        VAULT_V02:
822
+            The vault-native "v0.2" format for the `export vault`
823
+            subcommand.
824
+        VAULT_V03:
825
+            The vault-native "v0.3" format for the `export vault`
826
+            subcommand.
827
+
828
+    """
829
+
830
+    VAULT_STOREROOM = 'vault storeroom'
831
+    """"""
832
+    VAULT_V02 = 'vault v0.2'
833
+    """"""
834
+    VAULT_V03 = 'vault v0.3'
835
+    """"""
836
+
837
+    __str__ = str.__str__
838
+    __format__ = str.__format__  # type: ignore[assignment]
839
+
840
+
841
+class ExportSubcommand(str, enum.Enum):
842
+    """Subcommands provided by `derivepassphrase export`.
843
+
844
+    Attributes:
845
+        VAULT:
846
+            The `export vault` subcommand.
847
+
848
+    """
849
+
850
+    VAULT = 'vault'
851
+    """"""
852
+
853
+    __str__ = str.__str__
854
+    __format__ = str.__format__  # type: ignore[assignment]
855
+
856
+
857
+class Subcommand(str, enum.Enum):
858
+    """Subcommands provided by `derivepassphrase`.
859
+
860
+    Attributes:
861
+        EXPORT:
862
+            The `export` subcommand.
863
+        VAULT:
864
+            The `vault` subcommand.
865
+
866
+    """
867
+
868
+    EXPORT = 'export'
869
+    """"""
870
+    VAULT = 'vault'
871
+    """"""
872
+
873
+    __str__ = str.__str__
874
+    __format__ = str.__format__  # type: ignore[assignment]
... ...
@@ -7,6 +7,7 @@ from __future__ import annotations
7 7
 import base64
8 8
 import contextlib
9 9
 import copy
10
+import enum
10 11
 import errno
11 12
 import io
12 13
 import json
... ...
@@ -89,6 +90,35 @@ class VersionOutputData(NamedTuple):
89 90
     features: dict[str, bool]
90 91
 
91 92
 
93
+class KnownLineType(str, enum.Enum):
94
+    SUPPORTED_FOREIGN_CONFS = cli_messages.Label.SUPPORTED_FOREIGN_CONFIGURATION_FORMATS.value.singular.rstrip(
95
+        ':'
96
+    )
97
+    UNAVAILABLE_FOREIGN_CONFS = cli_messages.Label.UNAVAILABLE_FOREIGN_CONFIGURATION_FORMATS.value.singular.rstrip(
98
+        ':'
99
+    )
100
+    SUPPORTED_SCHEMES = (
101
+        cli_messages.Label.SUPPORTED_DERIVATION_SCHEMES.value.singular.rstrip(
102
+            ':'
103
+        )
104
+    )
105
+    UNAVAILABLE_SCHEMES = cli_messages.Label.UNAVAILABLE_DERIVATION_SCHEMES.value.singular.rstrip(
106
+        ':'
107
+    )
108
+    SUPPORTED_SUBCOMMANDS = (
109
+        cli_messages.Label.SUPPORTED_SUBCOMMANDS.value.singular.rstrip(':')
110
+    )
111
+    SUPPORTED_FEATURES = (
112
+        cli_messages.Label.SUPPORTED_FEATURES.value.singular.rstrip(':')
113
+    )
114
+    UNAVAILABLE_FEATURES = (
115
+        cli_messages.Label.UNAVAILABLE_FEATURES.value.singular.rstrip(':')
116
+    )
117
+    ENABLED_EXTRAS = (
118
+        cli_messages.Label.ENABLED_PEP508_EXTRAS.value.singular.rstrip(':')
119
+    )
120
+
121
+
92 122
 PASSPHRASE_GENERATION_OPTIONS: list[tuple[str, ...]] = [
93 123
     ('--phrase',),
94 124
     ('--key',),
... ...
@@ -423,21 +453,21 @@ def parse_version_output(  # noqa: C901
423 453
             item = item_.strip()
424 454
             if not item:
425 455
                 continue
426
-            if line_type == 'Supported foreign configuration formats':
456
+            if line_type == KnownLineType.SUPPORTED_FOREIGN_CONFS:
427 457
                 formats[item] = True
428
-            elif line_type == 'Known foreign configuration formats':
458
+            elif line_type == KnownLineType.UNAVAILABLE_FOREIGN_CONFS:
429 459
                 formats[item] = False
430
-            elif line_type == 'Supported derivation schemes':
460
+            elif line_type == KnownLineType.SUPPORTED_SCHEMES:
431 461
                 schemes[item] = True
432
-            elif line_type == 'Known derivation schemes':
462
+            elif line_type == KnownLineType.UNAVAILABLE_SCHEMES:
433 463
                 schemes[item] = False
434
-            elif line_type == 'Supported subcommands':
464
+            elif line_type == KnownLineType.SUPPORTED_SUBCOMMANDS:
435 465
                 subcommands.add(item)
436
-            elif line_type == 'PEP 508 extras':
466
+            elif line_type == KnownLineType.ENABLED_EXTRAS:
437 467
                 extras.add(item)
438
-            elif line_type == 'Supported features':
468
+            elif line_type == KnownLineType.SUPPORTED_FEATURES:
439 469
                 features[item] = True
440
-            elif line_type == 'Known features':
470
+            elif line_type == KnownLineType.UNAVAILABLE_FEATURES:
441 471
                 features[item] = False
442 472
             else:
443 473
                 raise AssertionError(  # noqa: TRY003
... ...
@@ -1848,8 +1878,8 @@ class TestAllCLI:
1848 1878
         assert result.clean_exit(empty_stderr=True), 'expected clean exit'
1849 1879
         assert result.output.strip(), 'expected version output'
1850 1880
         version_data = parse_version_output(result.output)
1851
-        actually_known_schemes = {'vault': True}
1852
-        subcommands = {'export', 'vault'}
1881
+        actually_known_schemes = dict.fromkeys(_types.DerivationScheme, True)
1882
+        subcommands = set(_types.Subcommand)
1853 1883
         assert version_data.derivation_schemes == actually_known_schemes
1854 1884
         assert not version_data.foreign_configuration_formats
1855 1885
         assert version_data.subcommands == subcommands
... ...
@@ -1891,11 +1921,11 @@ class TestAllCLI:
1891 1921
         assert result.output.strip(), 'expected version output'
1892 1922
         version_data = parse_version_output(result.output)
1893 1923
         actually_known_formats: dict[str, bool] = {
1894
-            'vault storeroom': False,
1895
-            'vault v0.2': False,
1896
-            'vault v0.3': False,
1924
+            _types.ForeignConfigurationFormat.VAULT_STOREROOM: False,
1925
+            _types.ForeignConfigurationFormat.VAULT_V02: False,
1926
+            _types.ForeignConfigurationFormat.VAULT_V03: False,
1897 1927
         }
1898
-        subcommands = {'vault'}
1928
+        subcommands = set(_types.ExportSubcommand)
1899 1929
         assert not version_data.derivation_schemes
1900 1930
         assert (
1901 1931
             version_data.foreign_configuration_formats
... ...
@@ -1945,12 +1975,12 @@ class TestAllCLI:
1945 1975
             from derivepassphrase.exporter import storeroom, vault_native  # noqa: I001,PLC0415
1946 1976
 
1947 1977
             actually_known_formats.update({
1948
-                'vault storeroom': not storeroom.STUBBED,
1949
-                'vault v0.2': not vault_native.STUBBED,
1950
-                'vault v0.3': not vault_native.STUBBED,
1978
+                _types.ForeignConfigurationFormat.VAULT_STOREROOM: not storeroom.STUBBED,
1979
+                _types.ForeignConfigurationFormat.VAULT_V02: not vault_native.STUBBED,
1980
+                _types.ForeignConfigurationFormat.VAULT_V03: not vault_native.STUBBED,
1951 1981
             })
1952 1982
             if not storeroom.STUBBED and not vault_native.STUBBED:
1953
-                actually_enabled_extras.add('export')
1983
+                actually_enabled_extras.add(_types.PEP508Extra.EXPORT)
1954 1984
         assert not version_data.derivation_schemes
1955 1985
         assert (
1956 1986
             version_data.foreign_configuration_formats
... ...
@@ -1994,7 +2024,7 @@ class TestAllCLI:
1994 2024
         assert result.output.strip(), 'expected version output'
1995 2025
         version_data = parse_version_output(result.output)
1996 2026
         features: dict[str, bool] = {
1997
-            'master SSH key': hasattr(socket, 'AF_UNIX'),
2027
+            _types.Feature.SSH_KEY: hasattr(socket, 'AF_UNIX'),
1998 2028
         }
1999 2029
         assert not version_data.derivation_schemes
2000 2030
         assert not version_data.foreign_configuration_formats
2001 2031