Marco Ricci commited on 2024-08-16 12:38:08
Zeige 2 geänderte Dateien mit 30 Einfügungen und 19 Löschungen.
Instead of blindly relying on `click.Context.fail`, which causes a usage message to be printed, implement our own error-exit function. Furthermore, since writing the configuration file is prone to similar problems as reading it, include a wrapper for writing the configuration file as well, which ensures errors are properly reported. Due to the rewrite, some error messages changed slightly in phrasing and/or in capitalization.
... | ... |
@@ -16,6 +16,7 @@ import os |
16 | 16 |
import socket |
17 | 17 |
from typing import ( |
18 | 18 |
TYPE_CHECKING, |
19 |
+ NoReturn, |
|
19 | 20 |
TextIO, |
20 | 21 |
cast, |
21 | 22 |
) |
... | ... |
@@ -844,13 +845,23 @@ def derivepassphrase( |
844 | 845 |
opt_str, f'mutually exclusive with {other_str}', ctx=ctx |
845 | 846 |
) |
846 | 847 |
|
848 |
+ def err(msg: str) -> NoReturn: |
|
849 |
+ click.echo(f'{PROG_NAME}: {msg}', err=True) |
|
850 |
+ ctx.exit(1) |
|
851 |
+ |
|
847 | 852 |
def get_config() -> _types.VaultConfig: |
848 | 853 |
try: |
849 | 854 |
return _load_config() |
850 | 855 |
except FileNotFoundError: |
851 | 856 |
return {'services': {}} |
852 | 857 |
except Exception as e: # noqa: BLE001 |
853 |
- ctx.fail(f'cannot load config: {e}') |
|
858 |
+ err(f'Cannot load config: {e}') |
|
859 |
+ |
|
860 |
+ def put_config(config: _types.VaultConfig, /) -> None: |
|
861 |
+ try: |
|
862 |
+ _save_config(config) |
|
863 |
+ except Exception as exc: # noqa: BLE001 |
|
864 |
+ err(f'Cannot store config: {exc}') |
|
854 | 865 |
|
855 | 866 |
configuration: _types.VaultConfig |
856 | 867 |
|
... | ... |
@@ -914,24 +925,24 @@ def derivepassphrase( |
914 | 925 |
break |
915 | 926 |
else: |
916 | 927 |
if not notes_value.strip(): |
917 |
- ctx.fail('not saving new notes: user aborted request') |
|
928 |
+ err('not saving new notes: user aborted request') |
|
918 | 929 |
configuration['services'].setdefault(service, {})['notes'] = ( |
919 | 930 |
notes_value.strip('\n') |
920 | 931 |
) |
921 |
- _save_config(configuration) |
|
932 |
+ put_config(configuration) |
|
922 | 933 |
elif delete_service_settings: |
923 | 934 |
assert service is not None |
924 | 935 |
configuration = get_config() |
925 | 936 |
if service in configuration['services']: |
926 | 937 |
del configuration['services'][service] |
927 |
- _save_config(configuration) |
|
938 |
+ put_config(configuration) |
|
928 | 939 |
elif delete_globals: |
929 | 940 |
configuration = get_config() |
930 | 941 |
if 'global' in configuration: |
931 | 942 |
del configuration['global'] |
932 |
- _save_config(configuration) |
|
943 |
+ put_config(configuration) |
|
933 | 944 |
elif clear_all_settings: |
934 |
- _save_config({'services': {}}) |
|
945 |
+ put_config({'services': {}}) |
|
935 | 946 |
elif import_settings: |
936 | 947 |
try: |
937 | 948 |
# TODO: keep track of auto-close; try os.dup if feasible |
... | ... |
@@ -943,13 +954,13 @@ def derivepassphrase( |
943 | 954 |
with infile: |
944 | 955 |
maybe_config = json.load(infile) |
945 | 956 |
except json.JSONDecodeError as e: |
946 |
- ctx.fail(f'Cannot load config: cannot decode JSON: {e}') |
|
957 |
+ err(f'Cannot load config: cannot decode JSON: {e}') |
|
947 | 958 |
except OSError as e: |
948 |
- ctx.fail(f'Cannot load config: {e.strerror}') |
|
959 |
+ err(f'Cannot load config: {e.strerror}: {e.filename!r}') |
|
949 | 960 |
if _types.is_vault_config(maybe_config): |
950 |
- _save_config(maybe_config) |
|
961 |
+ put_config(maybe_config) |
|
951 | 962 |
else: |
952 |
- ctx.fail('not a valid config') |
|
963 |
+ err(f'Cannot load config: {_INVALID_VAULT_CONFIG}') |
|
953 | 964 |
elif export_settings: |
954 | 965 |
configuration = get_config() |
955 | 966 |
try: |
... | ... |
@@ -962,7 +973,7 @@ def derivepassphrase( |
962 | 973 |
with outfile: |
963 | 974 |
json.dump(configuration, outfile) |
964 | 975 |
except OSError as e: |
965 |
- ctx.fail(f'cannot write config: {e.strerror}') |
|
976 |
+ err(f'Cannot store config: {e.strerror}: {e.filename!r}') |
|
966 | 977 |
else: |
967 | 978 |
configuration = get_config() |
968 | 979 |
# This block could be type checked more stringently, but this |
... | ... |
@@ -1001,13 +1012,13 @@ def derivepassphrase( |
1001 | 1012 |
'ASCII' |
1002 | 1013 |
) |
1003 | 1014 |
except IndexError: |
1004 |
- ctx.fail('no valid SSH key selected') |
|
1015 |
+ err('no valid SSH key selected') |
|
1005 | 1016 |
except (LookupError, RuntimeError) as e: |
1006 |
- ctx.fail(str(e)) |
|
1017 |
+ err(str(e)) |
|
1007 | 1018 |
elif use_phrase: |
1008 | 1019 |
maybe_phrase = _prompt_for_passphrase() |
1009 | 1020 |
if not maybe_phrase: |
1010 |
- ctx.fail('no passphrase given') |
|
1021 |
+ err('no passphrase given') |
|
1011 | 1022 |
else: |
1012 | 1023 |
phrase = maybe_phrase |
1013 | 1024 |
if store_config_only: |
... | ... |
@@ -609,7 +609,7 @@ class TestCLI: |
609 | 609 |
result.stderr_bytes |
610 | 610 |
), 'program did not print any error message' |
611 | 611 |
assert ( |
612 |
- b'not a valid config' in result.stderr_bytes |
|
612 |
+ b'Invalid vault config' in result.stderr_bytes |
|
613 | 613 |
), 'program did not print the expected error message' |
614 | 614 |
|
615 | 615 |
def test_213a_import_bad_config_not_json_data( |
... | ... |
@@ -699,7 +699,7 @@ class TestCLI: |
699 | 699 |
result.stderr_bytes |
700 | 700 |
), 'program did not print any error message' |
701 | 701 |
assert ( |
702 |
- b'cannot load config' in result.stderr_bytes |
|
702 |
+ b'Cannot load config' in result.stderr_bytes |
|
703 | 703 |
), 'program did not print the expected error message' |
704 | 704 |
|
705 | 705 |
def test_214b_export_settings_not_a_file( |
... | ... |
@@ -724,7 +724,7 @@ class TestCLI: |
724 | 724 |
result.stderr_bytes |
725 | 725 |
), 'program did not print any error message' |
726 | 726 |
assert ( |
727 |
- b'cannot load config' in result.stderr_bytes |
|
727 |
+ b'Cannot load config' in result.stderr_bytes |
|
728 | 728 |
), 'program did not print the expected error message' |
729 | 729 |
|
730 | 730 |
def test_214c_export_settings_target_not_a_file( |
... | ... |
@@ -747,7 +747,7 @@ class TestCLI: |
747 | 747 |
result.stderr_bytes |
748 | 748 |
), 'program did not print any error message' |
749 | 749 |
assert ( |
750 |
- b'cannot write config' in result.stderr_bytes |
|
750 |
+ b'Cannot store config' in result.stderr_bytes |
|
751 | 751 |
), 'program did not print the expected error message' |
752 | 752 |
|
753 | 753 |
def test_214d_export_settings_settings_directory_not_a_directory( |
... | ... |
@@ -773,7 +773,7 @@ class TestCLI: |
773 | 773 |
result.stderr_bytes |
774 | 774 |
), 'program did not print any error message' |
775 | 775 |
assert ( |
776 |
- b'cannot load config' in result.stderr_bytes |
|
776 |
+ b'Cannot load config' in result.stderr_bytes |
|
777 | 777 |
), 'program did not print the expected error message' |
778 | 778 |
|
779 | 779 |
def test_220_edit_notes_successfully(self, monkeypatch: Any) -> None: |
780 | 780 |