Marco Ricci commited on 2025-02-07 16:01:14
Zeige 5 geänderte Dateien mit 106 Einfügungen und 6 Löschungen.
When deriving a passphrase, if the service has any notes, vault(1) prints the notes after the passphrase. However, akin to source code comments before the code in question, service notes may actually be better placed above the derived passphrase instead, for some types of notes. Support such placement via a new command-line option `--print-notes-before`; the default, vault(1)-compatible behavior can be explicitly selected via `--print-notes-after`.
... | ... |
@@ -7,7 +7,7 @@ derivepassphrase-vault – derive a passphrase using the vault derivation scheme |
7 | 7 |
## SYNOPSIS |
8 | 8 |
|
9 | 9 |
<pre> |
10 |
-<code><b>derivepassphrase vault</b> [--phrase | --key] [--length <var>n</var>] [--repeat <var>n</var>] [--lower <var>n</var>] [--upper <var>n</var>] [--number <var>n</var>] [--space <var>n</var>] [--dash <var>n</var>] [--symbol <var>n</var>] <var>SERVICE</var></code> |
|
10 |
+<code><b>derivepassphrase vault</b> [--phrase | --key] [--length <var>n</var>] [--repeat <var>n</var>] [--lower <var>n</var>] [--upper <var>n</var>] [--number <var>n</var>] [--space <var>n</var>] [--dash <var>n</var>] [--symbol <var>n</var>] [--print-notes-before | --print-notes-after] <var>SERVICE</var></code> |
|
11 | 11 |
<code><b>derivepassphrase vault</b> {--phrase | --key | … | --symbol <var>n</var>} … --config [--unset <var>setting</var> …] [--overwrite-existing | --merge-existing] [<var>SERVICE</var>]</code> |
12 | 12 |
<code><b>derivepassphrase vault</b> [--phrase | --key | … | --symbol <var>n</var>] … --config --notes [--unset <var>setting</var> …] [--overwrite-existing | --merge-existing] [--modern-editor-interface | --vault-legacy-editor-interface] <var>SERVICE</var></code> |
13 | 13 |
<code><b>derivepassphrase vault</b> {--delete <var>SERVICE</var> | --delete-globals | --clear}</code> |
... | ... |
@@ -174,6 +174,11 @@ The compatibility and extension options modify the behavior to enable additional |
174 | 174 |
|
175 | 175 |
(vault(1) behaves as if `--vault-legacy-editor-interface` were always given.) |
176 | 176 |
|
177 |
+<b>-</b><b>-print-notes-before</b> / <b>-</b><b>-print-notes-after</b> |
|
178 |
+: When deriving a passphrase, if the service has any service notes, print these notes before or after (<em>default</em>) the passphrase. |
|
179 |
+ |
|
180 |
+ (<i>vault</i>(1) behaves as if `--print-notes-after` were always given.) |
|
181 |
+ |
|
177 | 182 |
### Other Options |
178 | 183 |
|
179 | 184 |
<b>-</b><b>-debug</b> |
... | ... |
@@ -20,6 +20,7 @@ |
20 | 20 |
.Op Fl \-space Ar n |
21 | 21 |
.Op Fl \-dash Ar n |
22 | 22 |
.Op Fl \-symbol Ar n |
23 |
+.Op Fl \-print\-notes\-before | Fl \-print\-notes\-after |
|
23 | 24 |
.Ar SERVICE |
24 | 25 |
. |
25 | 26 |
.Nm derivepassphrase vault |
... | ... |
@@ -463,6 +464,18 @@ behaves as if |
463 | 464 |
.Fl \-vault\-legacy\-editor\-interface |
464 | 465 |
were always given.) |
465 | 466 |
. |
467 |
+.It Fl \-print\-notes\-before No "" / "" Fl \-print\-notes\-after |
|
468 |
+When deriving a passphrase, if the service has any service notes, |
|
469 |
+print these notes before or after |
|
470 |
+.Em ( default ) |
|
471 |
+the passphrase. |
|
472 |
+.Pp |
|
473 |
+. |
|
474 |
+.Xr ( vault 1 |
|
475 |
+behaves as if |
|
476 |
+.Fl \-print\-notes\-after |
|
477 |
+were always given.) |
|
478 |
+. |
|
466 | 479 |
.El |
467 | 480 |
. |
468 | 481 |
.Ss Other options |
... | ... |
@@ -1112,6 +1112,16 @@ class Label(enum.Enum): |
1112 | 1112 |
'or the vault-like legacy one (default)', |
1113 | 1113 |
) |
1114 | 1114 |
"""""" |
1115 |
+ DERIVEPASSPHRASE_VAULT_PRINT_NOTES_BEFORE_HELP_TEXT = commented( |
|
1116 |
+ 'The corresponding option is displayed as ' |
|
1117 |
+ '"--print-notes-before / --print-notes-after", so you may want to ' |
|
1118 |
+ 'hint that the default (after) is the second of those options.', |
|
1119 |
+ )( |
|
1120 |
+ 'Label :: Help text :: One-line description', |
|
1121 |
+ 'print the service notes (if any) before or after (default) ' |
|
1122 |
+ 'the existing configuration', |
|
1123 |
+ ) |
|
1124 |
+ """""" |
|
1115 | 1125 |
|
1116 | 1126 |
EXPORT_VAULT_FORMAT_METAVAR_FMT = commented( |
1117 | 1127 |
'', |
... | ... |
@@ -602,6 +602,15 @@ def derivepassphrase_export_vault( |
602 | 602 |
), |
603 | 603 |
cls=cli_machinery.CompatibilityOption, |
604 | 604 |
) |
605 |
+@click.option( |
|
606 |
+ '--print-notes-before/--print-notes-after', |
|
607 |
+ 'print_notes_before', |
|
608 |
+ default=False, |
|
609 |
+ help=_msg.TranslatedString( |
|
610 |
+ _msg.Label.DERIVEPASSPHRASE_VAULT_PRINT_NOTES_BEFORE_HELP_TEXT |
|
611 |
+ ), |
|
612 |
+ cls=cli_machinery.CompatibilityOption, |
|
613 |
+) |
|
605 | 614 |
@cli_machinery.version_option |
606 | 615 |
@cli_machinery.color_forcing_pseudo_option |
607 | 616 |
@cli_machinery.standard_logging_options |
... | ... |
@@ -639,6 +648,7 @@ def derivepassphrase_vault( # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915 |
639 | 648 |
unset_settings: Sequence[str] = (), |
640 | 649 |
export_as: Literal['json', 'sh'] = 'json', |
641 | 650 |
modern_editor_interface: bool = False, |
651 |
+ print_notes_before: bool = False, |
|
642 | 652 |
) -> None: |
643 | 653 |
"""Derive a passphrase using the vault(1) derivation scheme. |
644 | 654 |
|
... | ... |
@@ -738,6 +748,10 @@ def derivepassphrase_vault( # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915 |
738 | 748 |
whether editing notes uses a modern editor interface |
739 | 749 |
(supporting comments and aborting) or a vault(1)-compatible |
740 | 750 |
legacy editor interface (WYSIWYG notes contents). |
751 |
+ print_notes_before: |
|
752 |
+ Command-line arguments `--print-notes-before` (True) and |
|
753 |
+ `--print-notes-after` (False). Controls whether the service |
|
754 |
+ notes (if any) are printed before the passphrase, or after. |
|
741 | 755 |
|
742 | 756 |
""" # noqa: DOC501 |
743 | 757 |
logger = logging.getLogger(PROG_NAME) |
... | ... |
@@ -1510,13 +1524,13 @@ def derivepassphrase_vault( # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915 |
1510 | 1524 |
) |
1511 | 1525 |
raise click.UsageError(str(err_msg)) |
1512 | 1526 |
kwargs.pop('key', '') |
1513 |
- service_notes = ( |
|
1514 |
- f'\n{settings["notes"]}\n\n' if 'notes' in settings else '' |
|
1515 |
- ) |
|
1527 |
+ service_notes = settings.get('notes', '').strip() |
|
1516 | 1528 |
result = vault.Vault(**kwargs).generate(service) |
1517 |
- if service_notes.strip(): |
|
1518 |
- click.echo(service_notes, err=True, color=ctx.color) |
|
1529 |
+ if print_notes_before and service_notes.strip(): |
|
1530 |
+ click.echo(f'{service_notes}\n', err=True, color=ctx.color) |
|
1519 | 1531 |
click.echo(result.decode('ASCII'), color=ctx.color) |
1532 |
+ if not print_notes_before and service_notes.strip(): |
|
1533 |
+ click.echo(f'\n{service_notes}\n', err=True, color=ctx.color) |
|
1520 | 1534 |
|
1521 | 1535 |
|
1522 | 1536 |
if __name__ == '__main__': |
... | ... |
@@ -641,6 +641,8 @@ class Parametrize(types.SimpleNamespace): |
641 | 641 |
'--export-as', |
642 | 642 |
'--modern-editor-interface', |
643 | 643 |
'--vault-legacy-editor-interface', |
644 |
+ '--print-notes-before', |
|
645 |
+ '--print-notes-after', |
|
644 | 646 |
}), |
645 | 647 |
id='derivepassphrase-vault', |
646 | 648 |
), |
... | ... |
@@ -2470,6 +2472,62 @@ class TestCLI: |
2470 | 2472 |
'expected error exit and known error message' |
2471 | 2473 |
) |
2472 | 2474 |
|
2475 |
+ @pytest.mark.parametrize( |
|
2476 |
+ ['notes_placement', 'placement_args'], |
|
2477 |
+ [ |
|
2478 |
+ pytest.param('after', ['--print-notes-after'], id='after'), |
|
2479 |
+ pytest.param('before', ['--print-notes-before'], id='before'), |
|
2480 |
+ ], |
|
2481 |
+ ) |
|
2482 |
+ @hypothesis.given( |
|
2483 |
+ notes=strategies.text( |
|
2484 |
+ strategies.characters( |
|
2485 |
+ min_codepoint=32, max_codepoint=126, include_characters='\n' |
|
2486 |
+ ), |
|
2487 |
+ min_size=1, |
|
2488 |
+ max_size=512, |
|
2489 |
+ ).filter(str.strip), |
|
2490 |
+ ) |
|
2491 |
+ def test_215_notes_placement( |
|
2492 |
+ self, |
|
2493 |
+ notes_placement: Literal['before', 'after'], |
|
2494 |
+ placement_args: list[str], |
|
2495 |
+ notes: str, |
|
2496 |
+ ) -> None: |
|
2497 |
+ maybe_notes = {'notes': notes.strip()} if notes.strip() else {} |
|
2498 |
+ vault_config = { |
|
2499 |
+ 'global': {'phrase': DUMMY_PASSPHRASE}, |
|
2500 |
+ 'services': { |
|
2501 |
+ DUMMY_SERVICE: {**maybe_notes, **DUMMY_CONFIG_SETTINGS} |
|
2502 |
+ }, |
|
2503 |
+ } |
|
2504 |
+ result_phrase = DUMMY_RESULT_PASSPHRASE.decode('ascii') |
|
2505 |
+ expected = ( |
|
2506 |
+ f'{notes}\n\n{result_phrase}\n' |
|
2507 |
+ if notes_placement == 'before' |
|
2508 |
+ else f'{result_phrase}\n\n{notes}\n\n' |
|
2509 |
+ ) |
|
2510 |
+ runner = click.testing.CliRunner(mix_stderr=True) |
|
2511 |
+ # TODO(the-13th-letter): Rewrite using parenthesized |
|
2512 |
+ # with-statements. |
|
2513 |
+ # https://the13thletter.info/derivepassphrase/latest/pycompatibility/#after-eol-py3.9 |
|
2514 |
+ with contextlib.ExitStack() as stack: |
|
2515 |
+ monkeypatch = stack.enter_context(pytest.MonkeyPatch.context()) |
|
2516 |
+ stack.enter_context( |
|
2517 |
+ tests.isolated_vault_config( |
|
2518 |
+ monkeypatch=monkeypatch, |
|
2519 |
+ runner=runner, |
|
2520 |
+ vault_config=vault_config, |
|
2521 |
+ ) |
|
2522 |
+ ) |
|
2523 |
+ result_ = runner.invoke( |
|
2524 |
+ cli.derivepassphrase_vault, |
|
2525 |
+ [*placement_args, '--', DUMMY_SERVICE], |
|
2526 |
+ catch_exceptions=False, |
|
2527 |
+ ) |
|
2528 |
+ result = tests.ReadableResult.parse(result_) |
|
2529 |
+ assert result.clean_exit(output=expected), 'expected clean exit' |
|
2530 |
+ |
|
2473 | 2531 |
@pytest.mark.parametrize( |
2474 | 2532 |
'modern_editor_interface', [False, True], ids=['legacy', 'modern'] |
2475 | 2533 |
) |
2476 | 2534 |