Skip to content

Commit

Permalink
fix: improved settings tui
Browse files Browse the repository at this point in the history
  • Loading branch information
robinvandernoord committed Jan 24, 2024
1 parent 01f8574 commit c0275b5
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 30 deletions.
93 changes: 69 additions & 24 deletions src/twofas/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@
from .__about__ import __version__
from ._security import keyring_manager
from ._types import TwoFactorAuthDetails
from .cli_settings import get_cli_setting, load_cli_settings, set_cli_setting
from .cli_settings import (
expand_path,
get_cli_setting,
load_cli_settings,
set_cli_setting,
)
from .cli_support import clear, exit_with_clear, generate_custom_style, state
from .core import TwoFactorStorage, load_services

Expand Down Expand Up @@ -40,9 +45,12 @@ def generate_all_totp(services: TwoFactorDetailStorage) -> None:


def generate_one_otp(services: TwoFactorDetailStorage) -> None:
while service_name := questionary.autocomplete(
"Choose a service", choices=services.keys(), style=generate_custom_style()
).ask():
while (
service_name := questionary.autocomplete(
"Choose a service", choices=services.keys(), style=generate_custom_style()
).ask()
or []
):
for service in services.find(service_name):
print_for_service(service)

Expand All @@ -53,9 +61,12 @@ def show_service_info(services: TwoFactorDetailStorage, about: str) -> None:


def show_service_info_interactive(services: TwoFactorDetailStorage) -> None:
while about := questionary.select(
"About which service?", choices=services.keys(), style=generate_custom_style()
).ask():
while (
about := questionary.select(
"About which service?", choices=services.keys(), style=generate_custom_style()
).ask()
or []
):
show_service_info(services, about)
if questionary.press_any_key_to_continue("Press 'Enter' to continue; Other keys to exit").ask() is None:
exit_with_clear(0)
Expand Down Expand Up @@ -93,20 +104,12 @@ def command_interactive(filename: str = None) -> None:
return show_service_info_interactive(services)
case "settings":
return command_settings(filename)
# manage files
# change specific settings
# default file - choose from list of files
case _:
exit_with_clear(0)


def default_2fas_file() -> str:
def add_2fas_file() -> str:
settings = state.settings
if settings.default_file:
return settings.default_file

elif settings.files:
return settings.files[0]

filename: str = questionary.path(
"Path to .2fas file?",
Expand All @@ -115,12 +118,29 @@ def default_2fas_file() -> str:
style=generate_custom_style(),
).ask()

set_cli_setting("default-file", filename)
settings.add_file(filename)
if filename is None:
exit_with_clear(0)

filename = expand_path(filename)

settings.add_file(filename)
return filename


def default_2fas_file() -> str:
settings = state.settings
if settings.default_file:
return settings.default_file

elif settings.files:
return settings.files[0]

filename = add_2fas_file()
set_cli_setting("default-file", filename)

return expand_path(filename)


def default_2fas_services() -> TwoFactorDetailStorage:
filename = default_2fas_file()
return prepare_to_generate(filename)
Expand Down Expand Up @@ -169,21 +189,39 @@ def set_default_file_interactive(filename: str) -> None:
use_shortcuts=True,
).ask()

if new_filename is None:
return command_settings(filename)

set_setting("default-file", new_filename)
prepare_to_generate(new_filename) # ask for passphrase

return command_settings(new_filename)


@clear
def command_manage_files(filename: str = None):
to_remove = questionary.checkbox(
"Which files do you want to remove?",
choices=state.settings.files or [],
style=generate_custom_style(),
).ask()
if to_remove is not None:
state.settings.remove_file(to_remove)

if filename:
return command_settings(filename)


@clear
def command_settings(filename: str) -> None:
rich.print(f"Active file: [blue]{filename}[/blue]")
action = questionary.select(
"What do you want to do?",
choices=[
questionary.Choice("Set default file", "set-default-file", shortcut_key="1"),
questionary.Choice("Manage files", "manage-files", shortcut_key="2"),
questionary.Choice("Back", "back", shortcut_key="3"),
questionary.Choice("Add file", "add-file", shortcut_key="2"),
questionary.Choice("Remove files", "remove-files", shortcut_key="3"),
questionary.Choice("Back", "back", shortcut_key="4"),
questionary.Choice("Exit", "exit", shortcut_key="0"),
],
use_shortcuts=True,
Expand All @@ -193,8 +231,11 @@ def command_settings(filename: str) -> None:
match action:
case "set-default-file":
set_default_file_interactive(filename)
case "manage-files":
print("todo: manage files")
case "add-file":
prepare_to_generate(add_2fas_file())
return command_settings(filename)
case "remove-files":
command_manage_files(filename)
case "back":
return command_interactive(filename)
case _:
Expand Down Expand Up @@ -247,6 +288,7 @@ def main(
self_update: bool = typer.Option(False, "--self-update", "-u"),
generate_all: bool = typer.Option(False, "--all", "-a"),
version: bool = typer.Option(False, "--version"),
remove: bool = typer.Option(False, "--remove", "-r"),
# flags:
verbose: bool = typer.Option(False, "--verbose", "-v"),
) -> None: # pragma: no cover
Expand Down Expand Up @@ -278,13 +320,17 @@ def main(
rich.print("[red]Err: can't work on multiple .2fas files![/red]", file=sys.stderr)
exit(1)

filename = file_args[0] if file_args else default_2fas_file()
filename = expand_path(file_args[0] if file_args else default_2fas_file())
settings.add_file(filename)

other_args = [_ for _ in args if not _.endswith(".2fas")]

if setting:
command_setting(args)
elif remove and file_args:
settings.remove_file(file_args[0])
elif remove:
command_manage_files()
elif info:
services = prepare_to_generate(filename)
show_service_info(services, about=info)
Expand All @@ -296,5 +342,4 @@ def main(
else:
command_interactive(filename)

# todo: something to --remove files from history
# todo: better --help info
31 changes: 28 additions & 3 deletions src/twofas/cli_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from typing import Any

import tomli_w
from configuraptor import TypedConfig, asdict
from configuraptor import TypedConfig, asdict, singleton
from configuraptor.core import convert_key

config = Path("~/.config").expanduser()
Expand All @@ -14,7 +14,15 @@
CONFIG_KEY = "tool.2fas"


class CliSettings(TypedConfig):
def expand_path(file: str | Path) -> str:
return str(Path(file).expanduser())


def expand_paths(paths: list[str]) -> list[str]:
return [expand_path(f) for f in paths]


class CliSettings(TypedConfig, singleton.Singleton):
files: list[str] | None
default_file: str | None
auto_verbose: bool = False
Expand All @@ -23,10 +31,27 @@ def add_file(self, filename: str | None, _config_file: str | Path = DEFAULT_SETT
if not filename:
return

filename = expand_path(filename)

files = self.files or []
if filename not in files:
files.append(filename)
set_cli_setting("files", files, _config_file)

set_cli_setting("files", expand_paths(files), _config_file)

self.files = expand_paths(files)

def remove_file(self, filename: str | list[str], _config_file: str | Path = DEFAULT_SETTINGS) -> None:
if isinstance(filename, str | Path):
filename = [filename]

filename = set(expand_paths(filename))

files = [_ for _ in (self.files or []) if _ not in filename]

set_cli_setting("files", expand_paths(files), _config_file)

self.files = expand_paths(files)


def load_cli_settings(input_file: str | Path = DEFAULT_SETTINGS, **overwrite: Any) -> CliSettings:
Expand Down
7 changes: 4 additions & 3 deletions tests/test_clisettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from src.twofas.cli_settings import get_cli_setting, load_cli_settings, set_cli_setting


# todo: deal with Singleton CLISettings

@pytest.fixture
def empty_temp_config():
with tempfile.NamedTemporaryFile(suffix=".toml") as f:
Expand Down Expand Up @@ -47,16 +49,15 @@ def test_overwrite_empty(empty_temp_config):
assert settings.default_file == "1"

settings.add_file("3", empty_temp_config)
settings.add_file("", empty_temp_config) # may NOT be written!
settings.add_file(None, empty_temp_config) # may NOT be written!
settings.add_file("", empty_temp_config) # may NOT be written!
settings.add_file(None, empty_temp_config) # may NOT be written!

assert "3" in settings.files

reloaded_settings = load_cli_settings(empty_temp_config)
assert reloaded_settings.files == ["1", "2", "3"]



def test_filled(filled_temp_config):
settings = load_cli_settings(filled_temp_config)
assert settings.files == ["a", "b"]
Expand Down

0 comments on commit c0275b5

Please sign in to comment.