diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 895437a8..0365bbe5 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -289,7 +289,10 @@ class Journal: def ask_delete(entry): return yesno( - f"Delete entry '{entry.pprint(short=True)}'?", + Message( + MsgText.DeleteEntryQuestion, + params={"entry_title": entry.pprint(short=True)}, + ), default=False, ) diff --git a/jrnl/install.py b/jrnl/install.py index 7f8b020e..ee004c53 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -124,10 +124,7 @@ def install(): pass # Encrypt it? - encrypt = yesno( - "Do you want to encrypt your journal? You can always change this later", - default=False, - ) + encrypt = yesno(Message(MsgText.EncryptJournalQuestion), default=False) if encrypt: default_config["encrypt"] = True print_msg(Message(MsgText.JournalEncrypted, MsgStyle.NORMAL)) diff --git a/jrnl/messages.py b/jrnl/messages.py index e2658ec3..46a81f30 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -53,6 +53,10 @@ class MsgStyle(Enum): "decoration": MsgDecoration.BRACKET, "color": _MsgColor("white"), } + PROMPT = { + "decoration": MsgDecoration.NONE, + "color": _MsgColor("white"), + } TITLE = { "decoration": MsgDecoration.BOX, "color": _MsgColor("cyan"), @@ -103,6 +107,19 @@ class MsgText(Enum): AllDoneUpgrade = "We're all done here and you can start enjoying jrnl 2" + # --- Prompts --- # + DeleteEntryQuestion = "Delete entry '{entry_title}'?" + EncryptJournalQuestion = """ + Do you want to encrypt your journal? (You can always change this later) + """ + YesOrNoPromptDefaultYes = "[Y/n]" + YesOrNoPromptDefaultNo = "[y/N]" + + # these should be lowercase, if possible in language + # "lowercase" means whatever `.lower()` returns + OneCharacterYes = "y" + OneCharacterNo = "n" + # --- Exceptions ---# UncaughtException = """ {name} diff --git a/jrnl/output.py b/jrnl/output.py index 04e209db..02d0056a 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -4,6 +4,7 @@ import logging import textwrap +from typing import Union from rich.text import Text from rich.console import Console @@ -38,20 +39,22 @@ def list_journals(configuration): return result -def print_msg(msg: Message) -> None: - print_msgs([msg], style=msg.style) +def print_msg(msg: Message, is_prompt: bool = False) -> Union[None, str]: + return print_msgs([msg], style=msg.style, is_prompt=is_prompt) def print_msgs( msgs: list[Message], delimiter: str = "\n", style: MsgStyle = MsgStyle.NORMAL, -) -> None: + is_prompt: bool = False, +) -> Union[None, str]: # Same as print_msg, but for a list text = Text("") decoration_callback = style.decoration.callback args = style.decoration.args prepend_newline = False + append_space = False for msg in msgs: args = _add_extra_style_args_if_needed(args, msg=msg) @@ -59,20 +62,27 @@ def print_msgs( if _needs_prepended_newline(msg): prepend_newline = True - m = format_msg(msg) + if _needs_appended_space(msg): + append_space = True + + m = format_msg_text(msg) m.append(delimiter) text.append(m) text.rstrip() + if append_space: + text.append(" ") + # Always print messages to stderr console = Console(stderr=True) + decorated_text = decoration_callback(text, **args) - console.print( - decoration_callback(text, **args), - new_line_start=prepend_newline, - ) + if is_prompt: + return str(console.input(prompt=decorated_text)) + else: + console.print(decorated_text, new_line_start=prepend_newline) def _add_extra_style_args_if_needed(args, msg): @@ -85,10 +95,18 @@ def _needs_prepended_newline(msg: Message) -> bool: return is_keyboard_int(msg) +def _needs_appended_space(msg: Message) -> bool: + return is_prompt(msg) + + +def is_prompt(msg: Message) -> bool: + return msg.style == MsgStyle.PROMPT + + def is_keyboard_int(msg: Message) -> bool: return msg.text == MsgText.KeyboardInterruptMsg -def format_msg(msg: Message) -> Text: +def format_msg_text(msg: Message) -> Text: text = textwrap.dedent(msg.text.value.format(**msg.params)).strip() return Text(text) diff --git a/jrnl/prompt.py b/jrnl/prompt.py index 3eae169c..fcf179e9 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -7,6 +7,7 @@ from jrnl.messages import Message from jrnl.messages import MsgText from jrnl.messages import MsgStyle from jrnl.output import print_msg +from jrnl.output import print_msgs def create_password(journal_name: str) -> str: @@ -20,14 +21,32 @@ def create_password(journal_name: str) -> str: print_msg(Message(MsgText.PasswordDidNotMatch, MsgStyle.ERROR)) - if yesno(str(MsgText.PasswordStoreInKeychain), default=True): + if yesno(Message(MsgText.PasswordStoreInKeychain), default=True): from .EncryptedJournal import set_keychain set_keychain(journal_name, pw) return pw -def yesno(prompt: str, default: bool = True): - prompt = f"{prompt.strip()} {'[Y/n]' if default else '[y/N]'} " - response = input(prompt) - return {"y": True, "n": False}.get(response.lower().strip(), default) +def yesno(prompt: Message, default: bool = True) -> bool: + response = print_msgs( + [ + prompt, + Message( + MsgText.YesOrNoPromptDefaultYes + if default + else MsgText.YesOrNoPromptDefaultNo + ), + ], + style=MsgStyle.PROMPT, + delimiter=" ", + is_prompt=True, + ) + + answers = { + str(MsgText.OneCharacterYes): True, + str(MsgText.OneCharacterNo): False, + } + + # Does using `lower()` work in all languages? + return answers.get(str(response).lower().strip(), default) diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 15125ab4..2eaebee8 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -115,7 +115,7 @@ def upgrade_jrnl(config_path): **kwargs, ) - cont = yesno("\nContinue upgrading jrnl?", default=False) + cont = yesno(Message(MsgText.ContinueUpgrade), default=False) if not cont: raise JrnlException(Message(MsgText.UpgradeAborted), MsgStyle.WARNING)