Clean up error handling, naming, and README accuracy

- config.py: warn and fall back to defaults on invalid keybinding
  fields instead of raising an unhandled ValueError
- clipboard.py: replace check=True with manual returncode check for
  a friendly error message instead of a raw traceback
- __main__.py: rename __handle_* to _handle_* (double-underscore
  name mangling is meaningless at module level)
- rofi.py: check that rofi is installed before launching
- history.py: warn on corrupted history file instead of silently
  returning empty
- README: fix install path (cd bw-menu/python), add name to
  available keybinding fields
This commit is contained in:
Navid Sassan 2026-02-17 22:36:38 +01:00
parent 4d6e64668a
commit baa3146840
6 changed files with 24 additions and 9 deletions

View File

@ -12,7 +12,7 @@ cargo install rbw # requires rbw >= 1.14.0
# install bw-menu
git clone https://git.navidsassan.ch/navid.sassan/bw-menu.git
cd bw-menu
cd bw-menu/python
uv sync
```
@ -94,7 +94,7 @@ keybindings:
totp: Shift+Return
```
Each entry maps a vault field to a [rofi key name](https://davatorium.github.io/rofi/current/rofi-keys.5/). Available fields: `password`, `username`, `totp`.
Each entry maps a vault field to a [rofi key name](https://davatorium.github.io/rofi/current/rofi-keys.5/). Available fields: `password`, `username`, `totp`, `name`.
The **first** entry is bound to rofi's accept key (`kb-accept-entry`). Additional entries become custom keybindings (`kb-custom-1`, `kb-custom-2`, …).

View File

@ -9,12 +9,12 @@ def main():
args = parse_args()
if args.command == "select":
__handle_select(args)
_handle_select(args)
elif args.command == "history":
__handle_history(args)
_handle_history(args)
def __handle_select(args):
def _handle_select(args):
selector = create_selector(args.selector)
selection = selector.run(args)
if selection is None:
@ -29,7 +29,7 @@ def __handle_select(args):
clipboard.copy(value)
def __handle_history(args):
def _handle_history(args):
if args.history_command == "list":
for i, entry in enumerate(history.load()):
print(f"{i}: {format_entry(entry)}")

View File

@ -8,7 +8,10 @@ def copy(text: str):
if tool is None:
print("No clipboard tool found (install wl-copy or xclip)", file=sys.stderr)
sys.exit(5)
subprocess.run(tool, input=text, encoding="utf-8", check=True)
result = subprocess.run(tool, input=text, encoding="utf-8")
if result.returncode != 0:
print(f"Error copying to clipboard: {result.stderr}", file=sys.stderr)
sys.exit(5)
def __detect_tool() -> list[str] | None:

View File

@ -54,9 +54,12 @@ def load() -> Config:
keybindings = {str(k): str(v) for k, v in kb.items()}
invalid = {k for k in keybindings if k not in VALID_FIELDS}
if invalid:
raise ValueError(
f"Invalid keybinding field(s): {', '.join(sorted(invalid))}. Valid fields: {', '.join(sorted(VALID_FIELDS))}"
print(
f"Warning: invalid keybinding field(s): {', '.join(sorted(invalid))}. "
f"Valid fields: {', '.join(sorted(VALID_FIELDS))}. Using defaults",
file=sys.stderr,
)
return Config()
return Config(keybindings=keybindings)
except yaml.YAMLError:
print(

View File

@ -1,5 +1,6 @@
import json
import os
import sys
from pathlib import Path
from bw_menu.rbw import Entry
@ -21,6 +22,10 @@ def load() -> list[Entry]:
data = json.loads(path.read_text())
return [Entry(e["name"], e["folder"], e["username"]) for e in data["entries"]]
except (json.JSONDecodeError, KeyError):
print(
f"Warning: history file {path} could not be read, ignoring",
file=sys.stderr,
)
return []

View File

@ -1,4 +1,5 @@
import os
import shutil
import subprocess
import sys
from pathlib import Path
@ -84,6 +85,9 @@ class RofiSelector:
path.unlink(missing_ok=True)
def __launch(self, keybindings: dict[str, str]):
if not shutil.which("rofi"):
print("rofi is not installed", file=sys.stderr)
sys.exit(5)
script = str(Path(sys.argv[0]).resolve())
keys = list(keybindings.values())