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 |