From e130f26cf1a6018feceff928c80c951842cd5878 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 19 Mar 2022 13:14:39 -0700 Subject: [PATCH 01/66] fix missed statement from last PR --- jrnl/jrnl.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index f6715f76d..8a9d2d7ad 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -136,7 +136,9 @@ def write_mode(args, config, journal, **kwargs): if not raw or raw.isspace(): logging.error("Write mode: couldn't get raw text or entry was empty") - raise JrnlException(Message(MsgText.NoTextReceived, MsgType.ERROR)) + raise JrnlException( + Message(MsgText.NoTextReceived, MsgType.ERROR) + ) logging.debug( 'Write mode: appending raw text to journal "%s": %s', args.journal_name, raw From f1bd01594e02918fec0e60808b95c4c26be54a44 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 19 Mar 2022 13:20:22 -0700 Subject: [PATCH 02/66] replace print statement for adding an entry to a journal --- jrnl/jrnl.py | 3 ++- jrnl/messages.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 8a9d2d7ad..24d770868 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -14,6 +14,7 @@ from .editor import get_text_from_stdin from . import time from .override import apply_overrides +from jrnl.output import print_msg from jrnl.exception import JrnlException from jrnl.messages import Message @@ -144,7 +145,7 @@ def write_mode(args, config, journal, **kwargs): 'Write mode: appending raw text to journal "%s": %s', args.journal_name, raw ) journal.new_entry(raw) - print(f"[Entry added to {args.journal_name} journal]", file=sys.stderr) + print_msg( Message(MsgText.JournalEntryAdded, MsgType.NORMAL, {"journal_name": args.journal_name})) journal.write() logging.debug("Write mode: completed journal.write()", args.journal_name, raw) diff --git a/jrnl/messages.py b/jrnl/messages.py index 486841957..336fb33f4 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -71,6 +71,7 @@ def __str__(self) -> str: # --- Journal status ---# JournalNotSaved = "Entry NOT saved to journal" + JournalEntryAdded = "Entry added to {journal_name} journal" # --- Editor ---# WritingEntryStart = """ From 4b9fe0cd4f54e56be26c39fa0fee28761d683bdd Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 16 Apr 2022 15:19:38 -0700 Subject: [PATCH 03/66] WIP --- jrnl/cli.py | 8 +++++- jrnl/jrnl.py | 44 +++++++++++++++++++++------------ jrnl/messages.py | 11 ++++++++- jrnl/output.py | 63 ++++++++++++++++++++++++++++++++++++++++++------ pyproject.toml | 1 + 5 files changed, 101 insertions(+), 26 deletions(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index cd33f2ec7..cd2275efb 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -45,7 +45,13 @@ def cli(manual_args=None): except KeyboardInterrupt: status_code = 1 - print_msg("\nKeyboardInterrupt", "\nAborted by user", msg=Message.ERROR) + + print_msg( + Message( + MsgText.KeyboardInterruptMsg, + MsgType.ERROR, + ) + ) except Exception as e: # uncaught exception diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 24d770868..f93f4deb6 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -15,6 +15,7 @@ from . import time from .override import apply_overrides from jrnl.output import print_msg +from jrnl.output import print_msgs from jrnl.exception import JrnlException from jrnl.messages import Message @@ -145,7 +146,13 @@ def write_mode(args, config, journal, **kwargs): 'Write mode: appending raw text to journal "%s": %s', args.journal_name, raw ) journal.new_entry(raw) - print_msg( Message(MsgText.JournalEntryAdded, MsgType.NORMAL, {"journal_name": args.journal_name})) + print_msg( + Message( + MsgText.JournalEntryAdded, + MsgType.NORMAL, + {"journal_name": args.journal_name}, + ) + ) journal.write() logging.debug("Write mode: completed journal.write()", args.journal_name, raw) @@ -279,35 +286,40 @@ def _print_edited_summary(journal, old_stats, **kwargs): "deleted": old_stats["count"] - len(journal), "modified": len([e for e in journal.entries if e.modified]), } - - prompts = [] + stats["modified"] -= stats["added"] + msgs = [] if stats["added"] > 0: - prompts.append(f"{stats['added']} {_pluralize_entry(stats['added'])} added") - stats["modified"] -= stats["added"] + my_msg = ( + MsgText.JournalCountAddedSingular + if stats["added"] == 1 + else MsgText.JournalCountAddedPlural + ) + msgs.append(Message(my_msg, MsgType.NORMAL, {"num": stats["added"]})) if stats["deleted"] > 0: - prompts.append( - f"{stats['deleted']} {_pluralize_entry(stats['deleted'])} deleted" + my_msg = ( + MsgText.JournalCountDeletedSingular + if stats["added"] == 1 + else MsgText.JournalCountDeletedPlural ) + msgs.append(Message(my_msg, MsgType.NORMAL, {"num": stats["deleted"]})) - if stats["modified"]: - prompts.append( - f"{stats['modified']} {_pluralize_entry(stats['modified'])} modified" + if stats["modified"] > 0: + my_msg = ( + MsgText.JournalCountModifiedSingular + if stats["added"] == 1 + else MsgText.JournalCountModifiedPlural ) + msgs.append(Message(my_msg, MsgType.NORMAL, {"num": stats["modified"]})) - if prompts: - print(f"[{', '.join(prompts).capitalize()}]", file=sys.stderr) + print_msgs(msgs) def _get_predit_stats(journal): return {"count": len(journal)} -def _pluralize_entry(num): - return "entry" if num == 1 else "entries" - - def _delete_search_results(journal, old_entries, **kwargs): if not journal.entries: raise JrnlException(Message(MsgText.NothingToDelete, MsgType.ERROR)) diff --git a/jrnl/messages.py b/jrnl/messages.py index 336fb33f4..6d3ea094a 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -11,7 +11,7 @@ class _MsgColor(NamedTuple): class MsgType(Enum): TITLE = _MsgColor("cyan") - NORMAL = _MsgColor("white") + NORMAL = _MsgColor("blue") WARNING = _MsgColor("yellow") ERROR = _MsgColor("red") @@ -34,6 +34,7 @@ def __str__(self) -> str: """ ConfigDirectoryIsFile = """ + Problem with config file! The path to your jrnl configuration directory is a file, not a directory: {config_directory_path} @@ -73,6 +74,14 @@ def __str__(self) -> str: JournalNotSaved = "Entry NOT saved to journal" JournalEntryAdded = "Entry added to {journal_name} journal" + JournalCountAddedSingular = "{num} entry added" + JournalCountModifiedSingular = "{num} entry modified" + JournalCountDeletedSingular = "{num} entry deleted" + + JournalCountAddedPlural = "{num} entries added" + JournalCountModifiedPlural = "{num} entries modified" + JournalCountDeletedPlural = "{num} entries deleted" + # --- Editor ---# WritingEntryStart = """ Writing Entry diff --git a/jrnl/output.py b/jrnl/output.py index f31a02e26..b32e4ddaa 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -4,11 +4,23 @@ import logging import sys import textwrap +from typing import Tuple + +from rich import print +import rich +from rich.panel import Panel +from rich.padding import Padding +from rich.text import Text +from rich.measure import Measurement +from rich.console import Group +from rich import box from jrnl.color import colorize from jrnl.color import RESET_COLOR from jrnl.color import WARNING_COLOR from jrnl.messages import Message +from jrnl.messages import MsgType +from jrnl.messages import MsgText def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs): @@ -38,14 +50,49 @@ def list_journals(configuration): return result -def print_msg(msg: Message): - msg_text = textwrap.dedent(msg.text.value.format(**msg.params)).strip().split("\n") +def print_msg(msg: Message) -> None: + print_msgs([msg]) + + +def print_msgs(msgs: list[Message], delimiter: str = "\n") -> None: + # Same as print_msg, but for a list + text = Text("") + + kwargs = { + "expand": False, + "border_style": None, + "padding": (0,2), + "title_align": "left", + "box": box.HEAVY + } + + for msg in msgs: + kwargs["border_style"] = msg.type.color + if msg.type == MsgType.ERROR: + kwargs["title"] = "Error" - longest_string = len(max(msg_text, key=len)) - msg_text = [f"[ {line:<{longest_string}} ]" for line in msg_text] + if is_keyboard_int(msg): + print() - # colorize can't be called until after the lines are padded, - # because python gets confused by the ansi color codes - msg_text[0] = f"[{colorize(msg_text[0][1:-1], msg.type.color)}]" + m = format_msg(msg) + m.append(delimiter) + text.append(m) - print("\n".join(msg_text), file=sys.stderr) + text.rstrip() + print(Panel(text, **kwargs)) + + +def is_keyboard_int(msg: Message) -> bool: + return msg.text == MsgText.KeyboardInterruptMsg + +def format_msg(msg: Message) -> Text: + text = ( + textwrap.dedent(msg.text.value.format(**msg.params)) + .strip() + # .splitlines(keepends=True) + ) + result = Text(text) + # result = Text(text[0]) + # result.stylize(msg.type.color) + # result.append("".join(text[1:])) + return result diff --git a/pyproject.toml b/pyproject.toml index 888617407..9695b9f62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ tzlocal = ">2.0, <3.0" # https://github.com/regebro/tzlocal/blob/master/CHANGE pytest = { version = ">=6.2", optional = true } pytest-bdd = { version = ">=4.0.1", optional = true } toml = { version = ">=0.10", optional = true } +rich = "^12.2.0" [tool.poetry.dev-dependencies] mkdocs = ">=1.0" From 253b80740a1b0e9b0764d6dbdb37d4d12464997d Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 16 Apr 2022 15:38:34 -0700 Subject: [PATCH 04/66] clean up linting and format --- jrnl/jrnl.py | 4 ++-- jrnl/output.py | 14 ++++---------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index f93f4deb6..2902c523c 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -300,7 +300,7 @@ def _print_edited_summary(journal, old_stats, **kwargs): if stats["deleted"] > 0: my_msg = ( MsgText.JournalCountDeletedSingular - if stats["added"] == 1 + if stats["deleted"] == 1 else MsgText.JournalCountDeletedPlural ) msgs.append(Message(my_msg, MsgType.NORMAL, {"num": stats["deleted"]})) @@ -308,7 +308,7 @@ def _print_edited_summary(journal, old_stats, **kwargs): if stats["modified"] > 0: my_msg = ( MsgText.JournalCountModifiedSingular - if stats["added"] == 1 + if stats["modified"] == 1 else MsgText.JournalCountModifiedPlural ) msgs.append(Message(my_msg, MsgType.NORMAL, {"num": stats["modified"]})) diff --git a/jrnl/output.py b/jrnl/output.py index b32e4ddaa..c1ebfa40f 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -4,18 +4,12 @@ import logging import sys import textwrap -from typing import Tuple from rich import print -import rich from rich.panel import Panel -from rich.padding import Padding from rich.text import Text -from rich.measure import Measurement -from rich.console import Group from rich import box -from jrnl.color import colorize from jrnl.color import RESET_COLOR from jrnl.color import WARNING_COLOR from jrnl.messages import Message @@ -61,9 +55,9 @@ def print_msgs(msgs: list[Message], delimiter: str = "\n") -> None: kwargs = { "expand": False, "border_style": None, - "padding": (0,2), + "padding": (0, 2), "title_align": "left", - "box": box.HEAVY + "box": box.HEAVY, } for msg in msgs: @@ -85,10 +79,10 @@ def print_msgs(msgs: list[Message], delimiter: str = "\n") -> None: def is_keyboard_int(msg: Message) -> bool: return msg.text == MsgText.KeyboardInterruptMsg + def format_msg(msg: Message) -> Text: text = ( - textwrap.dedent(msg.text.value.format(**msg.params)) - .strip() + textwrap.dedent(msg.text.value.format(**msg.params)).strip() # .splitlines(keepends=True) ) result = Text(text) From f142c912e26d3ff1609e0f007b3cf3ee66196cef Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 16 Apr 2022 15:52:58 -0700 Subject: [PATCH 05/66] change print statement over to new print_msg function --- jrnl/messages.py | 5 +++++ jrnl/plugins/markdown_exporter.py | 19 +++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 6d3ea094a..335c7b547 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -140,6 +140,11 @@ def __str__(self) -> str: No entries to delete, because the search returned no results """ + # --- Formats --- # + HeadingsPastH6 = """ + Headings increased past H6 on export - {date} {title} + """ + class Message(NamedTuple): text: MsgText diff --git a/jrnl/plugins/markdown_exporter.py b/jrnl/plugins/markdown_exporter.py index 11f748b68..ae10bf3fb 100644 --- a/jrnl/plugins/markdown_exporter.py +++ b/jrnl/plugins/markdown_exporter.py @@ -4,13 +4,14 @@ import os import re -import sys - -from jrnl.color import RESET_COLOR -from jrnl.color import WARNING_COLOR from .text_exporter import TextExporter +from jrnl.output import print_msg +from jrnl.messages import Message +from jrnl.messages import MsgText +from jrnl.messages import MsgType + class MarkdownExporter(TextExporter): """This Exporter can convert entries and journals into Markdown.""" @@ -63,10 +64,12 @@ def export_entry(cls, entry, to_multifile=True): newbody = newbody + os.linesep if warn_on_heading_level is True: - print( - f"{WARNING_COLOR}WARNING{RESET_COLOR}: " - f"Headings increased past H6 on export - {date_str} {entry.title}", - file=sys.stderr, + print_msg( + Message( + MsgText.HeadingsPastH6, + MsgType.WARNING, + {"date": date_str, "title": entry.title}, + ) ) return f"{heading} {date_str} {entry.title}\n{newbody} " From 0fc8bcfbc229d5ecb8f3e59ff959c6e4c5bfb4ea Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 16 Apr 2022 16:16:48 -0700 Subject: [PATCH 06/66] make print_msg always print to stderr --- jrnl/output.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jrnl/output.py b/jrnl/output.py index c1ebfa40f..e1e5e797d 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -9,6 +9,7 @@ from rich.panel import Panel from rich.text import Text from rich import box +from rich.console import Console from jrnl.color import RESET_COLOR from jrnl.color import WARNING_COLOR @@ -73,7 +74,8 @@ def print_msgs(msgs: list[Message], delimiter: str = "\n") -> None: text.append(m) text.rstrip() - print(Panel(text, **kwargs)) + + Console(stderr=True).print(Panel(text, **kwargs)) def is_keyboard_int(msg: Message) -> bool: From 42373760ffdb0bba804407fffe1a373c6c071e96 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 16 Apr 2022 16:17:15 -0700 Subject: [PATCH 07/66] change print statement over to new print_msg function --- jrnl/Journal.py | 16 +++++++++++++--- jrnl/messages.py | 5 +++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index bf446a927..f96db88b9 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -12,6 +12,11 @@ from . import time from .prompt import yesno +from jrnl.output import print_msg +from jrnl.messages import Message +from jrnl.messages import MsgText +from jrnl.messages import MsgType + class Tag: def __init__(self, name, count=0): @@ -408,9 +413,14 @@ def open_journal(journal_name, config, legacy=False): if os.path.isdir(config["journal"]): if config["encrypt"]: - print( - "Warning: This journal's config has 'encrypt' set to true, but this type of journal can't be encrypted.", - file=sys.stderr, + print_msg( + Message( + MsgText.ConfigEncryptedForUnencryptableJournalType, + MsgType.WARNING, + { + "journal_name": journal_name, + }, + ) ) if config["journal"].strip("/").endswith(".dayone") or "entries" in os.listdir( diff --git a/jrnl/messages.py b/jrnl/messages.py index 335c7b547..7d815fe89 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -60,6 +60,11 @@ def __str__(self) -> str: this journal to the new journal, then encrypt the new journal. """ + ConfigEncryptedForUnencryptableJournalType = """ + The config for journal "{journal_name}" has 'encrypt' set to true, but this type + of journal can't be encrypted. Please fix your config file. + """ + KeyboardInterruptMsg = "Aborted by user" CantReadTemplate = """ From c16aa42d6c3327fc79dc505e1a63453db2cc6cdc Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 03:24:37 -0700 Subject: [PATCH 08/66] update importer to use new message function --- jrnl/messages.py | 5 +++++ jrnl/plugins/jrnl_importer.py | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 7d815fe89..639b11991 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -150,6 +150,11 @@ def __str__(self) -> str: Headings increased past H6 on export - {date} {title} """ + # --- Import --- # + ImportSummary = """ + {count} imported to {journal_name} journal + """ + class Message(NamedTuple): text: MsgText diff --git a/jrnl/plugins/jrnl_importer.py b/jrnl/plugins/jrnl_importer.py index 54dd2ab83..d820aa8e9 100644 --- a/jrnl/plugins/jrnl_importer.py +++ b/jrnl/plugins/jrnl_importer.py @@ -8,6 +8,7 @@ from jrnl.messages import Message from jrnl.messages import MsgText from jrnl.messages import MsgType +from jrnl.output import print_msg class JRNLImporter: @@ -34,8 +35,11 @@ def import_(journal, input=None): journal.import_(other_journal_txt) new_cnt = len(journal.entries) - print( - "[{} imported to {} journal]".format(new_cnt - old_cnt, journal.name), - file=sys.stderr, - ) journal.write() + print_msg( + Message( + MsgText.ImportSummary, + MsgType.NORMAL, + {"count": new_cnt - old_cnt, "journal_name": journal.name}, + ) + ) From 7ded211ff8a08d57d28d6b85c32125b4118711d2 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 03:37:35 -0700 Subject: [PATCH 09/66] update yaml format to use new message function --- jrnl/messages.py | 4 ++++ jrnl/plugins/yaml_exporter.py | 15 ++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 639b11991..c91f789ac 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -150,6 +150,10 @@ def __str__(self) -> str: Headings increased past H6 on export - {date} {title} """ + YamlMustBeDirectory = """ + YAML export must be to a directory, not a single file + """ + # --- Import --- # ImportSummary = """ {count} imported to {journal_name} journal diff --git a/jrnl/plugins/yaml_exporter.py b/jrnl/plugins/yaml_exporter.py index 8983d2e4c..0f8202079 100644 --- a/jrnl/plugins/yaml_exporter.py +++ b/jrnl/plugins/yaml_exporter.py @@ -6,12 +6,13 @@ import re import sys -from jrnl.color import ERROR_COLOR -from jrnl.color import RESET_COLOR -from jrnl.color import WARNING_COLOR - from .text_exporter import TextExporter +from jrnl.exception import JrnlException +from jrnl.messages import Message +from jrnl.messages import MsgText +from jrnl.messages import MsgType + class YAMLExporter(TextExporter): """This Exporter can convert entries and journals into Markdown formatted text with YAML front matter.""" @@ -129,8 +130,4 @@ def export_entry(cls, entry, to_multifile=True): @classmethod def export_journal(cls, journal): """Returns an error, as YAML export requires a directory as a target.""" - raise RuntimeError( - "{}ERROR{}: YAML export must be to individual files. Please specify a directory to export to.".format( - ERROR_COLOR, RESET_COLOR - ) - ) + raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgType.ERROR)) From af220be35fa4904a9530e15f03ff3a351fa2e0c6 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 03:37:49 -0700 Subject: [PATCH 10/66] code cleanup --- jrnl/output.py | 12 ++---------- jrnl/plugins/jrnl_importer.py | 5 ++++- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/jrnl/output.py b/jrnl/output.py index e1e5e797d..1a8e7fb1f 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -19,7 +19,6 @@ def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs): - warning_msg = f""" The command {old_cmd} is deprecated and will be removed from jrnl soon. Please use {new_cmd} instead. @@ -83,12 +82,5 @@ def is_keyboard_int(msg: Message) -> bool: def format_msg(msg: Message) -> Text: - text = ( - textwrap.dedent(msg.text.value.format(**msg.params)).strip() - # .splitlines(keepends=True) - ) - result = Text(text) - # result = Text(text[0]) - # result.stylize(msg.type.color) - # result.append("".join(text[1:])) - return result + text = textwrap.dedent(msg.text.value.format(**msg.params)).strip() + return Text(text) diff --git a/jrnl/plugins/jrnl_importer.py b/jrnl/plugins/jrnl_importer.py index d820aa8e9..61d0e0f13 100644 --- a/jrnl/plugins/jrnl_importer.py +++ b/jrnl/plugins/jrnl_importer.py @@ -40,6 +40,9 @@ def import_(journal, input=None): Message( MsgText.ImportSummary, MsgType.NORMAL, - {"count": new_cnt - old_cnt, "journal_name": journal.name}, + { + "count": new_cnt - old_cnt, + "journal_name": journal.name, + }, ) ) From 7eb385d0bb1c930fad2da295e60cfdf2cb29f1e9 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 03:41:37 -0700 Subject: [PATCH 11/66] update yaml format to use new message function --- jrnl/plugins/yaml_exporter.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/jrnl/plugins/yaml_exporter.py b/jrnl/plugins/yaml_exporter.py index 0f8202079..b22b5743b 100644 --- a/jrnl/plugins/yaml_exporter.py +++ b/jrnl/plugins/yaml_exporter.py @@ -12,6 +12,7 @@ from jrnl.messages import Message from jrnl.messages import MsgText from jrnl.messages import MsgType +from jrnl.output import print_msg class YAMLExporter(TextExporter): @@ -79,11 +80,12 @@ def export_entry(cls, entry, to_multifile=True): spacebody = spacebody + "\t" + line if warn_on_heading_level is True: - print( - "{}WARNING{}: Headings increased past H6 on export - {} {}".format( - WARNING_COLOR, RESET_COLOR, date_str, entry.title - ), - file=sys.stderr, + print_msg( + Message( + MsgText.HeadingsPastH6, + MsgType.WARNING, + {"date": date_str, "title": entry.title}, + ) ) dayone_attributes = "" From 6945d6a5355cc0851e68fe85f0638e59b62b40c2 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 03:43:29 -0700 Subject: [PATCH 12/66] update yaml format to use new exception handling --- jrnl/plugins/yaml_exporter.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jrnl/plugins/yaml_exporter.py b/jrnl/plugins/yaml_exporter.py index b22b5743b..f08e37512 100644 --- a/jrnl/plugins/yaml_exporter.py +++ b/jrnl/plugins/yaml_exporter.py @@ -25,10 +25,7 @@ class YAMLExporter(TextExporter): def export_entry(cls, entry, to_multifile=True): """Returns a markdown representation of a single entry, with YAML front matter.""" if to_multifile is False: - raise RuntimeError( - f"{ERROR_COLOR}ERROR{RESET_COLOR}: YAML export must be to individual files. Please \ - specify a directory to export to." - ) + raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgType.ERROR)) date_str = entry.date.strftime(entry.journal.config["timeformat"]) body_wrapper = "\n" if entry.body else "" From 7690594aacb6713f8d86f8a5e23234460fd66275 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 03:50:03 -0700 Subject: [PATCH 13/66] update Journal class to use new message function --- jrnl/Journal.py | 19 +++++++++++++++++-- jrnl/messages.py | 3 +++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index f96db88b9..5965fddea 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -87,9 +87,24 @@ def open(self, filename=None): if not os.path.exists(filename): if not os.path.isdir(dirname): os.makedirs(dirname) - print(f"[Directory {dirname} created]", file=sys.stderr) + print_msg( + Message( + MsgText.DirectoryCreated, + MsgType.NORMAL, + {"directory_name": dirname}, + ) + ) self.create_file(filename) - print(f"[Journal '{self.name}' created at {filename}]", file=sys.stderr) + print_msg( + Message( + MsgText.JournalCreated, + MsgType.NORMAL, + { + "journal_name": self.name, + "filename": filename, + }, + ) + ) text = self._load(filename) self.entries = self._parse(text) diff --git a/jrnl/messages.py b/jrnl/messages.py index c91f789ac..c21bb520b 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -87,6 +87,9 @@ def __str__(self) -> str: JournalCountModifiedPlural = "{num} entries modified" JournalCountDeletedPlural = "{num} entries deleted" + JournalCreated = "Journal '{journal_name}' created at {filename}" + DirectoryCreated = "Directory {directory_name} created" + # --- Editor ---# WritingEntryStart = """ Writing Entry From 2df7419ac1416f15a331a6f9cb18c3137e8ddbc6 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 03:58:02 -0700 Subject: [PATCH 14/66] update install module to use new message function --- jrnl/install.py | 8 ++++---- jrnl/messages.py | 5 +++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/jrnl/install.py b/jrnl/install.py index f8322fbae..340fd16a9 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -17,6 +17,7 @@ from .prompt import yesno from .upgrade import is_old_version +from jrnl.output import print_msg from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText @@ -35,9 +36,8 @@ def upgrade_config(config_data, alt_config_path=None): config_data[key] = default_config[key] save_config(config_data, alt_config_path) config_path = alt_config_path if alt_config_path else get_config_path() - print( - f"[Configuration updated to newest version at {config_path}]", - file=sys.stderr, + print_msg( + Message(MsgText.ConfigUpdated, MsgType.NORMAL, {"config_path": config_path}) ) @@ -121,7 +121,7 @@ def install(): ) if encrypt: default_config["encrypt"] = True - print("Journal will be encrypted.", file=sys.stderr) + print_msg(Message(MsgText.JournalEncrypted, MsgType.NORMAL)) save_config(default_config) return default_config diff --git a/jrnl/messages.py b/jrnl/messages.py index c21bb520b..6b3d9b52a 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -89,6 +89,7 @@ def __str__(self) -> str: JournalCreated = "Journal '{journal_name}' created at {filename}" DirectoryCreated = "Directory {directory_name} created" + JournalEncrypted = "Journal will be encrypted." # --- Editor ---# WritingEntryStart = """ @@ -138,6 +139,10 @@ def __str__(self) -> str: {config_file} """ + ConfigUpdated = """ + Configuration updated to newest version at {config_path} + """ + # --- Password --- # PasswordMaxTriesExceeded = """ Too many attempts with wrong password From 80880837373eb94ed98ca18bcb7678d81898087b Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 04:03:49 -0700 Subject: [PATCH 15/66] update config module to use new message function --- jrnl/config.py | 20 +++++++++++--------- jrnl/messages.py | 3 +++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/jrnl/config.py b/jrnl/config.py index 2b07b14bb..100b6f44f 100644 --- a/jrnl/config.py +++ b/jrnl/config.py @@ -1,20 +1,18 @@ import logging import os -import sys import colorama from ruamel.yaml import YAML import xdg.BaseDirectory from . import __version__ +from jrnl.output import list_journals +from jrnl.output import print_msg from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText from jrnl.messages import MsgType -from .color import ERROR_COLOR -from .color import RESET_COLOR -from .output import list_journals # Constants DEFAULT_CONFIG_NAME = "jrnl.yaml" @@ -146,11 +144,15 @@ def verify_config_colors(config): if upper_color == "NONE": continue if not getattr(colorama.Fore, upper_color, None): - print( - "[{2}ERROR{3}: {0} set to invalid color: {1}]".format( - key, color, ERROR_COLOR, RESET_COLOR - ), - file=sys.stderr, + print_msg( + Message( + MsgText.InvalidColor, + MsgType.NORMAL, + { + "key": key, + "color": color, + }, + ) ) all_valid_colors = False return all_valid_colors diff --git a/jrnl/messages.py b/jrnl/messages.py index 6b3d9b52a..2341960e9 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -167,6 +167,9 @@ def __str__(self) -> str: {count} imported to {journal_name} journal """ + # --- Color --- # + InvalidColor = "{key} set to invalid color: {color}" + class Message(NamedTuple): text: MsgText From 9c030ac790b1451897ba0247b15d5f986df26bdc Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 04:10:20 -0700 Subject: [PATCH 16/66] update upgrade module to properly use new message and exception handling --- jrnl/messages.py | 2 ++ jrnl/upgrade.py | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 2341960e9..1da61e552 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -131,6 +131,8 @@ def __str__(self) -> str: UpgradeAborted = "jrnl was NOT upgraded" + AbortingUpgrade = "Aborting upgrade..." + ImportAborted = "Entries were NOT imported" # -- Config --- # diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 3027f4e7e..f8ddb275b 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -152,9 +152,8 @@ def upgrade_jrnl(config_path): failed_journals = [j for j in all_journals if not j.validate_parsing()] if len(failed_journals) > 0: - print_msg("Aborting upgrade.", msg=Message.NORMAL) - raise JrnlException( + Message(MsgText.AbortingUpgrade, MsgType.WARNING), Message( MsgText.JournalFailedUpgrade, MsgType.ERROR, @@ -162,7 +161,7 @@ def upgrade_jrnl(config_path): "s": "s" if len(failed_journals) > 1 else "", "failed_journals": "\n".join(j.name for j in failed_journals), }, - ) + ), ) # write all journals - or - don't From cf5b177d1d4ab8d25eea8922d9718a85d72932e5 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 04:11:24 -0700 Subject: [PATCH 17/66] fix typo --- jrnl/upgrade.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index f8ddb275b..981f1c394 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -13,7 +13,6 @@ from .prompt import yesno from jrnl.output import print_msg - from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText @@ -34,7 +33,7 @@ def backup(filename, binary=False): print(f"\nError: {filename} does not exist.") cont = yesno(f"\nCreate {filename}?", default=False) if not cont: - raise JrnlException(Message(MsgText.UpgradeAborted), MsgType.WARNING) + raise JrnlException(Message(MsgText.UpgradeAborted, MsgType.WARNING)) def check_exists(path): From cd464eebe08c1ee339eb3296e1d8386d616e28d1 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 04:17:31 -0700 Subject: [PATCH 18/66] update upgrade module to use new message handling --- jrnl/messages.py | 3 +++ jrnl/upgrade.py | 12 ++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 1da61e552..0954f7d74 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -75,6 +75,8 @@ def __str__(self) -> str: NoDefaultJournal = "No default journal configured\n{journals}" + FileDoesNotExist = "{filename} does not exist" + # --- Journal status ---# JournalNotSaved = "Entry NOT saved to journal" JournalEntryAdded = "Entry added to {journal_name} journal" @@ -90,6 +92,7 @@ def __str__(self) -> str: JournalCreated = "Journal '{journal_name}' created at {filename}" DirectoryCreated = "Directory {directory_name} created" JournalEncrypted = "Journal will be encrypted." + BackupCreated = "Created a backup at {filename}" # --- Editor ---# WritingEntryStart = """ diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 981f1c394..e775eff90 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -20,7 +20,6 @@ def backup(filename, binary=False): - print(f" Created a backup at {filename}.backup", file=sys.stderr) filename = os.path.expanduser(os.path.expandvars(filename)) try: @@ -29,8 +28,17 @@ def backup(filename, binary=False): with open(filename + ".backup", "wb" if binary else "w") as backup: backup.write(contents) + + print_msg( + Message( + MsgText.BackupCreated, MsgType.NORMAL, {"filename": f"filename.backup"} + ) + ) + except FileNotFoundError: - print(f"\nError: {filename} does not exist.") + print_msg( + Message(MsgText.FileDoesNotExist, MsgType.WARNING, {"filename": filename}) + ) cont = yesno(f"\nCreate {filename}?", default=False) if not cont: raise JrnlException(Message(MsgText.UpgradeAborted, MsgType.WARNING)) From f1119e1593506cf8447a5b52497658f55d91df9e Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 04:22:42 -0700 Subject: [PATCH 19/66] update welcome message to use new handling --- jrnl/messages.py | 19 +++++++++++++++++++ jrnl/upgrade.py | 18 +----------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 0954f7d74..0ed2cc8a0 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -24,6 +24,25 @@ class MsgText(Enum): def __str__(self) -> str: return self.value + # -- Welcome --- # + + WelcomeToJrnl = """ + Welcome to jrnl {version}! + + It looks like you've been using an older version of jrnl until now. That's + okay - jrnl will now upgrade your configuration and journal files. Afterwards + you can enjoy all of the great new features that come with jrnl 2: + + - Support for storing your journal in multiple files + - Faster reading and writing for large journals + - New encryption back-end that makes installing jrnl much easier + - Tons of bug fixes + + Please note that jrnl 1.x is NOT forward compatible with this version of jrnl. + If you choose to proceed, you will not be able to use your journals with + older versions of jrnl anymore. + """ + # --- Exceptions ---# UncaughtException = """ {name} diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index e775eff90..ad0f4fa6c 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -54,23 +54,7 @@ def check_exists(path): def upgrade_jrnl(config_path): config = load_config(config_path) - print( - f"""Welcome to jrnl {__version__}. - -It looks like you've been using an older version of jrnl until now. That's -okay - jrnl will now upgrade your configuration and journal files. Afterwards -you can enjoy all of the great new features that come with jrnl 2: - -- Support for storing your journal in multiple files -- Faster reading and writing for large journals -- New encryption back-end that makes installing jrnl much easier -- Tons of bug fixes - -Please note that jrnl 1.x is NOT forward compatible with this version of jrnl. -If you choose to proceed, you will not be able to use your journals with -older versions of jrnl anymore. -""" - ) + print_msg(Message(MsgText.WelcomeToJrnl, MsgType.NORMAL, {"version": __version__})) encrypted_journals = {} plain_journals = {} From 93650f99273e7355e5714bc8aca51201937c89f0 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 04:25:32 -0700 Subject: [PATCH 20/66] update upgrade module to use new message handling --- jrnl/messages.py | 2 +- jrnl/upgrade.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 0ed2cc8a0..3c365b189 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -94,7 +94,7 @@ def __str__(self) -> str: NoDefaultJournal = "No default journal configured\n{journals}" - FileDoesNotExist = "{filename} does not exist" + DoesNotExist = "{name} does not exist" # --- Journal status ---# JournalNotSaved = "Entry NOT saved to journal" diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index ad0f4fa6c..f7c7274dc 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -37,7 +37,7 @@ def backup(filename, binary=False): except FileNotFoundError: print_msg( - Message(MsgText.FileDoesNotExist, MsgType.WARNING, {"filename": filename}) + Message(MsgText.DoesNotExist, MsgType.WARNING, {"name": filename}) ) cont = yesno(f"\nCreate {filename}?", default=False) if not cont: @@ -73,6 +73,7 @@ def upgrade_jrnl(config_path): path = os.path.expanduser(path) else: print(f"\nError: {path} does not exist.") + print_msg(Message(MsgText.DoesNotExist, MsgType.ERROR, {"name": path})) continue if encrypt: From 1b6c3edbd3e140b73c64552f145ce6afb94b5d51 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 05:17:39 -0700 Subject: [PATCH 21/66] update upgrade module journal summaries to use new message handling --- jrnl/messages.py | 11 ++++++ jrnl/upgrade.py | 89 ++++++++++++++++++++++++++++++------------------ 2 files changed, 67 insertions(+), 33 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 3c365b189..275ccdc67 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -157,6 +157,17 @@ def __str__(self) -> str: ImportAborted = "Entries were NOT imported" + JournalsToUpgrade = """ + The following {journal_type} journals will be upgraded to jrnl {version}: + + """ + + JournalsToIgnore = """ + The following journals will not be touched: + + """ + PaddedJournalName = "{journal_name:{pad}} -> {path}" + # -- Config --- # AltConfigNotFound = """ Alternate configuration file not found at the given path: diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index f7c7274dc..9cb0102c9 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -13,6 +13,7 @@ from .prompt import yesno from jrnl.output import print_msg +from jrnl.output import print_msgs from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText @@ -36,9 +37,7 @@ def backup(filename, binary=False): ) except FileNotFoundError: - print_msg( - Message(MsgText.DoesNotExist, MsgType.WARNING, {"name": filename}) - ) + print_msg(Message(MsgText.DoesNotExist, MsgType.WARNING, {"name": filename})) cont = yesno(f"\nCreate {filename}?", default=False) if not cont: raise JrnlException(Message(MsgText.UpgradeAborted, MsgType.WARNING)) @@ -83,36 +82,41 @@ def upgrade_jrnl(config_path): else: plain_journals[journal_name] = path - longest_journal_name = max([len(journal) for journal in config["journals"]]) - if encrypted_journals: - print( - f"\nFollowing encrypted journals will be upgraded to jrnl {__version__}:", - file=sys.stderr, - ) - for journal, path in encrypted_journals.items(): - print( - " {:{pad}} -> {}".format(journal, path, pad=longest_journal_name), - file=sys.stderr, - ) - - if plain_journals: - print( - f"\nFollowing plain text journals will upgraded to jrnl {__version__}:", - file=sys.stderr, - ) - for journal, path in plain_journals.items(): - print( - " {:{pad}} -> {}".format(journal, path, pad=longest_journal_name), - file=sys.stderr, - ) - - if other_journals: - print("\nFollowing journals will be not be touched:", file=sys.stderr) - for journal, path in other_journals.items(): - print( - " {:{pad}} -> {}".format(journal, path, pad=longest_journal_name), - file=sys.stderr, - ) + kwargs = { + # longest journal name + "pad": max([len(journal) for journal in config["journals"]]), + "version": __version__, + } + + _print_journal_summary( + journals=encrypted_journals, + header=Message( + MsgText.JournalsToUpgrade, + params={ + "journal_type": "encrypted", + "version": __version__, + }, + ), + **kwargs, + ) + + _print_journal_summary( + journals=plain_journals, + header=Message( + MsgText.JournalsToUpgrade, + params={ + "journal_type": "plain text", + "version": __version__, + }, + ), + **kwargs, + ) + + _print_journal_summary( + journals=other_journals, + header=Message(MsgText.JournalsToIgnore), + **kwargs, + ) cont = yesno("\nContinue upgrading jrnl?", default=False) if not cont: @@ -169,3 +173,22 @@ def upgrade_jrnl(config_path): def is_old_version(config_path): return is_config_json(config_path) + + +def _print_journal_summary(journals: dict, header: Message, pad: int) -> None: + if not journals: + return + + msgs = [header] + for journal, path in journals.items(): + msgs.append( + Message( + MsgText.PaddedJournalName, + params={ + "journal_name": journal, + "path": path, + "pad": pad, + }, + ) + ) + print_msgs(msgs) From 03c0ae33f056711c5bb4c04545a7fc94af299bcf Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 05:18:02 -0700 Subject: [PATCH 22/66] take out old code --- jrnl/upgrade.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 9cb0102c9..90e978873 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -71,7 +71,6 @@ def upgrade_jrnl(config_path): if os.path.exists(os.path.expanduser(path)): path = os.path.expanduser(path) else: - print(f"\nError: {path} does not exist.") print_msg(Message(MsgText.DoesNotExist, MsgType.ERROR, {"name": path})) continue From 3ccf695f5020d07a41b406c56fd256c38d8ec8b7 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 05:27:33 -0700 Subject: [PATCH 23/66] update upgrade module to use new message handling --- jrnl/messages.py | 5 +++++ jrnl/upgrade.py | 26 ++++++++++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 275ccdc67..15b85f8b7 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -166,6 +166,11 @@ def __str__(self) -> str: The following journals will not be touched: """ + + UpgradingJournal = """ + Upgrading {journal_type} '{journal_name}' journal stored in {path}... + """ + PaddedJournalName = "{journal_name:{pad}} -> {path}" # -- Config --- # diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 90e978873..9adc77958 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -122,10 +122,17 @@ def upgrade_jrnl(config_path): raise JrnlException(Message(MsgText.UpgradeAborted), MsgType.WARNING) for journal_name, path in encrypted_journals.items(): - print( - f"\nUpgrading encrypted '{journal_name}' journal stored in {path}...", - file=sys.stderr, + print_msg( + Message( + MsgText.UpgradingJournal, + params={ + "journal_type": "encrypted", + "journal_name": journal_name, + "path": path, + }, + ) ) + backup(path, binary=True) old_journal = Journal.open_journal( journal_name, scope_config(config, journal_name), legacy=True @@ -133,10 +140,17 @@ def upgrade_jrnl(config_path): all_journals.append(EncryptedJournal.from_journal(old_journal)) for journal_name, path in plain_journals.items(): - print( - f"\nUpgrading plain text '{journal_name}' journal stored in {path}...", - file=sys.stderr, + print_msg( + Message( + MsgText.UpgradingJournal, + params={ + "journal_type": "plain text", + "journal_name": journal_name, + "path": path, + }, + ) ) + backup(path) old_journal = Journal.open_journal( journal_name, scope_config(config, journal_name), legacy=True From 48d36ecad91aecd204c8de3a7107cf77ffb27162 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 05:33:14 -0700 Subject: [PATCH 24/66] update upgrade module to use new message handling --- jrnl/messages.py | 5 ++++- jrnl/upgrade.py | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 15b85f8b7..ee4c9e058 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -25,7 +25,6 @@ def __str__(self) -> str: return self.value # -- Welcome --- # - WelcomeToJrnl = """ Welcome to jrnl {version}! @@ -43,6 +42,8 @@ def __str__(self) -> str: older versions of jrnl anymore. """ + AllDoneUpgrade = "We're all done here and you can start enjoying jrnl 2" + # --- Exceptions ---# UncaughtException = """ {name} @@ -171,6 +172,8 @@ def __str__(self) -> str: Upgrading {journal_type} '{journal_name}' journal stored in {path}... """ + UpgradingConfig = "Upgrading config..." + PaddedJournalName = "{journal_name:{pad}} -> {path}" # -- Config --- # diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 9adc77958..00afc3cbb 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -177,11 +177,11 @@ def upgrade_jrnl(config_path): for j in all_journals: j.write() - print("\nUpgrading config...", file=sys.stderr) + print_msg(Message(MsgText.UpgradingConfig, MsgType.NORMAL)) backup(config_path) - print("\nWe're all done here and you can start enjoying jrnl 2.", file=sys.stderr) + print_msg(Message(MsgText.AllDoneUpgrade, MsgType.NORMAL)) def is_old_version(config_path): From 9eb3e1684b8f53713c903c0730c2438974f2debf Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 09:22:58 -0700 Subject: [PATCH 25/66] update more modules to use new message handling --- jrnl/EncryptedJournal.py | 29 ++++++++++++++++++----------- jrnl/commands.py | 20 ++++++++++++++------ jrnl/messages.py | 16 +++++++++++++++- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index 704a091ab..a1f65aac9 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -54,7 +54,7 @@ def decrypt_content( set_keychain(keychain, None) attempt = 1 while result is None and attempt < max_attempts: - print("Wrong password, try again.", file=sys.stderr) + print_msg(Message(MsgText.WrongPasswordTryAgain, MsgType.WARNING)) password = getpass.getpass() result = decrypt_func(password) attempt += 1 @@ -79,13 +79,22 @@ def open(self, filename=None): if not os.path.exists(filename): if not os.path.isdir(dirname): os.makedirs(dirname) - print(f"[Directory {dirname} created]", file=sys.stderr) + print_msg( + Message( + MsgText.DirectoryCreated, + MsgType.NORMAL, + {"directory_name": dirname}, + ) + ) self.create_file(filename) self.password = create_password(self.name) - print( - f"Encrypted journal '{self.name}' created at {filename}", - file=sys.stderr, + print_msg( + Message( + MsgText.JournalCreated, + MsgType.NORMAL, + {"journal_name": self.name, "path": filename}, + ) ) text = self._load(filename) @@ -179,7 +188,7 @@ def get_keychain(journal_name): return keyring.get_password("jrnl", journal_name) except keyring.errors.KeyringError as e: if not isinstance(e, keyring.errors.NoKeyringError): - print("Failed to retrieve keyring", file=sys.stderr) + print_msg(Message(MsgText.KeyringRetrievalFailure, MsgType.ERROR)) return "" @@ -196,9 +205,7 @@ def set_keychain(journal_name, password): keyring.set_password("jrnl", journal_name, password) except keyring.errors.KeyringError as e: if isinstance(e, keyring.errors.NoKeyringError): - print( - "Keyring backend not found. Please install one of the supported backends by visiting: https://pypi.org/project/keyring/", - file=sys.stderr, - ) + msg = Message(MsgText.KeyringBackendNotFound, MsgType.WARNING) else: - print("Failed to retrieve keyring", file=sys.stderr) + msg = Message(MsgText.KeyringRetrievalFailure, MsgType.ERROR) + print_msg(msg) diff --git a/jrnl/commands.py b/jrnl/commands.py index c795402a2..cf7ccbf51 100644 --- a/jrnl/commands.py +++ b/jrnl/commands.py @@ -13,6 +13,8 @@ """ import platform import sys + +from jrnl.output import print_msg from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText @@ -89,9 +91,12 @@ def postconfig_encrypt(args, config, original_config, **kwargs): new_journal = EncryptedJournal.from_journal(journal) new_journal.write(args.filename) - print( - f"Journal encrypted to {args.filename or new_journal.config['journal']}.", - file=sys.stderr, + print_msg( + Message( + MsgText.JournalEncryptedTo, + MsgType.NORMAL, + {"path": args.filename or new_journal.config["journal"]}, + ) ) # Update the config, if we encrypted in place @@ -114,9 +119,12 @@ def postconfig_decrypt(args, config, original_config, **kwargs): new_journal = PlainJournal.from_journal(journal) new_journal.write(args.filename) - print( - f"Journal decrypted to {args.filename or new_journal.config['journal']}.", - file=sys.stderr, + print_msg( + Message( + MsgText.JournalDecryptedTo, + MsgType.NORMAL, + {"path": args.filename or new_journal.config["journal"]}, + ) ) # Update the config, if we decrypted in place diff --git a/jrnl/messages.py b/jrnl/messages.py index ee4c9e058..ae359d15b 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -111,7 +111,9 @@ def __str__(self) -> str: JournalCreated = "Journal '{journal_name}' created at {filename}" DirectoryCreated = "Directory {directory_name} created" - JournalEncrypted = "Journal will be encrypted." + JournalEncrypted = "Journal will be encrypted" + JournalEncryptedTo = "Journal encrypted to {path}" + JournalDecryptedTo = "Journal decrypted to {path}" BackupCreated = "Created a backup at {filename}" # --- Editor ---# @@ -191,6 +193,8 @@ def __str__(self) -> str: Too many attempts with wrong password """ + WrongPasswordTryAgain = "Wrong password, try again." + # --- Search --- # NothingToDelete = """ No entries to delete, because the search returned no results @@ -213,6 +217,16 @@ def __str__(self) -> str: # --- Color --- # InvalidColor = "{key} set to invalid color: {color}" + # --- Keyring --- # + KeyringBackendNotFound = """ + Keyring backend not found. + + Please install one of the supported backends by visiting: + https://pypi.org/project/keyring/ + """ + + KeyringRetrievalFailure = "Failed to retrieve keyring" + class Message(NamedTuple): text: MsgText From 8da4a991feef6403cd3e76d67236014e67697559 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 09:23:18 -0700 Subject: [PATCH 26/66] take out old comment --- jrnl/FolderJournal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jrnl/FolderJournal.py b/jrnl/FolderJournal.py index 74f6291bb..59f12a4ac 100644 --- a/jrnl/FolderJournal.py +++ b/jrnl/FolderJournal.py @@ -80,7 +80,6 @@ def write(self): filenames = get_files(self.config["journal"]) for filename in filenames: if os.stat(filename).st_size <= 0: - # print("empty file: {}".format(filename)) os.remove(filename) def delete_entries(self, entries_to_delete): From 28712132b7fda34b9fa1bfa621a4e0f745e6950c Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 09:30:37 -0700 Subject: [PATCH 27/66] update deprecated_cmd to use new message handling --- jrnl/messages.py | 6 ++++++ jrnl/output.py | 14 +++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index ae359d15b..f8ea47f42 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -227,6 +227,12 @@ def __str__(self) -> str: KeyringRetrievalFailure = "Failed to retrieve keyring" + # --- Deprecation --- # + DeprecatedCommand = """ + The command {old_cmd} is deprecated and will be removed from jrnl soon. + Please use {new_cmd} instead. + """ + class Message(NamedTuple): text: MsgText diff --git a/jrnl/output.py b/jrnl/output.py index 1a8e7fb1f..8386f7694 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -19,13 +19,13 @@ def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs): - warning_msg = f""" - The command {old_cmd} is deprecated and will be removed from jrnl soon. - Please use {new_cmd} instead. - """ - warning_msg = textwrap.dedent(warning_msg) - logging.warning(warning_msg) - print(f"{WARNING_COLOR}{warning_msg}{RESET_COLOR}", file=sys.stderr) + print_msg( + Message( + MsgText.DeprecatedCommand, + MsgType.WARNING, + {"old_cmd": old_cmd, "new_cmd": new_cmd}, + ) + ) if callback is not None: callback(**kwargs) From eeca1e23386f52161b123d2fb966ed5558be946e Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 10:39:08 -0700 Subject: [PATCH 28/66] update text_exporter with new message handling, get rid of old color constants --- jrnl/color.py | 4 --- jrnl/output.py | 2 -- jrnl/plugins/text_exporter.py | 48 ++++++++++++++++++++--------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/jrnl/color.py b/jrnl/color.py index 691cce9cb..7cc8df2ff 100644 --- a/jrnl/color.py +++ b/jrnl/color.py @@ -9,10 +9,6 @@ if on_windows(): colorama.init() -WARNING_COLOR = colorama.Fore.YELLOW -ERROR_COLOR = colorama.Fore.RED -RESET_COLOR = colorama.Fore.RESET - def colorize(string, color, bold=False): """Returns the string colored with colorama.Fore.color. If the color set by diff --git a/jrnl/output.py b/jrnl/output.py index 8386f7694..55ea71873 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -11,8 +11,6 @@ from rich import box from rich.console import Console -from jrnl.color import RESET_COLOR -from jrnl.color import WARNING_COLOR from jrnl.messages import Message from jrnl.messages import MsgType from jrnl.messages import MsgText diff --git a/jrnl/plugins/text_exporter.py b/jrnl/plugins/text_exporter.py index 4a5300df2..51226f05d 100644 --- a/jrnl/plugins/text_exporter.py +++ b/jrnl/plugins/text_exporter.py @@ -6,8 +6,10 @@ import re import unicodedata -from jrnl.color import ERROR_COLOR -from jrnl.color import RESET_COLOR +from jrnl.output import print_msg +from jrnl.messages import Message +from jrnl.messages import MsgText +from jrnl.messages import MsgType class TextExporter: @@ -29,14 +31,18 @@ def export_journal(cls, journal): @classmethod def write_file(cls, journal, path): """Exports a journal into a single file.""" - try: - with open(path, "w", encoding="utf-8") as f: - f.write(cls.export_journal(journal)) - return f"[Journal exported to {path}]" - except IOError as e: - return f"[{ERROR_COLOR}ERROR{RESET_COLOR}: {e.filename} {e.strerror}]" - except RuntimeError as e: - return e + with open(path, "w", encoding="utf-8") as f: + f.write(cls.export_journal(journal)) + print_msg( + Message( + MsgText.JournalExportedTo, + MsgType.NORMAL, + { + "path": path, + }, + ) + ) + return "" @classmethod def make_filename(cls, entry): @@ -48,17 +54,17 @@ def make_filename(cls, entry): def write_files(cls, journal, path): """Exports a journal into individual files for each entry.""" for entry in journal.entries: - try: - full_path = os.path.join(path, cls.make_filename(entry)) - with open(full_path, "w", encoding="utf-8") as f: - f.write(cls.export_entry(entry)) - except IOError as e: - return "[{2}ERROR{3}: {0} {1}]".format( - e.filename, e.strerror, ERROR_COLOR, RESET_COLOR - ) - except RuntimeError as e: - return e - return "[Journal exported to {}]".format(path) + full_path = os.path.join(path, cls.make_filename(entry)) + with open(full_path, "w", encoding="utf-8") as f: + f.write(cls.export_entry(entry)) + print_msg( + Message( + MsgText.JournalExportedTo, + MsgType.NORMAL, + {"path": path}, + ) + ) + return "" def _slugify(string): """Slugifies a string. From fd930922db720c195bfba4e9e2867fd44767e4af Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 10:39:38 -0700 Subject: [PATCH 29/66] get rid of hardcoded text --- jrnl/messages.py | 6 ++++-- jrnl/upgrade.py | 4 ---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index f8ea47f42..95c96f544 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -161,7 +161,7 @@ def __str__(self) -> str: ImportAborted = "Entries were NOT imported" JournalsToUpgrade = """ - The following {journal_type} journals will be upgraded to jrnl {version}: + The following journals will be upgraded to jrnl {version}: """ @@ -171,7 +171,7 @@ def __str__(self) -> str: """ UpgradingJournal = """ - Upgrading {journal_type} '{journal_name}' journal stored in {path}... + Upgrading '{journal_name}' journal stored in {path}... """ UpgradingConfig = "Upgrading config..." @@ -209,6 +209,8 @@ def __str__(self) -> str: YAML export must be to a directory, not a single file """ + JournalExportedTo = "Journal exported to {path}" + # --- Import --- # ImportSummary = """ {count} imported to {journal_name} journal diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 00afc3cbb..d811f8413 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -92,7 +92,6 @@ def upgrade_jrnl(config_path): header=Message( MsgText.JournalsToUpgrade, params={ - "journal_type": "encrypted", "version": __version__, }, ), @@ -104,7 +103,6 @@ def upgrade_jrnl(config_path): header=Message( MsgText.JournalsToUpgrade, params={ - "journal_type": "plain text", "version": __version__, }, ), @@ -126,7 +124,6 @@ def upgrade_jrnl(config_path): Message( MsgText.UpgradingJournal, params={ - "journal_type": "encrypted", "journal_name": journal_name, "path": path, }, @@ -144,7 +141,6 @@ def upgrade_jrnl(config_path): Message( MsgText.UpgradingJournal, params={ - "journal_type": "plain text", "journal_name": journal_name, "path": path, }, From 6f5a08207591f357c351d9233a0c6d07599647e0 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 10:39:49 -0700 Subject: [PATCH 30/66] whitespace changes --- jrnl/jrnl.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 2902c523c..1436e057d 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -346,8 +346,10 @@ def _display_search_results(args, journal, **kwargs): elif args.export: exporter = plugins.get_exporter(args.export) print(exporter.export(journal, args.filename)) + elif kwargs["config"].get("display_format"): exporter = plugins.get_exporter(kwargs["config"]["display_format"]) print(exporter.export(journal, args.filename)) + else: print(journal.pprint()) From 16755f849a216c235052353bc8493f6fd051b93e Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 13:22:11 -0700 Subject: [PATCH 31/66] rework MsgType into MsgStyle so messages can have different styles --- jrnl/EncryptedJournal.py | 16 +++--- jrnl/Journal.py | 8 +-- jrnl/cli.py | 6 +-- jrnl/commands.py | 8 +-- jrnl/config.py | 8 +-- jrnl/editor.py | 12 ++--- jrnl/install.py | 10 ++-- jrnl/jrnl.py | 20 ++++---- jrnl/messages.py | 82 +++++++++++++++++++++++++++---- jrnl/output.py | 31 +++++------- jrnl/plugins/fancy_exporter.py | 4 +- jrnl/plugins/jrnl_importer.py | 8 +-- jrnl/plugins/markdown_exporter.py | 4 +- jrnl/plugins/text_exporter.py | 6 +-- jrnl/plugins/yaml_exporter.py | 8 +-- jrnl/prompt.py | 7 ++- jrnl/upgrade.py | 22 ++++----- 17 files changed, 161 insertions(+), 99 deletions(-) diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index a1f65aac9..2e6cb3f3f 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -24,7 +24,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle def make_key(password): @@ -54,13 +54,13 @@ def decrypt_content( set_keychain(keychain, None) attempt = 1 while result is None and attempt < max_attempts: - print_msg(Message(MsgText.WrongPasswordTryAgain, MsgType.WARNING)) + print_msg(Message(MsgText.WrongPasswordTryAgain, MsgStyle.WARNING)) password = getpass.getpass() result = decrypt_func(password) attempt += 1 if result is None: - raise JrnlException(Message(MsgText.PasswordMaxTriesExceeded, MsgType.ERROR)) + raise JrnlException(Message(MsgText.PasswordMaxTriesExceeded, MsgStyle.ERROR)) return result @@ -82,7 +82,7 @@ def open(self, filename=None): print_msg( Message( MsgText.DirectoryCreated, - MsgType.NORMAL, + MsgStyle.NORMAL, {"directory_name": dirname}, ) ) @@ -92,7 +92,7 @@ def open(self, filename=None): print_msg( Message( MsgText.JournalCreated, - MsgType.NORMAL, + MsgStyle.NORMAL, {"journal_name": self.name, "path": filename}, ) ) @@ -188,7 +188,7 @@ def get_keychain(journal_name): return keyring.get_password("jrnl", journal_name) except keyring.errors.KeyringError as e: if not isinstance(e, keyring.errors.NoKeyringError): - print_msg(Message(MsgText.KeyringRetrievalFailure, MsgType.ERROR)) + print_msg(Message(MsgText.KeyringRetrievalFailure, MsgStyle.ERROR)) return "" @@ -205,7 +205,7 @@ def set_keychain(journal_name, password): keyring.set_password("jrnl", journal_name, password) except keyring.errors.KeyringError as e: if isinstance(e, keyring.errors.NoKeyringError): - msg = Message(MsgText.KeyringBackendNotFound, MsgType.WARNING) + msg = Message(MsgText.KeyringBackendNotFound, MsgStyle.WARNING) else: - msg = Message(MsgText.KeyringRetrievalFailure, MsgType.ERROR) + msg = Message(MsgText.KeyringRetrievalFailure, MsgStyle.ERROR) print_msg(msg) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 5965fddea..895437a84 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -15,7 +15,7 @@ from jrnl.output import print_msg from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle class Tag: @@ -90,7 +90,7 @@ def open(self, filename=None): print_msg( Message( MsgText.DirectoryCreated, - MsgType.NORMAL, + MsgStyle.NORMAL, {"directory_name": dirname}, ) ) @@ -98,7 +98,7 @@ def open(self, filename=None): print_msg( Message( MsgText.JournalCreated, - MsgType.NORMAL, + MsgStyle.NORMAL, { "journal_name": self.name, "filename": filename, @@ -431,7 +431,7 @@ def open_journal(journal_name, config, legacy=False): print_msg( Message( MsgText.ConfigEncryptedForUnencryptableJournalType, - MsgType.WARNING, + MsgStyle.WARNING, { "journal_name": journal_name, }, diff --git a/jrnl/cli.py b/jrnl/cli.py index cd2275efb..c96556da8 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -12,7 +12,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle def configure_logger(debug=False): @@ -49,7 +49,7 @@ def cli(manual_args=None): print_msg( Message( MsgText.KeyboardInterruptMsg, - MsgType.ERROR, + MsgStyle.ERROR, ) ) @@ -73,7 +73,7 @@ def cli(manual_args=None): print_msg( Message( MsgText.UncaughtException, - MsgType.ERROR, + MsgStyle.ERROR, {"name": type(e).__name__, "exception": e}, ) ) diff --git a/jrnl/commands.py b/jrnl/commands.py index cf7ccbf51..eb0c0d6fd 100644 --- a/jrnl/commands.py +++ b/jrnl/commands.py @@ -18,7 +18,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle def preconfig_diagnostic(_): @@ -78,7 +78,7 @@ def postconfig_encrypt(args, config, original_config, **kwargs): raise JrnlException( Message( MsgText.CannotEncryptJournalType, - MsgType.ERROR, + MsgStyle.ERROR, { "journal_name": args.journal_name, "journal_type": journal.__class__.__name__, @@ -94,7 +94,7 @@ def postconfig_encrypt(args, config, original_config, **kwargs): print_msg( Message( MsgText.JournalEncryptedTo, - MsgType.NORMAL, + MsgStyle.NORMAL, {"path": args.filename or new_journal.config["journal"]}, ) ) @@ -122,7 +122,7 @@ def postconfig_decrypt(args, config, original_config, **kwargs): print_msg( Message( MsgText.JournalDecryptedTo, - MsgType.NORMAL, + MsgStyle.NORMAL, {"path": args.filename or new_journal.config["journal"]}, ) ) diff --git a/jrnl/config.py b/jrnl/config.py index 100b6f44f..1f21397a2 100644 --- a/jrnl/config.py +++ b/jrnl/config.py @@ -11,7 +11,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle # Constants @@ -72,7 +72,7 @@ def get_config_path(): raise JrnlException( Message( MsgText.ConfigDirectoryIsFile, - MsgType.ERROR, + MsgStyle.ERROR, { "config_directory_path": os.path.join( xdg.BaseDirectory.xdg_config_home, XDG_RESOURCE @@ -147,7 +147,7 @@ def verify_config_colors(config): print_msg( Message( MsgText.InvalidColor, - MsgType.NORMAL, + MsgStyle.NORMAL, { "key": key, "color": color, @@ -202,7 +202,7 @@ def get_journal_name(args, config): raise JrnlException( Message( MsgText.NoDefaultJournal, - MsgType.ERROR, + MsgStyle.ERROR, {"journals": list_journals(config)}, ), ) diff --git a/jrnl/editor.py b/jrnl/editor.py index 24c625de5..68e49fef9 100644 --- a/jrnl/editor.py +++ b/jrnl/editor.py @@ -12,7 +12,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle def get_text_from_editor(config, template=""): @@ -33,7 +33,7 @@ def get_text_from_editor(config, template=""): raise JrnlException( Message( MsgText.EditorMisconfigured, - MsgType.ERROR, + MsgStyle.ERROR, {"editor_key": config["editor"]}, ) ) @@ -43,7 +43,7 @@ def get_text_from_editor(config, template=""): os.remove(tmpfile) if not raw: - raise JrnlException(Message(MsgText.NoTextReceived, MsgType.ERROR)) + raise JrnlException(Message(MsgText.NoTextReceived, MsgStyle.ERROR)) return raw @@ -52,7 +52,7 @@ def get_text_from_stdin(): print_msg( Message( MsgText.WritingEntryStart, - MsgType.TITLE, + MsgStyle.TITLE, { "how_to_quit": MsgText.HowToQuitWindows if on_windows() @@ -66,8 +66,8 @@ def get_text_from_stdin(): except KeyboardInterrupt: logging.error("Write mode: keyboard interrupt") raise JrnlException( - Message(MsgText.KeyboardInterruptMsg, MsgType.ERROR), - Message(MsgText.JournalNotSaved, MsgType.WARNING), + Message(MsgText.KeyboardInterruptMsg, MsgStyle.ERROR), + Message(MsgText.JournalNotSaved, MsgStyle.WARNING), ) return raw diff --git a/jrnl/install.py b/jrnl/install.py index 340fd16a9..ef0693efe 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -21,7 +21,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle def upgrade_config(config_data, alt_config_path=None): @@ -37,7 +37,9 @@ def upgrade_config(config_data, alt_config_path=None): save_config(config_data, alt_config_path) config_path = alt_config_path if alt_config_path else get_config_path() print_msg( - Message(MsgText.ConfigUpdated, MsgType.NORMAL, {"config_path": config_path}) + Message( + MsgText.ConfigUpdated, MsgStyle.NORMAL, {"config_path": config_path} + ) ) @@ -54,7 +56,7 @@ def find_alt_config(alt_config): if not os.path.exists(alt_config): raise JrnlException( Message( - MsgText.AltConfigNotFound, MsgType.ERROR, {"config_file": alt_config} + MsgText.AltConfigNotFound, MsgStyle.ERROR, {"config_file": alt_config} ) ) @@ -121,7 +123,7 @@ def install(): ) if encrypt: default_config["encrypt"] = True - print_msg(Message(MsgText.JournalEncrypted, MsgType.NORMAL)) + print_msg(Message(MsgText.JournalEncrypted, MsgStyle.NORMAL)) save_config(default_config) return default_config diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index 1436e057d..41fa439fd 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -20,7 +20,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle def run(args): @@ -138,9 +138,7 @@ def write_mode(args, config, journal, **kwargs): if not raw or raw.isspace(): logging.error("Write mode: couldn't get raw text or entry was empty") - raise JrnlException( - Message(MsgText.NoTextReceived, MsgType.ERROR) - ) + raise JrnlException(Message(MsgText.NoTextReceived, MsgStyle.ERROR)) logging.debug( 'Write mode: appending raw text to journal "%s": %s', args.journal_name, raw @@ -149,7 +147,7 @@ def write_mode(args, config, journal, **kwargs): print_msg( Message( MsgText.JournalEntryAdded, - MsgType.NORMAL, + MsgStyle.NORMAL, {"journal_name": args.journal_name}, ) ) @@ -213,7 +211,7 @@ def _get_editor_template(config, **kwargs): raise JrnlException( Message( MsgText.CantReadTemplate, - MsgType.ERROR, + MsgStyle.ERROR, {"template": config["template"]}, ) ) @@ -256,7 +254,7 @@ def _edit_search_results(config, journal, old_entries, **kwargs): raise JrnlException( Message( MsgText.EditorNotConfigured, - MsgType.ERROR, + MsgStyle.ERROR, {"config_file": get_config_path()}, ) ) @@ -295,7 +293,7 @@ def _print_edited_summary(journal, old_stats, **kwargs): if stats["added"] == 1 else MsgText.JournalCountAddedPlural ) - msgs.append(Message(my_msg, MsgType.NORMAL, {"num": stats["added"]})) + msgs.append(Message(my_msg, MsgStyle.NORMAL, {"num": stats["added"]})) if stats["deleted"] > 0: my_msg = ( @@ -303,7 +301,7 @@ def _print_edited_summary(journal, old_stats, **kwargs): if stats["deleted"] == 1 else MsgText.JournalCountDeletedPlural ) - msgs.append(Message(my_msg, MsgType.NORMAL, {"num": stats["deleted"]})) + msgs.append(Message(my_msg, MsgStyle.NORMAL, {"num": stats["deleted"]})) if stats["modified"] > 0: my_msg = ( @@ -311,7 +309,7 @@ def _print_edited_summary(journal, old_stats, **kwargs): if stats["modified"] == 1 else MsgText.JournalCountModifiedPlural ) - msgs.append(Message(my_msg, MsgType.NORMAL, {"num": stats["modified"]})) + msgs.append(Message(my_msg, MsgStyle.NORMAL, {"num": stats["modified"]})) print_msgs(msgs) @@ -322,7 +320,7 @@ def _get_predit_stats(journal): def _delete_search_results(journal, old_entries, **kwargs): if not journal.entries: - raise JrnlException(Message(MsgText.NothingToDelete, MsgType.ERROR)) + raise JrnlException(Message(MsgText.NothingToDelete, MsgStyle.ERROR)) entries_to_delete = journal.prompt_delete_entries() diff --git a/jrnl/messages.py b/jrnl/messages.py index 95c96f544..fae7a015f 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -1,23 +1,81 @@ from enum import Enum from typing import NamedTuple from typing import Mapping +from typing import Callable +from rich.panel import Panel +from rich import box class _MsgColor(NamedTuple): - # This is a colorama color, and colorama doesn't support enums or type hints - # see: https://github.com/tartley/colorama/issues/91 + """ + String representing a standard color to display + see: https://rich.readthedocs.io/en/stable/appendix/colors.html + """ + color: str -class MsgType(Enum): - TITLE = _MsgColor("cyan") - NORMAL = _MsgColor("blue") - WARNING = _MsgColor("yellow") - ERROR = _MsgColor("red") +class MsgDecoration(Enum): + NONE = { + "callback": lambda x, **kwargs: x, + "args": {} + } + BRACKET = { + "callback": lambda x, **kwargs: f"[ {x} ]", + "args": {} + } + BOX = { + "callback": Panel, + "args": { + "expand": False, + "padding": (0, 2), + "title_align": "left", + "box": box.HEAVY, + }, + } + + @property + def callback(self) -> Callable: + return self.value["callback"] + + @property + def args(self) -> dict: + return self.value["args"] + + +class MsgStyle(Enum): + BARE = { + "decoration": MsgDecoration.NONE, + "color": _MsgColor("white"), + } + PLAIN = { + "decoration": MsgDecoration.BRACKET, + "color": _MsgColor("white"), + } + TITLE = { + "decoration": MsgDecoration.BOX, + "color": _MsgColor("cyan"), + } + NORMAL = { + "decoration": MsgDecoration.BOX, + "color": _MsgColor("white"), + } + WARNING = { + "decoration": MsgDecoration.BOX, + "color": _MsgColor("yellow"), + } + ERROR = { + "decoration": MsgDecoration.BOX, + "color": _MsgColor("red"), + } + + @property + def decoration(self) -> MsgDecoration: + return self.value["decoration"] @property def color(self) -> _MsgColor: - return self.value.color + return self.value["color"].color class MsgText(Enum): @@ -193,7 +251,11 @@ def __str__(self) -> str: Too many attempts with wrong password """ - WrongPasswordTryAgain = "Wrong password, try again." + PasswordCanNotBeEmpty = """ + Password can't be empty! + """ + + WrongPasswordTryAgain = "Wrong password, try again" # --- Search --- # NothingToDelete = """ @@ -238,5 +300,5 @@ def __str__(self) -> str: class Message(NamedTuple): text: MsgText - type: MsgType = MsgType.NORMAL + style: MsgStyle = MsgStyle.NORMAL params: Mapping = {} diff --git a/jrnl/output.py b/jrnl/output.py index 55ea71873..5022887a5 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -6,13 +6,11 @@ import textwrap from rich import print -from rich.panel import Panel from rich.text import Text -from rich import box from rich.console import Console from jrnl.messages import Message -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle from jrnl.messages import MsgText @@ -20,7 +18,7 @@ def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs): print_msg( Message( MsgText.DeprecatedCommand, - MsgType.WARNING, + MsgStyle.WARNING, {"old_cmd": old_cmd, "new_cmd": new_cmd}, ) ) @@ -43,25 +41,21 @@ def list_journals(configuration): def print_msg(msg: Message) -> None: - print_msgs([msg]) + print_msgs([msg], style=msg.style) -def print_msgs(msgs: list[Message], delimiter: str = "\n") -> None: +def print_msgs( + msgs: list[Message], delimiter: str = "\n", style: MsgStyle = MsgStyle.NORMAL +) -> None: # Same as print_msg, but for a list text = Text("") - - kwargs = { - "expand": False, - "border_style": None, - "padding": (0, 2), - "title_align": "left", - "box": box.HEAVY, - } + callback = style.decoration.callback + args = style.decoration.args for msg in msgs: - kwargs["border_style"] = msg.type.color - if msg.type == MsgType.ERROR: - kwargs["title"] = "Error" + args["border_style"] = msg.style.color + if msg.style == MsgStyle.ERROR: + args["title"] = "Error" if is_keyboard_int(msg): print() @@ -72,7 +66,8 @@ def print_msgs(msgs: list[Message], delimiter: str = "\n") -> None: text.rstrip() - Console(stderr=True).print(Panel(text, **kwargs)) + # import ipdb; ipdb.sset_trace() + Console(stderr=True).print(callback(text, **args)) def is_keyboard_int(msg: Message) -> bool: diff --git a/jrnl/plugins/fancy_exporter.py b/jrnl/plugins/fancy_exporter.py index c3dbc467c..0c440e965 100644 --- a/jrnl/plugins/fancy_exporter.py +++ b/jrnl/plugins/fancy_exporter.py @@ -5,7 +5,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle from textwrap import TextWrapper from .text_exporter import TextExporter @@ -90,7 +90,7 @@ def check_provided_linewrap_viability(linewrap, card, journal): raise JrnlException( Message( MsgText.LineWrapTooSmallForDateFormat, - MsgType.NORMAL, + MsgStyle.NORMAL, { "config_linewrap": linewrap, "columns": width_violation, diff --git a/jrnl/plugins/jrnl_importer.py b/jrnl/plugins/jrnl_importer.py index 61d0e0f13..39f53f854 100644 --- a/jrnl/plugins/jrnl_importer.py +++ b/jrnl/plugins/jrnl_importer.py @@ -7,7 +7,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle from jrnl.output import print_msg @@ -29,8 +29,8 @@ def import_(journal, input=None): other_journal_txt = sys.stdin.read() except KeyboardInterrupt: raise JrnlException( - Message(MsgText.KeyboardInterruptMsg, MsgType.ERROR), - Message(MsgText.ImportAborted, MsgType.WARNING), + Message(MsgText.KeyboardInterruptMsg, MsgStyle.ERROR), + Message(MsgText.ImportAborted, MsgStyle.WARNING), ) journal.import_(other_journal_txt) @@ -39,7 +39,7 @@ def import_(journal, input=None): print_msg( Message( MsgText.ImportSummary, - MsgType.NORMAL, + MsgStyle.NORMAL, { "count": new_cnt - old_cnt, "journal_name": journal.name, diff --git a/jrnl/plugins/markdown_exporter.py b/jrnl/plugins/markdown_exporter.py index ae10bf3fb..63be85e05 100644 --- a/jrnl/plugins/markdown_exporter.py +++ b/jrnl/plugins/markdown_exporter.py @@ -10,7 +10,7 @@ from jrnl.output import print_msg from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle class MarkdownExporter(TextExporter): @@ -67,7 +67,7 @@ def export_entry(cls, entry, to_multifile=True): print_msg( Message( MsgText.HeadingsPastH6, - MsgType.WARNING, + MsgStyle.WARNING, {"date": date_str, "title": entry.title}, ) ) diff --git a/jrnl/plugins/text_exporter.py b/jrnl/plugins/text_exporter.py index 51226f05d..d82a1e401 100644 --- a/jrnl/plugins/text_exporter.py +++ b/jrnl/plugins/text_exporter.py @@ -9,7 +9,7 @@ from jrnl.output import print_msg from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle class TextExporter: @@ -36,7 +36,7 @@ def write_file(cls, journal, path): print_msg( Message( MsgText.JournalExportedTo, - MsgType.NORMAL, + MsgStyle.NORMAL, { "path": path, }, @@ -60,7 +60,7 @@ def write_files(cls, journal, path): print_msg( Message( MsgText.JournalExportedTo, - MsgType.NORMAL, + MsgStyle.NORMAL, {"path": path}, ) ) diff --git a/jrnl/plugins/yaml_exporter.py b/jrnl/plugins/yaml_exporter.py index f08e37512..4db1f97a1 100644 --- a/jrnl/plugins/yaml_exporter.py +++ b/jrnl/plugins/yaml_exporter.py @@ -11,7 +11,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle from jrnl.output import print_msg @@ -25,7 +25,7 @@ class YAMLExporter(TextExporter): def export_entry(cls, entry, to_multifile=True): """Returns a markdown representation of a single entry, with YAML front matter.""" if to_multifile is False: - raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgType.ERROR)) + raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR)) date_str = entry.date.strftime(entry.journal.config["timeformat"]) body_wrapper = "\n" if entry.body else "" @@ -80,7 +80,7 @@ def export_entry(cls, entry, to_multifile=True): print_msg( Message( MsgText.HeadingsPastH6, - MsgType.WARNING, + MsgStyle.WARNING, {"date": date_str, "title": entry.title}, ) ) @@ -129,4 +129,4 @@ def export_entry(cls, entry, to_multifile=True): @classmethod def export_journal(cls, journal): """Returns an error, as YAML export requires a directory as a target.""" - raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgType.ERROR)) + raise JrnlException(Message(MsgText.YamlMustBeDirectory, MsgStyle.ERROR)) diff --git a/jrnl/prompt.py b/jrnl/prompt.py index 3cdd3f6f1..1123026e8 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -4,6 +4,11 @@ import getpass import sys +from jrnl.messages import Message +from jrnl.messages import MsgText +from jrnl.messages import MsgStyle +from jrnl.output import print_msg + def create_password( journal_name: str, prompt: str = "Enter password for new journal: " @@ -11,7 +16,7 @@ def create_password( while True: pw = getpass.getpass(prompt) if not pw: - print("Password can't be an empty string!", file=sys.stderr) + print_msg(Message(MsgText.PasswordCanNotBeEmpty, MsgStyle.PLAIN)) continue elif pw == getpass.getpass("Enter password again: "): break diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index d811f8413..15125ab47 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -17,7 +17,7 @@ from jrnl.exception import JrnlException from jrnl.messages import Message from jrnl.messages import MsgText -from jrnl.messages import MsgType +from jrnl.messages import MsgStyle def backup(filename, binary=False): @@ -32,15 +32,15 @@ def backup(filename, binary=False): print_msg( Message( - MsgText.BackupCreated, MsgType.NORMAL, {"filename": f"filename.backup"} + MsgText.BackupCreated, MsgStyle.NORMAL, {"filename": f"filename.backup"} ) ) except FileNotFoundError: - print_msg(Message(MsgText.DoesNotExist, MsgType.WARNING, {"name": filename})) + print_msg(Message(MsgText.DoesNotExist, MsgStyle.WARNING, {"name": filename})) cont = yesno(f"\nCreate {filename}?", default=False) if not cont: - raise JrnlException(Message(MsgText.UpgradeAborted, MsgType.WARNING)) + raise JrnlException(Message(MsgText.UpgradeAborted, MsgStyle.WARNING)) def check_exists(path): @@ -53,7 +53,7 @@ def check_exists(path): def upgrade_jrnl(config_path): config = load_config(config_path) - print_msg(Message(MsgText.WelcomeToJrnl, MsgType.NORMAL, {"version": __version__})) + print_msg(Message(MsgText.WelcomeToJrnl, MsgStyle.NORMAL, {"version": __version__})) encrypted_journals = {} plain_journals = {} @@ -71,7 +71,7 @@ def upgrade_jrnl(config_path): if os.path.exists(os.path.expanduser(path)): path = os.path.expanduser(path) else: - print_msg(Message(MsgText.DoesNotExist, MsgType.ERROR, {"name": path})) + print_msg(Message(MsgText.DoesNotExist, MsgStyle.ERROR, {"name": path})) continue if encrypt: @@ -117,7 +117,7 @@ def upgrade_jrnl(config_path): cont = yesno("\nContinue upgrading jrnl?", default=False) if not cont: - raise JrnlException(Message(MsgText.UpgradeAborted), MsgType.WARNING) + raise JrnlException(Message(MsgText.UpgradeAborted), MsgStyle.WARNING) for journal_name, path in encrypted_journals.items(): print_msg( @@ -158,10 +158,10 @@ def upgrade_jrnl(config_path): if len(failed_journals) > 0: raise JrnlException( - Message(MsgText.AbortingUpgrade, MsgType.WARNING), + Message(MsgText.AbortingUpgrade, MsgStyle.WARNING), Message( MsgText.JournalFailedUpgrade, - MsgType.ERROR, + MsgStyle.ERROR, { "s": "s" if len(failed_journals) > 1 else "", "failed_journals": "\n".join(j.name for j in failed_journals), @@ -173,11 +173,11 @@ def upgrade_jrnl(config_path): for j in all_journals: j.write() - print_msg(Message(MsgText.UpgradingConfig, MsgType.NORMAL)) + print_msg(Message(MsgText.UpgradingConfig, MsgStyle.NORMAL)) backup(config_path) - print_msg(Message(MsgText.AllDoneUpgrade, MsgType.NORMAL)) + print_msg(Message(MsgText.AllDoneUpgrade, MsgStyle.NORMAL)) def is_old_version(config_path): From 782b14c0f6a1c8c729688033e4904e28080e6aea Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sun, 1 May 2022 13:25:13 -0700 Subject: [PATCH 32/66] add comment --- jrnl/messages.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jrnl/messages.py b/jrnl/messages.py index fae7a015f..f0ae6ddfb 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -21,6 +21,7 @@ class MsgDecoration(Enum): "args": {} } BRACKET = { + # @todo this should be a more robust function "callback": lambda x, **kwargs: f"[ {x} ]", "args": {} } From e5fdf72fb67d9771263b6aad535d11996917c23d Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 7 May 2022 10:53:46 -0700 Subject: [PATCH 33/66] Move around code to separate concerns of each function a bit more --- jrnl/output.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/jrnl/output.py b/jrnl/output.py index 5022887a5..13d932d75 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -53,12 +53,7 @@ def print_msgs( args = style.decoration.args for msg in msgs: - args["border_style"] = msg.style.color - if msg.style == MsgStyle.ERROR: - args["title"] = "Error" - - if is_keyboard_int(msg): - print() + args = _add_extra_style_args_if_needed(args, msg=msg) m = format_msg(msg) m.append(delimiter) @@ -70,10 +65,24 @@ def print_msgs( Console(stderr=True).print(callback(text, **args)) +def _add_extra_style_args_if_needed(args, msg): + args["border_style"] = msg.style.color + if msg.style == MsgStyle.ERROR: + args["title"] = "Error" + return args + + def is_keyboard_int(msg: Message) -> bool: return msg.text == MsgText.KeyboardInterruptMsg def format_msg(msg: Message) -> Text: - text = textwrap.dedent(msg.text.value.format(**msg.params)).strip() + text = "" + + if is_keyboard_int(msg): + # extra line break for keyboard interrupts + text = "\n" + + text += textwrap.dedent(msg.text.value.format(**msg.params)).strip() return Text(text) + From 082e665a5038e0bfb7fafb89b0c058c1045b3aa0 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Mon, 16 May 2022 12:04:08 -0700 Subject: [PATCH 34/66] update create_password and yesno prompt functions for new messaging --- jrnl/messages.py | 14 ++++++-------- jrnl/prompt.py | 14 ++++++-------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index f0ae6ddfb..92dded07e 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -248,15 +248,13 @@ def __str__(self) -> str: """ # --- Password --- # - PasswordMaxTriesExceeded = """ - Too many attempts with wrong password - """ - - PasswordCanNotBeEmpty = """ - Password can't be empty! - """ - + PasswordFirstEntry = "Enter new password: " + PasswordConfirmEntry = "Enter password again: " + PasswordMaxTriesExceeded = "Too many attempts with wrong password" + PasswordCanNotBeEmpty = "Password can't be empty!" + PasswordDidNotMatch = "Passwords did not match, please try again" WrongPasswordTryAgain = "Wrong password, try again" + PasswordStoreInKeychain = "Do you want to store the password in your keychain?" # --- Search --- # NothingToDelete = """ diff --git a/jrnl/prompt.py b/jrnl/prompt.py index 1123026e8..caf5d56a0 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -10,27 +10,25 @@ from jrnl.output import print_msg -def create_password( - journal_name: str, prompt: str = "Enter password for new journal: " -) -> str: +def create_password(journal_name: str) -> str: while True: - pw = getpass.getpass(prompt) + pw = getpass.getpass(str(MsgText.PasswordFirstEntry)) if not pw: print_msg(Message(MsgText.PasswordCanNotBeEmpty, MsgStyle.PLAIN)) continue - elif pw == getpass.getpass("Enter password again: "): + elif pw == getpass.getpass(str(MsgText.PasswordConfirmEntry)): break - print("Passwords did not match, please try again", file=sys.stderr) + print_msg(Message(MsgText.PasswordDidNotMatch, MsgStyle.ERROR)) - if yesno("Do you want to store the password in your keychain?", default=True): + if yesno(str(MsgText.PasswordStoreInKeychain), default=True): from .EncryptedJournal import set_keychain set_keychain(journal_name, pw) return pw -def yesno(prompt, default=True): +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) From fcb8b2203e084a9a66876aab4902ee24f9f29217 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Mon, 16 May 2022 12:05:14 -0700 Subject: [PATCH 35/66] fix missing newline for keyboard interrupts --- jrnl/output.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/jrnl/output.py b/jrnl/output.py index 13d932d75..462a49106 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -49,20 +49,30 @@ def print_msgs( ) -> None: # Same as print_msg, but for a list text = Text("") - callback = style.decoration.callback + decoration_callback = style.decoration.callback args = style.decoration.args + prepend_newline = False for msg in msgs: - args = _add_extra_style_args_if_needed(args, msg=msg) + args = _add_extra_style_args_if_needed(args, msg=msg) + + if _needs_prepended_newline(msg): + prepend_newline = True m = format_msg(msg) m.append(delimiter) + text.append(m) text.rstrip() - # import ipdb; ipdb.sset_trace() - Console(stderr=True).print(callback(text, **args)) + # Always print messages to stderr + console = Console(stderr=True) + + console.print( + decoration_callback(text, **args), + new_line_start=prepend_newline, + ) def _add_extra_style_args_if_needed(args, msg): @@ -72,17 +82,15 @@ def _add_extra_style_args_if_needed(args, msg): return args +def _needs_prepended_newline(msg: Message) -> bool: + return is_keyboard_int(msg) + + def is_keyboard_int(msg: Message) -> bool: return msg.text == MsgText.KeyboardInterruptMsg def format_msg(msg: Message) -> Text: - text = "" - - if is_keyboard_int(msg): - # extra line break for keyboard interrupts - text = "\n" - - text += textwrap.dedent(msg.text.value.format(**msg.params)).strip() + text = textwrap.dedent(msg.text.value.format(**msg.params)).strip() return Text(text) From 59e3477043cb55e6b4ee9235a3293f9d16fbbb45 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Mon, 16 May 2022 12:05:38 -0700 Subject: [PATCH 36/66] fix misc linting --- jrnl/messages.py | 8 ++++---- jrnl/output.py | 7 +++---- jrnl/prompt.py | 1 - 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 92dded07e..7803a337d 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -17,13 +17,13 @@ class _MsgColor(NamedTuple): class MsgDecoration(Enum): NONE = { - "callback": lambda x, **kwargs: x, - "args": {} + "callback": lambda x, **_: x, + "args": {}, } BRACKET = { # @todo this should be a more robust function - "callback": lambda x, **kwargs: f"[ {x} ]", - "args": {} + "callback": lambda x, **_: f"[ {x} ]", + "args": {}, } BOX = { "callback": Panel, diff --git a/jrnl/output.py b/jrnl/output.py index 462a49106..b62202e14 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -2,10 +2,8 @@ # License: https://www.gnu.org/licenses/gpl-3.0.html import logging -import sys import textwrap -from rich import print from rich.text import Text from rich.console import Console @@ -45,7 +43,9 @@ def print_msg(msg: Message) -> None: def print_msgs( - msgs: list[Message], delimiter: str = "\n", style: MsgStyle = MsgStyle.NORMAL + msgs: list[Message], + delimiter: str = "\n", + style: MsgStyle = MsgStyle.NORMAL, ) -> None: # Same as print_msg, but for a list text = Text("") @@ -93,4 +93,3 @@ def is_keyboard_int(msg: Message) -> bool: def format_msg(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 caf5d56a0..3eae169ca 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -2,7 +2,6 @@ # License: https://www.gnu.org/licenses/gpl-3.0.html import getpass -import sys from jrnl.messages import Message from jrnl.messages import MsgText From 222a6969e619e1685c1f3c01c379f4303e82360b Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Mon, 16 May 2022 12:06:07 -0700 Subject: [PATCH 37/66] fix bug with panel titles always showing 'error' after one error --- jrnl/output.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jrnl/output.py b/jrnl/output.py index b62202e14..04e209db6 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -77,8 +77,7 @@ def print_msgs( def _add_extra_style_args_if_needed(args, msg): args["border_style"] = msg.style.color - if msg.style == MsgStyle.ERROR: - args["title"] = "Error" + args["title"] = "Error" if msg.style == MsgStyle.ERROR else None return args From a1758d5e9ace4a23b94ac2d25b53857fdc8c59c5 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Mon, 16 May 2022 12:09:00 -0700 Subject: [PATCH 38/66] fix missing import --- jrnl/EncryptedJournal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index 2e6cb3f3f..ef30504d7 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -25,6 +25,7 @@ from jrnl.messages import Message from jrnl.messages import MsgText from jrnl.messages import MsgStyle +from jrnl.output import print_msg def make_key(password): From d58cb312415f9cc437b6e16e82db89b4e84760ce Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 17 May 2022 08:37:02 -0700 Subject: [PATCH 39/66] update debug output after uncaught exception --- jrnl/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index c96556da8..391127095 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -67,8 +67,9 @@ def cli(manual_args=None): debug = True if debug: - print("\n") + from rich.console import Console traceback.print_tb(sys.exc_info()[2]) + Console(stderr=True).print_exception() print_msg( Message( From 3381ca752c0962fa15ec0988f20301ef17c51b2e Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 17 May 2022 08:43:49 -0700 Subject: [PATCH 40/66] update exception for new exception handling --- jrnl/install.py | 11 +++++++++-- jrnl/messages.py | 5 +++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/jrnl/install.py b/jrnl/install.py index ef0693efe..7f8b020ed 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -78,8 +78,15 @@ def load_or_install_jrnl(alt_config_path): config = load_config(config_path) if config is None: - print("Unable to parse config file", file=sys.stderr) - sys.exit() + raise JrnlException( + Message( + MsgText.CantParseConfigFile, + MsgStyle.ERROR, + { + "config_path": config_path, + }, + ) + ) if is_old_version(config_path): from jrnl import upgrade diff --git a/jrnl/messages.py b/jrnl/messages.py index 7803a337d..e2658ec33 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -121,6 +121,11 @@ def __str__(self) -> str: Removing this file will allow jrnl to save its configuration. """ + CantParseConfigFile = """ + Unable to parse config file at: + {config_path} + """ + LineWrapTooSmallForDateFormat = """ The provided linewrap value of {config_linewrap} is too small by {columns} columns to display the timestamps in the configured time From 4e4125155c5abd8c268ea7c6583d256e64d67165 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 17 May 2022 11:01:34 -0700 Subject: [PATCH 41/66] rewrite yesno function to use new centralized messages --- jrnl/Journal.py | 5 ++++- jrnl/install.py | 5 +---- jrnl/messages.py | 17 +++++++++++++++++ jrnl/output.py | 36 +++++++++++++++++++++++++++--------- jrnl/prompt.py | 29 ++++++++++++++++++++++++----- jrnl/upgrade.py | 2 +- 6 files changed, 74 insertions(+), 20 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 895437a84..0365bbe5c 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -289,7 +289,10 @@ def prompt_delete_entries(self): 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 7f8b020ed..ee004c53f 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 e2658ec33..46a81f309 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 @@ def __str__(self) -> str: 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 04e209db6..02d0056a4 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 3eae169ca..fcf179e9a 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -7,6 +7,7 @@ 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 15125ab47..2eaebee81 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) From 13b1046a059d8e34fd9a7ba857be038f5d822f0f Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 17 May 2022 11:01:50 -0700 Subject: [PATCH 42/66] reduce the debug output slightly --- jrnl/cli.py | 3 ++- jrnl/messages.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index 391127095..9bcb324ce 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -68,8 +68,9 @@ def cli(manual_args=None): if debug: from rich.console import Console + traceback.print_tb(sys.exc_info()[2]) - Console(stderr=True).print_exception() + Console(stderr=True).print_exception(extra_lines=1) print_msg( Message( diff --git a/jrnl/messages.py b/jrnl/messages.py index 46a81f309..6733835f1 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -114,6 +114,7 @@ def __str__(self) -> str: """ YesOrNoPromptDefaultYes = "[Y/n]" YesOrNoPromptDefaultNo = "[y/N]" + ContinueUpgrade = "Continue upgrading jrnl?" # these should be lowercase, if possible in language # "lowercase" means whatever `.lower()` returns From 70157303d435a30d05151502651f6ec0bc2eef46 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 17 May 2022 12:04:24 -0700 Subject: [PATCH 43/66] clean up print_msgs function --- jrnl/cli.py | 2 +- jrnl/editor.py | 2 +- jrnl/messages.py | 99 +++++++++++++++++++++-------------- jrnl/output.py | 59 ++++++--------------- jrnl/plugins/jrnl_importer.py | 2 +- jrnl/prompt.py | 2 +- 6 files changed, 81 insertions(+), 85 deletions(-) diff --git a/jrnl/cli.py b/jrnl/cli.py index 9bcb324ce..305e4502e 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -49,7 +49,7 @@ def cli(manual_args=None): print_msg( Message( MsgText.KeyboardInterruptMsg, - MsgStyle.ERROR, + MsgStyle.ERROR_ON_NEW_LINE, ) ) diff --git a/jrnl/editor.py b/jrnl/editor.py index 68e49fef9..91aa4d527 100644 --- a/jrnl/editor.py +++ b/jrnl/editor.py @@ -66,7 +66,7 @@ def get_text_from_stdin(): except KeyboardInterrupt: logging.error("Write mode: keyboard interrupt") raise JrnlException( - Message(MsgText.KeyboardInterruptMsg, MsgStyle.ERROR), + Message(MsgText.KeyboardInterruptMsg, MsgStyle.ERROR_ON_NEW_LINE), Message(MsgText.JournalNotSaved, MsgStyle.WARNING), ) diff --git a/jrnl/messages.py b/jrnl/messages.py index 6733835f1..ef7637973 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -44,45 +44,6 @@ def args(self) -> dict: return self.value["args"] -class MsgStyle(Enum): - BARE = { - "decoration": MsgDecoration.NONE, - "color": _MsgColor("white"), - } - PLAIN = { - "decoration": MsgDecoration.BRACKET, - "color": _MsgColor("white"), - } - PROMPT = { - "decoration": MsgDecoration.NONE, - "color": _MsgColor("white"), - } - TITLE = { - "decoration": MsgDecoration.BOX, - "color": _MsgColor("cyan"), - } - NORMAL = { - "decoration": MsgDecoration.BOX, - "color": _MsgColor("white"), - } - WARNING = { - "decoration": MsgDecoration.BOX, - "color": _MsgColor("yellow"), - } - ERROR = { - "decoration": MsgDecoration.BOX, - "color": _MsgColor("red"), - } - - @property - def decoration(self) -> MsgDecoration: - return self.value["decoration"] - - @property - def color(self) -> _MsgColor: - return self.value["color"].color - - class MsgText(Enum): def __str__(self) -> str: return self.value @@ -122,6 +83,7 @@ def __str__(self) -> str: OneCharacterNo = "n" # --- Exceptions ---# + Error = "Error" UncaughtException = """ {name} {exception} @@ -320,6 +282,65 @@ def __str__(self) -> str: """ +class MsgStyle(Enum): + BARE = { + "decoration": MsgDecoration.NONE, + "color": _MsgColor("white"), + } + PLAIN = { + "decoration": MsgDecoration.BRACKET, + "color": _MsgColor("white"), + } + PROMPT = { + "decoration": MsgDecoration.NONE, + "color": _MsgColor("white"), + "append_space": True, + } + TITLE = { + "decoration": MsgDecoration.BOX, + "color": _MsgColor("cyan"), + } + NORMAL = { + "decoration": MsgDecoration.BOX, + "color": _MsgColor("white"), + } + WARNING = { + "decoration": MsgDecoration.BOX, + "color": _MsgColor("yellow"), + } + ERROR = { + "decoration": MsgDecoration.BOX, + "color": _MsgColor("red"), + "box_title": str(MsgText.Error), + } + ERROR_ON_NEW_LINE = { + "decoration": MsgDecoration.BOX, + "color": _MsgColor("red"), + "prepend_newline": True, + "box_title": str(MsgText.Error), + } + + @property + def decoration(self) -> MsgDecoration: + return self.value["decoration"] + + @property + def color(self) -> _MsgColor: + return self.value["color"].color + + @property + def prepend_newline(self) -> bool: + return self.value.get("prepend_newline", False) + + @property + def append_space(self) -> bool: + return self.value.get("append_space", False) + + @property + def box_title(self) -> MsgText: + return self.value.get("box_title", None) + + class Message(NamedTuple): text: MsgText style: MsgStyle = MsgStyle.NORMAL diff --git a/jrnl/output.py b/jrnl/output.py index 02d0056a4..69346773f 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -39,74 +39,49 @@ def list_journals(configuration): return result -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_msg(msg: Message) -> Union[None, str]: + """Helper function to print a single message""" + return print_msgs([msg], style=msg.style) def print_msgs( msgs: list[Message], delimiter: str = "\n", style: MsgStyle = MsgStyle.NORMAL, - is_prompt: bool = False, + get_input: 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 + text = Text("", end="") + kwargs = style.decoration.args - for msg in msgs: - args = _add_extra_style_args_if_needed(args, msg=msg) - - if _needs_prepended_newline(msg): - prepend_newline = True - - if _needs_appended_space(msg): - append_space = True + for i, msg in enumerate(msgs): + kwargs = _add_extra_style_args_if_needed(kwargs, msg=msg) m = format_msg_text(msg) - m.append(delimiter) - text.append(m) + if i != len(msgs) - 1: + m.append(delimiter) - text.rstrip() + text.append(m) - if append_space: + if style.append_space: text.append(" ") + decorated_text = style.decoration.callback(text, **kwargs) + # Always print messages to stderr console = Console(stderr=True) - decorated_text = decoration_callback(text, **args) - - if is_prompt: + if get_input: return str(console.input(prompt=decorated_text)) - else: - console.print(decorated_text, new_line_start=prepend_newline) + console.print(decorated_text, new_line_start=style.prepend_newline) def _add_extra_style_args_if_needed(args, msg): args["border_style"] = msg.style.color - args["title"] = "Error" if msg.style == MsgStyle.ERROR else None + args["title"] = msg.style.box_title return args -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_text(msg: Message) -> Text: text = textwrap.dedent(msg.text.value.format(**msg.params)).strip() return Text(text) diff --git a/jrnl/plugins/jrnl_importer.py b/jrnl/plugins/jrnl_importer.py index 39f53f854..cfdee0d77 100644 --- a/jrnl/plugins/jrnl_importer.py +++ b/jrnl/plugins/jrnl_importer.py @@ -29,7 +29,7 @@ def import_(journal, input=None): other_journal_txt = sys.stdin.read() except KeyboardInterrupt: raise JrnlException( - Message(MsgText.KeyboardInterruptMsg, MsgStyle.ERROR), + Message(MsgText.KeyboardInterruptMsg, MsgStyle.ERROR_ON_NEW_LINE), Message(MsgText.ImportAborted, MsgStyle.WARNING), ) diff --git a/jrnl/prompt.py b/jrnl/prompt.py index fcf179e9a..91c9d69a0 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -40,7 +40,7 @@ def yesno(prompt: Message, default: bool = True) -> bool: ], style=MsgStyle.PROMPT, delimiter=" ", - is_prompt=True, + get_input=True, ) answers = { From 1a6afd9f4af355aef744a11a45a2bdc7d71f6436 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 17 May 2022 12:34:19 -0700 Subject: [PATCH 44/66] clean up create_password function --- jrnl/output.py | 8 +++++--- jrnl/prompt.py | 17 ++++++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/jrnl/output.py b/jrnl/output.py index 69346773f..a91dae62f 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -39,9 +39,10 @@ def list_journals(configuration): return result -def print_msg(msg: Message) -> Union[None, str]: +def print_msg(msg: Message, **kwargs) -> Union[None, str]: """Helper function to print a single message""" - return print_msgs([msg], style=msg.style) + kwargs["style"] = msg.style + return print_msgs([msg], **kwargs) def print_msgs( @@ -49,6 +50,7 @@ def print_msgs( delimiter: str = "\n", style: MsgStyle = MsgStyle.NORMAL, get_input: bool = False, + screen_input: bool = False, ) -> Union[None, str]: # Same as print_msg, but for a list text = Text("", end="") @@ -72,7 +74,7 @@ def print_msgs( # Always print messages to stderr console = Console(stderr=True) if get_input: - return str(console.input(prompt=decorated_text)) + return str(console.input(prompt=decorated_text, password=screen_input)) console.print(decorated_text, new_line_start=style.prepend_newline) diff --git a/jrnl/prompt.py b/jrnl/prompt.py index 91c9d69a0..c9bbb1ea7 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -1,8 +1,5 @@ # Copyright (C) 2012-2021 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html - -import getpass - from jrnl.messages import Message from jrnl.messages import MsgText from jrnl.messages import MsgStyle @@ -11,20 +8,26 @@ def create_password(journal_name: str) -> str: + kwargs = { + "get_input": True, + "screen_input": True, + } while True: - pw = getpass.getpass(str(MsgText.PasswordFirstEntry)) + pw = print_msg(Message(MsgText.PasswordFirstEntry, MsgStyle.PROMPT), **kwargs) + if not pw: - print_msg(Message(MsgText.PasswordCanNotBeEmpty, MsgStyle.PLAIN)) + print_msg(Message(MsgText.PasswordCanNotBeEmpty, MsgStyle.WARNING)) continue - elif pw == getpass.getpass(str(MsgText.PasswordConfirmEntry)): + + elif pw == print_msg(Message(MsgText.PasswordConfirmEntry, MsgStyle.PROMPT), **kwargs): break print_msg(Message(MsgText.PasswordDidNotMatch, MsgStyle.ERROR)) if yesno(Message(MsgText.PasswordStoreInKeychain), default=True): from .EncryptedJournal import set_keychain - set_keychain(journal_name, pw) + return pw From fabcd53c6fa8d5a834eddd9b183aa0dcbc561e8f Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Tue, 17 May 2022 12:58:07 -0700 Subject: [PATCH 45/66] clean up misc linting --- jrnl/EncryptedJournal.py | 1 - jrnl/Journal.py | 1 - jrnl/output.py | 2 -- jrnl/plugins/yaml_exporter.py | 1 - jrnl/prompt.py | 5 ++++- jrnl/upgrade.py | 3 +-- 6 files changed, 5 insertions(+), 8 deletions(-) diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index ef30504d7..69c73681e 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -3,7 +3,6 @@ import hashlib import logging import os -import sys from typing import Callable from typing import Optional diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 0365bbe5c..061463d37 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -6,7 +6,6 @@ import logging import os import re -import sys from . import Entry from . import time diff --git a/jrnl/output.py b/jrnl/output.py index a91dae62f..37caa24c0 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -1,7 +1,5 @@ # Copyright (C) 2012-2021 jrnl contributors # License: https://www.gnu.org/licenses/gpl-3.0.html - -import logging import textwrap from typing import Union diff --git a/jrnl/plugins/yaml_exporter.py b/jrnl/plugins/yaml_exporter.py index 4db1f97a1..f78c781b5 100644 --- a/jrnl/plugins/yaml_exporter.py +++ b/jrnl/plugins/yaml_exporter.py @@ -4,7 +4,6 @@ import os import re -import sys from .text_exporter import TextExporter diff --git a/jrnl/prompt.py b/jrnl/prompt.py index c9bbb1ea7..a0efd5395 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -19,13 +19,16 @@ def create_password(journal_name: str) -> str: print_msg(Message(MsgText.PasswordCanNotBeEmpty, MsgStyle.WARNING)) continue - elif pw == print_msg(Message(MsgText.PasswordConfirmEntry, MsgStyle.PROMPT), **kwargs): + elif pw == print_msg( + Message(MsgText.PasswordConfirmEntry, MsgStyle.PROMPT), **kwargs + ): break print_msg(Message(MsgText.PasswordDidNotMatch, MsgStyle.ERROR)) if yesno(Message(MsgText.PasswordStoreInKeychain), default=True): from .EncryptedJournal import set_keychain + set_keychain(journal_name, pw) return pw diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index 2eaebee81..fc8c581ed 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -2,7 +2,6 @@ # License: https://www.gnu.org/licenses/gpl-3.0.html import os -import sys from . import Journal from . import __version__ @@ -32,7 +31,7 @@ def backup(filename, binary=False): print_msg( Message( - MsgText.BackupCreated, MsgStyle.NORMAL, {"filename": f"filename.backup"} + MsgText.BackupCreated, MsgStyle.NORMAL, {"filename": "filename.backup"} ) ) From 32ab358bc2405a8cb4ab9e4a2ce66976322455cf Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Wed, 18 May 2022 12:18:24 -0700 Subject: [PATCH 46/66] WIP --- tests/lib/fixtures.py | 2 ++ tests/unit/test_output.py | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/unit/test_output.py diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index 8d820db6e..22ec5f599 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -195,6 +195,8 @@ def _mock_password(): if not password: password = Exception("Unexpected call for password") + # @todo replace with with rich.console.Console().input(password=True) + # since getpass is no longer used return patch("getpass.getpass", side_effect=password) return {"getpass": _mock_password} diff --git a/tests/unit/test_output.py b/tests/unit/test_output.py new file mode 100644 index 000000000..050375ff5 --- /dev/null +++ b/tests/unit/test_output.py @@ -0,0 +1,27 @@ +# Copyright (C) 2012-2021 jrnl contributors +# License: https://www.gnu.org/licenses/gpl-3.0.html +from unittest.mock import Mock +from unittest.mock import patch + +from jrnl.messages import Message +from jrnl.output import print_msg + + +@patch("jrnl.output.print_msgs") +def test_print_msg_calls_print_msgs_as_list_with_style(print_msgs): + test_msg = Mock(Message) + print_msg(test_msg) + print_msgs.assert_called_once_with([test_msg], style=test_msg.style) + + +@patch("jrnl.output.print_msgs") +def test_print_msg_calls_print_msgs_with_kwargs(print_msgs): + test_msg = Mock(Message) + kwargs = { + "delimter": "test delimiter 🤡", + "get_input": True, + "screen_input": True, + "some_rando_arg": "💩", + } + print_msg(test_msg, **kwargs) + print_msgs.assert_called_once_with([test_msg], style=test_msg.style, **kwargs) From ebfc7bd965956c51e2f24de9f5a148ae06c07972 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 28 May 2022 10:29:04 -0700 Subject: [PATCH 47/66] rename screen_input to hide_input to be more clear --- jrnl/output.py | 4 ++-- jrnl/prompt.py | 2 +- tests/unit/test_output.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/jrnl/output.py b/jrnl/output.py index 37caa24c0..85e17a624 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -48,7 +48,7 @@ def print_msgs( delimiter: str = "\n", style: MsgStyle = MsgStyle.NORMAL, get_input: bool = False, - screen_input: bool = False, + hide_input: bool = False, ) -> Union[None, str]: # Same as print_msg, but for a list text = Text("", end="") @@ -72,7 +72,7 @@ def print_msgs( # Always print messages to stderr console = Console(stderr=True) if get_input: - return str(console.input(prompt=decorated_text, password=screen_input)) + return str(console.input(prompt=decorated_text, password=hide_input)) console.print(decorated_text, new_line_start=style.prepend_newline) diff --git a/jrnl/prompt.py b/jrnl/prompt.py index a0efd5395..74f5f1fb8 100644 --- a/jrnl/prompt.py +++ b/jrnl/prompt.py @@ -10,7 +10,7 @@ def create_password(journal_name: str) -> str: kwargs = { "get_input": True, - "screen_input": True, + "hide_input": True, } while True: pw = print_msg(Message(MsgText.PasswordFirstEntry, MsgStyle.PROMPT), **kwargs) diff --git a/tests/unit/test_output.py b/tests/unit/test_output.py index 050375ff5..0fa9f45bc 100644 --- a/tests/unit/test_output.py +++ b/tests/unit/test_output.py @@ -20,7 +20,7 @@ def test_print_msg_calls_print_msgs_with_kwargs(print_msgs): kwargs = { "delimter": "test delimiter 🤡", "get_input": True, - "screen_input": True, + "hide_input": True, "some_rando_arg": "💩", } print_msg(test_msg, **kwargs) From 71eb7462bef809f4c0fb00f65b9c1d28ef902871 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 28 May 2022 13:49:54 -0700 Subject: [PATCH 48/66] update encrypted journal prompt to use new messaging functionality --- jrnl/EncryptedJournal.py | 9 ++++++--- jrnl/messages.py | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index 69c73681e..dbd2c501a 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -1,5 +1,4 @@ import base64 -import getpass import hashlib import logging import os @@ -46,8 +45,12 @@ def decrypt_content( keychain: str = None, max_attempts: int = 3, ) -> str: + get_pw = lambda: print_msg( + Message(MsgText.Password, MsgStyle.PROMPT), get_input=True, hide_input=True + ) + pwd_from_keychain = keychain and get_keychain(keychain) - password = pwd_from_keychain or getpass.getpass() + password = pwd_from_keychain or get_pw() result = decrypt_func(password) # Password is bad: if result is None and pwd_from_keychain: @@ -55,7 +58,7 @@ def decrypt_content( attempt = 1 while result is None and attempt < max_attempts: print_msg(Message(MsgText.WrongPasswordTryAgain, MsgStyle.WARNING)) - password = getpass.getpass() + password = get_pw() result = decrypt_func(password) attempt += 1 diff --git a/jrnl/messages.py b/jrnl/messages.py index ef7637973..7c40b60a3 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -233,6 +233,7 @@ def __str__(self) -> str: """ # --- Password --- # + Password = "Password:" PasswordFirstEntry = "Enter new password: " PasswordConfirmEntry = "Enter password again: " PasswordMaxTriesExceeded = "Too many attempts with wrong password" From bdbd3ca2a60b17f18d7c58404eb75c2935c5ce4d Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 28 May 2022 13:50:10 -0700 Subject: [PATCH 49/66] fix typo in message key --- jrnl/EncryptedJournal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index dbd2c501a..f0cd8ebe9 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -96,7 +96,7 @@ def open(self, filename=None): Message( MsgText.JournalCreated, MsgStyle.NORMAL, - {"journal_name": self.name, "path": filename}, + {"journal_name": self.name, "filename": filename}, ) ) From 4636f1a3ef41d5ecee9d76b85835c2e38f59a224 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 28 May 2022 13:51:02 -0700 Subject: [PATCH 50/66] move rich console into function so we can mock properly --- jrnl/output.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jrnl/output.py b/jrnl/output.py index 85e17a624..fb21990bc 100644 --- a/jrnl/output.py +++ b/jrnl/output.py @@ -70,12 +70,17 @@ def print_msgs( decorated_text = style.decoration.callback(text, **kwargs) # Always print messages to stderr - console = Console(stderr=True) + console = _get_console(stderr=True) + if get_input: return str(console.input(prompt=decorated_text, password=hide_input)) console.print(decorated_text, new_line_start=style.prepend_newline) +def _get_console(stderr: bool = True) -> Console: + return Console(stderr=stderr) + + def _add_extra_style_args_if_needed(args, msg): args["border_style"] = msg.style.color args["title"] = msg.style.box_title From 1461f03511afb436ed37f22a3c677dcd40197c68 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 28 May 2022 13:52:17 -0700 Subject: [PATCH 51/66] update password mock to use rich console instead of getpass --- tests/lib/fixtures.py | 13 +++++++++---- tests/lib/then_steps.py | 4 ++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index 22ec5f599..a294a2708 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -10,6 +10,7 @@ from keyring import errors from pytest import fixture from unittest.mock import patch +from unittest.mock import Mock from .helpers import get_fixture import toml @@ -183,6 +184,8 @@ def toml_version(working_dir): @fixture def mock_password(request): def _mock_password(): + from rich.console import Console + password = get_fixture(request, "password") user_input = get_fixture(request, "user_input") @@ -195,11 +198,13 @@ def _mock_password(): if not password: password = Exception("Unexpected call for password") - # @todo replace with with rich.console.Console().input(password=True) - # since getpass is no longer used - return patch("getpass.getpass", side_effect=password) + mock_console = Mock(wraps=Console(stderr=True)) + mock_console.input = Mock() + mock_console.input.side_effect = password + + return patch("jrnl.output._get_console", return_value=mock_console) - return {"getpass": _mock_password} + return {"rich_console_input": _mock_password} @fixture diff --git a/tests/lib/then_steps.py b/tests/lib/then_steps.py index 4ef1681bf..e78eb6e7e 100644 --- a/tests/lib/then_steps.py +++ b/tests/lib/then_steps.py @@ -164,12 +164,12 @@ def config_var_in_memory( @then("we should be prompted for a password") def password_was_called(cli_run): - assert cli_run["mocks"]["getpass"].called + assert cli_run["mocks"]["rich_console_input"].called @then("we should not be prompted for a password") def password_was_not_called(cli_run): - assert not cli_run["mocks"]["getpass"].called + assert not cli_run["mocks"]["rich_console_input"].called @then(parse("the cache directory should contain the files\n{file_list}")) From a15807a46b835b46b03152253b1a0144fe88c5e1 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 28 May 2022 13:52:33 -0700 Subject: [PATCH 52/66] add more helpful output to then step --- tests/lib/then_steps.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/lib/then_steps.py b/tests/lib/then_steps.py index e78eb6e7e..59ee4a2fa 100644 --- a/tests/lib/then_steps.py +++ b/tests/lib/then_steps.py @@ -47,20 +47,21 @@ def output_should_contain( ): we_should = parse_should_or_should_not(should_or_should_not) + output_str = f"\nEXPECTED:\n{expected_output}\n\nACTUAL STDOUT:\n{cli_run['stdout']}\n\nACTUAL STDERR:\n{cli_run['stderr']}" assert expected_output if which_output_stream is None: assert ((expected_output in cli_run["stdout"]) == we_should) or ( (expected_output in cli_run["stderr"]) == we_should - ) + ), output_str elif which_output_stream == "standard": - assert (expected_output in cli_run["stdout"]) == we_should + assert (expected_output in cli_run["stdout"]) == we_should, output_str elif which_output_stream == "error": - assert (expected_output in cli_run["stderr"]) == we_should + assert (expected_output in cli_run["stderr"]) == we_should, output_str else: - assert (expected_output in cli_run[which_output_stream]) == we_should + assert (expected_output in cli_run[which_output_stream]) == we_should, output_str @then(parse("the output should not contain\n{expected_output}")) From bf8e779c65415b6db7e595472dd3f12008ba1b3b Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 28 May 2022 13:52:44 -0700 Subject: [PATCH 53/66] fix test by updating expected output --- tests/bdd/features/config_file.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bdd/features/config_file.feature b/tests/bdd/features/config_file.feature index 0bba70377..168cd7aaf 100644 --- a/tests/bdd/features/config_file.feature +++ b/tests/bdd/features/config_file.feature @@ -73,7 +73,7 @@ Feature: Multiple journals these three eyes these three eyes n - Then the output should contain "Encrypted journal 'new_encrypted' created" + Then the output should contain "Journal 'new_encrypted' created at " Scenario: Don't overwrite main config when encrypting a journal in an alternate config Given the config "basic_onefile.yaml" exists From d2f2967bc114968544a01f56a0a7ec2a8029da4f Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 28 May 2022 15:12:36 -0700 Subject: [PATCH 54/66] update message to use new functionality --- jrnl/install.py | 8 ++++++-- jrnl/messages.py | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/jrnl/install.py b/jrnl/install.py index ee004c53f..4aff7cac4 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -109,8 +109,12 @@ def install(): # Where to create the journal? default_journal_path = get_default_journal_path() - path_query = f"Path to your journal file (leave blank for {default_journal_path}): " - journal_path = os.path.abspath(input(path_query).strip() or default_journal_path) + user_given_path = print_msg( + Message(MsgText.InstallJournalPathQuestion, MsgStyle.PROMPT), + get_input=True, + ) + journal_path = os.path.abspath(user_given_path or default_journal_path) + default_config = get_default_config() default_config["journals"][DEFAULT_JOURNAL_KEY] = os.path.expanduser( os.path.expandvars(journal_path) diff --git a/jrnl/messages.py b/jrnl/messages.py index 7c40b60a3..9b5c95c15 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -69,6 +69,9 @@ def __str__(self) -> str: AllDoneUpgrade = "We're all done here and you can start enjoying jrnl 2" # --- Prompts --- # + InstallJournalPathQuestion = """ + Path to your journal file (leave blank for {default_journal_path}): + """ DeleteEntryQuestion = "Delete entry '{entry_title}'?" EncryptJournalQuestion = """ Do you want to encrypt your journal? (You can always change this later) From 34df64c989bf3ed44acafccac8d9ab4ca421799c Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 28 May 2022 15:13:27 -0700 Subject: [PATCH 55/66] rework mocks in test suite for new messaging functionality --- tests/bdd/features/datetime.feature | 2 +- tests/lib/fixtures.py | 74 +++++++++++++---------------- tests/lib/then_steps.py | 4 +- tests/lib/when_steps.py | 6 +-- 4 files changed, 41 insertions(+), 45 deletions(-) diff --git a/tests/bdd/features/datetime.feature b/tests/bdd/features/datetime.feature index 7676f1b28..0b5ce472c 100644 --- a/tests/bdd/features/datetime.feature +++ b/tests/bdd/features/datetime.feature @@ -69,7 +69,7 @@ Feature: Reading and writing to journal with custom date formats Scenario: Writing an entry at the prompt with custom date Given we use the config "little_endian_dates.yaml" - When we run "jrnl" and enter "2013-05-10: I saw Elvis. He's alive." + When we run "jrnl" and pipe "2013-05-10: I saw Elvis. He's alive." Then we should get no error When we run "jrnl -999" Then the output should contain "10.05.2013 09:00 I saw Elvis." diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index a294a2708..c843eacfc 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -86,7 +86,7 @@ def cli_run( mock_editor, mock_user_input, mock_overrides, - mock_password, + mock_piped_input, ): # Check if we need more mocks mock_factories.update(mock_args) @@ -95,7 +95,7 @@ def cli_run( mock_factories.update(mock_editor) mock_factories.update(mock_config_path) mock_factories.update(mock_user_input) - mock_factories.update(mock_password) + mock_factories.update(mock_piped_input) return { "status": 0, @@ -181,32 +181,6 @@ def toml_version(working_dir): return pyproject_contents["tool"]["poetry"]["version"] -@fixture -def mock_password(request): - def _mock_password(): - from rich.console import Console - - password = get_fixture(request, "password") - user_input = get_fixture(request, "user_input") - - if password: - password = password.splitlines() - - elif user_input: - password = user_input.splitlines() - - if not password: - password = Exception("Unexpected call for password") - - mock_console = Mock(wraps=Console(stderr=True)) - mock_console.input = Mock() - mock_console.input.side_effect = password - - return patch("jrnl.output._get_console", return_value=mock_console) - - return {"rich_console_input": _mock_password} - - @fixture def input_method(): return "" @@ -228,23 +202,43 @@ def should_not(): @fixture -def mock_user_input(request, is_tty): - def _generator(target): - def _mock_user_input(): - user_input = get_fixture(request, "user_input", None) +def mock_piped_input(request, is_tty): + def _mock_piped_input(): + piped_input = get_fixture(request, "all_input", None) + + if piped_input is None: + piped_input = Exception("Unexpected call for piped input") + else: + piped_input = piped_input.splitlines() + + if is_tty: + piped_input = [] + + return patch("sys.stdin.read", side_effect=piped_input) - if user_input is None: - user_input = Exception("Unexpected call for user input") - else: - user_input = user_input.splitlines() if is_tty else [user_input] + return {"piped_input": _mock_piped_input} - return patch(target, side_effect=user_input) - return _mock_user_input +@fixture +def mock_user_input(request): + def _mock_user_input(): + from rich.console import Console + + user_input = get_fixture(request, "all_input", None) + + if user_input is None: + user_input = Exception("Unexpected call for user input") + else: + user_input = user_input.splitlines() + + mock_console = Mock(wraps=Console(stderr=True)) + mock_console.input = Mock() + mock_console.input.side_effect = user_input + + return patch("jrnl.output._get_console", return_value=mock_console) return { - "stdin": _generator("sys.stdin.read"), - "input": _generator("builtins.input"), + "user_input": _mock_user_input } diff --git a/tests/lib/then_steps.py b/tests/lib/then_steps.py index 59ee4a2fa..f13d27bf0 100644 --- a/tests/lib/then_steps.py +++ b/tests/lib/then_steps.py @@ -61,7 +61,9 @@ def output_should_contain( assert (expected_output in cli_run["stderr"]) == we_should, output_str else: - assert (expected_output in cli_run[which_output_stream]) == we_should, output_str + assert ( + expected_output in cli_run[which_output_stream] + ) == we_should, output_str @then(parse("the output should not contain\n{expected_output}")) diff --git a/tests/lib/when_steps.py b/tests/lib/when_steps.py index 60302c7c6..6e60f61cd 100644 --- a/tests/lib/when_steps.py +++ b/tests/lib/when_steps.py @@ -22,11 +22,11 @@ def when_we_change_directory(directory_name): # These variables are used in the `@when(re(...))` section below command = '(?P[^"]*)' input_method = "(?Penter|pipe)" -user_input = '("(?P[^"]*)")' +all_input = '("(?P[^"]*)")' -@when(parse('we run "jrnl {command}" and {input_method}\n{user_input}')) -@when(re(f'we run "jrnl ?{command}" and {input_method} {user_input}')) +@when(parse('we run "jrnl {command}" and {input_method}\n{all_input}')) +@when(re(f'we run "jrnl ?{command}" and {input_method} {all_input}')) @when(parse('we run "jrnl {command}"')) @when('we run "jrnl"') def we_run_jrnl(cli_run, capsys, keyring): From c325ba38eae4b80165907569a2c422adf8f92281 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 4 Jun 2022 17:07:41 -0700 Subject: [PATCH 56/66] fix linting issue --- jrnl/EncryptedJournal.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jrnl/EncryptedJournal.py b/jrnl/EncryptedJournal.py index f0cd8ebe9..fb4ba5ce7 100644 --- a/jrnl/EncryptedJournal.py +++ b/jrnl/EncryptedJournal.py @@ -45,9 +45,10 @@ def decrypt_content( keychain: str = None, max_attempts: int = 3, ) -> str: - get_pw = lambda: print_msg( - Message(MsgText.Password, MsgStyle.PROMPT), get_input=True, hide_input=True - ) + def get_pw(): + return print_msg( + Message(MsgText.Password, MsgStyle.PROMPT), get_input=True, hide_input=True + ) pwd_from_keychain = keychain and get_keychain(keychain) password = pwd_from_keychain or get_pw() From 5cb46f572d722b313e2e2e22b60ae069f7792d95 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 4 Jun 2022 17:08:29 -0700 Subject: [PATCH 57/66] fix more tests --- jrnl/install.py | 8 ++++++- jrnl/upgrade.py | 1 - tests/bdd/features/encrypt.feature | 6 +++--- tests/bdd/features/format.feature | 2 +- tests/bdd/features/multiple_journals.feature | 2 +- tests/bdd/features/star.feature | 3 ++- tests/bdd/features/upgrade.feature | 2 +- tests/bdd/features/write.feature | 14 ++++++------- tests/lib/fixtures.py | 22 ++++++++++++++------ tests/lib/then_steps.py | 6 +++--- 10 files changed, 40 insertions(+), 26 deletions(-) diff --git a/jrnl/install.py b/jrnl/install.py index 4aff7cac4..d119787c7 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -110,7 +110,13 @@ def install(): # Where to create the journal? default_journal_path = get_default_journal_path() user_given_path = print_msg( - Message(MsgText.InstallJournalPathQuestion, MsgStyle.PROMPT), + Message( + MsgText.InstallJournalPathQuestion, + MsgStyle.PROMPT, + params={ + "default_journal_path": default_journal_path, + }, + ), get_input=True, ) journal_path = os.path.abspath(user_given_path or default_journal_path) diff --git a/jrnl/upgrade.py b/jrnl/upgrade.py index fc8c581ed..4108272dc 100644 --- a/jrnl/upgrade.py +++ b/jrnl/upgrade.py @@ -83,7 +83,6 @@ def upgrade_jrnl(config_path): kwargs = { # longest journal name "pad": max([len(journal) for journal in config["journals"]]), - "version": __version__, } _print_journal_summary( diff --git a/tests/bdd/features/encrypt.feature b/tests/bdd/features/encrypt.feature index 7f466c1d0..5d4152488 100644 --- a/tests/bdd/features/encrypt.feature +++ b/tests/bdd/features/encrypt.feature @@ -2,8 +2,8 @@ Feature: Encrypting and decrypting journals Scenario: Decrypting a journal Given we use the config "encrypted.yaml" - # And we use the password "bad doggie no biscuit" if prompted - When we run "jrnl --decrypt" and enter "bad doggie no biscuit" + And we use the password "bad doggie no biscuit" if prompted + When we run "jrnl --decrypt" Then the output should contain "Journal decrypted" And the config for journal "default" should contain "encrypt: false" When we run "jrnl -99 --short" @@ -46,7 +46,7 @@ Feature: Encrypting and decrypting journals Scenario Outline: Running jrnl with encrypt: true on unencryptable journals Given we use the config "" When we run "jrnl --config-override encrypt true here is a new entry" - Then the error output should contain "this type of journal can't be encrypted" + Then the error output should contain "journal can't be encrypted" Examples: configs | config_file | diff --git a/tests/bdd/features/format.feature b/tests/bdd/features/format.feature index 3f0decb22..b1c120ade 100644 --- a/tests/bdd/features/format.feature +++ b/tests/bdd/features/format.feature @@ -429,7 +429,7 @@ Feature: Custom formats Given we use the config "" And we use the password "test" if prompted When we run "jrnl --export yaml --file nonexistent_dir" - Then the output should contain "YAML export must be to individual files" + Then the output should contain "YAML export must be to a directory" And the output should not contain "Traceback" Examples: configs diff --git a/tests/bdd/features/multiple_journals.feature b/tests/bdd/features/multiple_journals.feature index 35df069bb..09bd71fdd 100644 --- a/tests/bdd/features/multiple_journals.feature +++ b/tests/bdd/features/multiple_journals.feature @@ -87,7 +87,7 @@ Feature: Multiple journals these three eyes these three eyes n - Then the output should contain "Encrypted journal 'new_encrypted' created" + Then the output should contain "Journal 'new_encrypted' created at" Scenario: Read and write to journal with emoji name Given we use the config "multiple.yaml" diff --git a/tests/bdd/features/star.feature b/tests/bdd/features/star.feature index f1340f7ba..f59a7ecc0 100644 --- a/tests/bdd/features/star.feature +++ b/tests/bdd/features/star.feature @@ -29,7 +29,8 @@ Feature: Starring entries Scenario: Starring an entry will mark it in an encrypted journal Given we use the config "encrypted.yaml" - When we run "jrnl 20 july 2013 *: Best day of my life!" and enter "bad doggie no biscuit" + And we use the password "bad doggie no biscuit" if prompted + When we run "jrnl 20 july 2013 *: Best day of my life!" Then the output should contain "Entry added" When we run "jrnl -on 2013-07-20 -starred" and enter "bad doggie no biscuit" Then the output should contain "2013-07-20 09:00 Best day of my life!" diff --git a/tests/bdd/features/upgrade.feature b/tests/bdd/features/upgrade.feature index e5714a82c..23628eb72 100644 --- a/tests/bdd/features/upgrade.feature +++ b/tests/bdd/features/upgrade.feature @@ -49,6 +49,6 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x When we run "jrnl --list" and enter Y bad doggie no biscuit - Then the output should contain "Error: features/journals/missing.journal does not exist." + Then the output should contain "features/journals/missing.journal does not exist." And the output should contain "We're all done" And we should get no error diff --git a/tests/bdd/features/write.feature b/tests/bdd/features/write.feature index 608bc7725..649319601 100644 --- a/tests/bdd/features/write.feature +++ b/tests/bdd/features/write.feature @@ -233,8 +233,7 @@ Feature: Writing new entries. And we append to the editor if opened [2021-11-13] worked on jrnl tests When we run "jrnl --edit" - Then the output should contain - [1 entry added] + Then the output should contain "1 entry added" Examples: configs | config_file | @@ -252,8 +251,7 @@ Feature: Writing new entries. [2021-11-12] worked on jrnl tests again [2021-11-13] worked on jrnl tests a little bit more When we run "jrnl --edit" - Then the output should contain - [3 entries added] + Then the error output should contain "3 entries added" Examples: configs | config_file | @@ -269,8 +267,8 @@ Feature: Writing new entries. And we write to the editor if opened [2021-11-13] I am replacing my whole journal with this entry When we run "jrnl --edit" - Then the output should contain - [2 entries deleted, 1 entry modified] + Then the output should contain "2 entries deleted" + Then the output should contain "1 entry modified" Examples: configs | config_file | @@ -287,7 +285,7 @@ Feature: Writing new entries. [2021-11-13] I am replacing the last entry with this entry When we run "jrnl --edit -1" Then the output should contain - [1 entry modified] + 1 entry modified Examples: configs | config_file | @@ -304,7 +302,7 @@ Feature: Writing new entries. This is a small addendum to my latest entry. When we run "jrnl --edit" Then the output should contain - [1 entry modified] + 1 entry modified Examples: configs | config_file | diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index c843eacfc..109782eeb 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -201,6 +201,8 @@ def should_not(): return False +# @todo is this named properly? does it need to be merged with user_input? does +# it only mock piped input? or also user input? @fixture def mock_piped_input(request, is_tty): def _mock_piped_input(): @@ -209,7 +211,7 @@ def _mock_piped_input(): if piped_input is None: piped_input = Exception("Unexpected call for piped input") else: - piped_input = piped_input.splitlines() + piped_input = [piped_input] if is_tty: piped_input = [] @@ -225,21 +227,29 @@ def _mock_user_input(): from rich.console import Console user_input = get_fixture(request, "all_input", None) + password_input = get_fixture(request, "password", None) if user_input is None: user_input = Exception("Unexpected call for user input") else: - user_input = user_input.splitlines() + user_input = iter(user_input.splitlines()) + + if password_input is None: + password_input = Exception("Unexpected call for password input") + + def mock_console_input(**kwargs): + if kwargs["password"]: + return password_input + else: + return next(user_input) mock_console = Mock(wraps=Console(stderr=True)) mock_console.input = Mock() - mock_console.input.side_effect = user_input + mock_console.input = mock_console_input return patch("jrnl.output._get_console", return_value=mock_console) - return { - "user_input": _mock_user_input - } + return {"user_input": _mock_user_input} @fixture diff --git a/tests/lib/then_steps.py b/tests/lib/then_steps.py index f13d27bf0..a3b1c9a71 100644 --- a/tests/lib/then_steps.py +++ b/tests/lib/then_steps.py @@ -167,12 +167,12 @@ def config_var_in_memory( @then("we should be prompted for a password") def password_was_called(cli_run): - assert cli_run["mocks"]["rich_console_input"].called + assert cli_run["mocks"]["user_input"].called @then("we should not be prompted for a password") def password_was_not_called(cli_run): - assert not cli_run["mocks"]["rich_console_input"].called + assert not cli_run["mocks"]["user_input"].called @then(parse("the cache directory should contain the files\n{file_list}")) @@ -364,7 +364,7 @@ def count_editor_args(num_args, cli_run, editor_state, should_or_should_not): def stdin_prompt_called(cli_run, should_or_should_not): we_should = parse_should_or_should_not(should_or_should_not) - assert cli_run["mocks"]["stdin"].called == we_should + assert cli_run["mocks"]["piped_input"].called == we_should @then(parse('the editor filename should end with "{suffix}"')) From 57f53e96ceffe87ea06ef638672c5f778d1011f6 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 4 Jun 2022 18:56:36 -0700 Subject: [PATCH 58/66] fix more tests --- tests/bdd/features/override.feature | 2 +- tests/bdd/features/upgrade.feature | 4 +- tests/lib/fixtures.py | 73 ++++++++++++++++++----------- tests/lib/given_steps.py | 6 +-- tests/lib/then_steps.py | 2 +- 5 files changed, 53 insertions(+), 34 deletions(-) diff --git a/tests/bdd/features/override.feature b/tests/bdd/features/override.feature index d4b46e977..ef761d9ca 100644 --- a/tests/bdd/features/override.feature +++ b/tests/bdd/features/override.feature @@ -3,7 +3,7 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys Scenario: Override configured editor with built-in input === editor:'' Given we use the config "basic_encrypted.yaml" And we use the password "test" if prompted - When we run "jrnl --config-override editor ''" and enter + When we run "jrnl --config-override editor ''" and pipe This is a journal entry Then the stdin prompt should have been called And the editor should not have been called diff --git a/tests/bdd/features/upgrade.feature b/tests/bdd/features/upgrade.feature index 23628eb72..c092dedb9 100644 --- a/tests/bdd/features/upgrade.feature +++ b/tests/bdd/features/upgrade.feature @@ -41,7 +41,7 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x Scenario: Upgrade with missing journal Given we use the config "upgrade_from_195_with_missing_journal.json" When we run "jrnl --list" and enter "Y" - Then the output should contain "Error: features/journals/missing.journal does not exist." + Then the output should contain "features/journals/missing.journal does not exist" And we should get no error Scenario: Upgrade with missing encrypted journal @@ -49,6 +49,6 @@ Feature: Upgrading Journals from 1.x.x to 2.x.x When we run "jrnl --list" and enter Y bad doggie no biscuit - Then the output should contain "features/journals/missing.journal does not exist." + Then the output should contain "features/journals/missing.journal does not exist" And the output should contain "We're all done" And we should get no error diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index 109782eeb..7f510585e 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -6,6 +6,7 @@ from pathlib import Path import tempfile +from collections.abc import Iterable from keyring import backend from keyring import errors from pytest import fixture @@ -13,6 +14,7 @@ from unittest.mock import Mock from .helpers import get_fixture import toml +from rich.console import Console from jrnl.config import load_config from jrnl.os_compat import split_args @@ -86,7 +88,6 @@ def cli_run( mock_editor, mock_user_input, mock_overrides, - mock_piped_input, ): # Check if we need more mocks mock_factories.update(mock_args) @@ -95,7 +96,6 @@ def cli_run( mock_factories.update(mock_editor) mock_factories.update(mock_config_path) mock_factories.update(mock_user_input) - mock_factories.update(mock_piped_input) return { "status": 0, @@ -203,53 +203,72 @@ def should_not(): # @todo is this named properly? does it need to be merged with user_input? does # it only mock piped input? or also user input? -@fixture -def mock_piped_input(request, is_tty): - def _mock_piped_input(): - piped_input = get_fixture(request, "all_input", None) +# @fixture +# def mock_stdin_input(request, is_tty): +# def _mock_stdin_input(): +# stdin_input = get_fixture(request, "all_input", None) - if piped_input is None: - piped_input = Exception("Unexpected call for piped input") - else: - piped_input = [piped_input] +# if stdin_input is None: +# stdin_input = Exception("Unexpected call for piped input") +# else: +# stdin_input = [stdin_input] - if is_tty: - piped_input = [] +# if is_tty: +# stdin_input = [] - return patch("sys.stdin.read", side_effect=piped_input) +# return patch("sys.stdin.read", side_effect=stdin_input) - return {"piped_input": _mock_piped_input} +# return {"stdin_input": _mock_stdin_input} @fixture -def mock_user_input(request): +def mock_user_input(request, password_input, stdin_input): def _mock_user_input(): - from rich.console import Console - + # user_input needs to be here because we don't know it until cli_run starts user_input = get_fixture(request, "all_input", None) - password_input = get_fixture(request, "password", None) - if user_input is None: user_input = Exception("Unexpected call for user input") else: user_input = iter(user_input.splitlines()) - if password_input is None: - password_input = Exception("Unexpected call for password input") - def mock_console_input(**kwargs): - if kwargs["password"]: + # import ipdb; ipdb.sset_trace() + if kwargs["password"] and not isinstance(password_input, Exception): return password_input - else: + + if isinstance(user_input, Iterable): return next(user_input) + # exceptions + return user_input if not kwargs["password"] else password_input + mock_console = Mock(wraps=Console(stderr=True)) - mock_console.input = Mock() - mock_console.input = mock_console_input + mock_console.input = Mock(side_effect=mock_console_input) return patch("jrnl.output._get_console", return_value=mock_console) - return {"user_input": _mock_user_input} + return { + "user_input": _mock_user_input, + "stdin_input": lambda: patch("sys.stdin.read", side_effect=stdin_input), + } + + +@fixture +def password_input(request): + password_input = get_fixture(request, "password", None) + if password_input is None: + password_input = Exception("Unexpected call for password input") + return password_input + + +@fixture +def stdin_input(request, is_tty): + stdin_input = get_fixture(request, "all_input", None) + if stdin_input is None or is_tty: + stdin_input = Exception("Unexpected call for stdin input") + else: + stdin_input = [stdin_input] + return stdin_input @fixture diff --git a/tests/lib/given_steps.py b/tests/lib/given_steps.py index dd7c47206..4b5458490 100644 --- a/tests/lib/given_steps.py +++ b/tests/lib/given_steps.py @@ -120,9 +120,9 @@ def config_exists(config_file, temp_dir, working_dir): shutil.copy2(config_source, config_dest) -@given(parse('we use the password "{pw}" if prompted'), target_fixture="password") -def use_password_forever(pw): - return pw +@given(parse('we use the password "{password}" if prompted')) +def use_password_forever(password): + return password @given("we create a cache directory", target_fixture="cache_dir") diff --git a/tests/lib/then_steps.py b/tests/lib/then_steps.py index a3b1c9a71..8abf9c168 100644 --- a/tests/lib/then_steps.py +++ b/tests/lib/then_steps.py @@ -364,7 +364,7 @@ def count_editor_args(num_args, cli_run, editor_state, should_or_should_not): def stdin_prompt_called(cli_run, should_or_should_not): we_should = parse_should_or_should_not(should_or_should_not) - assert cli_run["mocks"]["piped_input"].called == we_should + assert cli_run["mocks"]["stdin_input"].called == we_should @then(parse('the editor filename should end with "{suffix}"')) From 920bc80d80f83481e52873311fb5e3459ac32f07 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 4 Jun 2022 19:03:00 -0700 Subject: [PATCH 59/66] fix more tests --- tests/bdd/features/write.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bdd/features/write.feature b/tests/bdd/features/write.feature index 649319601..e2347f3ca 100644 --- a/tests/bdd/features/write.feature +++ b/tests/bdd/features/write.feature @@ -172,7 +172,7 @@ Feature: Writing new entries. Scenario Outline: Writing an entry at the prompt (no editor) should store the entry Given we use the config "" And we use the password "bad doggie no biscuit" if prompted - When we run "jrnl" and enter "25 jul 2013: I saw Elvis. He's alive." + When we run "jrnl" and pipe "25 jul 2013: I saw Elvis. He's alive." Then we should get no error When we run "jrnl -on '2013-07-25'" Then the output should contain "2013-07-25 09:00 I saw Elvis." From 178c9131b73dba81cfc7e1294d25d6c80963ada1 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 4 Jun 2022 19:12:12 -0700 Subject: [PATCH 60/66] fix more tests --- tests/bdd/features/write.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bdd/features/write.feature b/tests/bdd/features/write.feature index e2347f3ca..0c860b54b 100644 --- a/tests/bdd/features/write.feature +++ b/tests/bdd/features/write.feature @@ -268,7 +268,7 @@ Feature: Writing new entries. [2021-11-13] I am replacing my whole journal with this entry When we run "jrnl --edit" Then the output should contain "2 entries deleted" - Then the output should contain "1 entry modified" + Then the output should contain "3 entries modified" Examples: configs | config_file | From 517c5220ee5c75e69abc6ca7bc68422019f39c66 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 4 Jun 2022 19:32:12 -0700 Subject: [PATCH 61/66] fix merge bug --- jrnl/jrnl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index f1780cbb7..da95b8706 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -364,7 +364,7 @@ def _delete_search_results(journal, old_entries, **kwargs): def _change_time_search_results(args, journal, old_entries, no_prompt=False, **kwargs): if not journal.entries: - raise JrnlException(Message(MsgText.NothingToModify, MsgType.WARNING)) + raise JrnlException(Message(MsgText.NothingToModify, MsgStyle.WARNING)) # separate entries we are not editing other_entries = _other_entries(journal, old_entries) From ac05bc67ce3f717f3161d87b34a983e78d79bf43 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 4 Jun 2022 19:41:17 -0700 Subject: [PATCH 62/66] update prompt_action_entries to use new messaging functionality --- jrnl/Journal.py | 4 ++-- jrnl/jrnl.py | 6 ++++-- jrnl/messages.py | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 55e63e6ee..b2e5383b0 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -288,7 +288,7 @@ def change_date_entries(self, date): for entry in self.entries: entry.date = date - def prompt_action_entries(self, message): + def prompt_action_entries(self, msg: MsgText): """Prompts for action for each entry in a journal, using given message. Returns the entries the user wishes to apply the action on.""" to_act = [] @@ -296,7 +296,7 @@ def prompt_action_entries(self, message): def ask_action(entry): return yesno( Message( - MsgText.DeleteEntryQuestion, + msg, params={"entry_title": entry.pprint(short=True)}, ), default=False, diff --git a/jrnl/jrnl.py b/jrnl/jrnl.py index da95b8706..18ee4ed4f 100644 --- a/jrnl/jrnl.py +++ b/jrnl/jrnl.py @@ -353,7 +353,7 @@ def _delete_search_results(journal, old_entries, **kwargs): if not journal.entries: raise JrnlException(Message(MsgText.NothingToDelete, MsgStyle.ERROR)) - entries_to_delete = journal.prompt_action_entries("Delete entry") + entries_to_delete = journal.prompt_action_entries(MsgText.DeleteEntryQuestion) if entries_to_delete: journal.entries = old_entries @@ -372,7 +372,9 @@ def _change_time_search_results(args, journal, old_entries, no_prompt=False, **k if no_prompt: entries_to_change = journal.entries else: - entries_to_change = journal.prompt_action_entries("Change time") + entries_to_change = journal.prompt_action_entries( + MsgText.ChangeTimeEntryQuestion + ) if entries_to_change: other_entries += [e for e in journal.entries if e not in entries_to_change] diff --git a/jrnl/messages.py b/jrnl/messages.py index 4b2ca5534..6483850ee 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -73,6 +73,7 @@ def __str__(self) -> str: Path to your journal file (leave blank for {default_journal_path}): """ DeleteEntryQuestion = "Delete entry '{entry_title}'?" + ChangeTimeEntryQuestion = "Change time for '{entry_title}'?" EncryptJournalQuestion = """ Do you want to encrypt your journal? (You can always change this later) """ From 6d18e75559810981a811aa45068fb85d4fa68455 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 11 Jun 2022 12:15:49 -0700 Subject: [PATCH 63/66] Add new input_method "type" This does the same thing as input_method "pipe" but is more clear what it's doing (typing text into the builtin composer) --- tests/bdd/features/datetime.feature | 2 +- tests/bdd/features/override.feature | 2 +- tests/bdd/features/write.feature | 2 +- tests/lib/fixtures.py | 4 ++-- tests/lib/when_steps.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/bdd/features/datetime.feature b/tests/bdd/features/datetime.feature index 0b5ce472c..1b9d00b95 100644 --- a/tests/bdd/features/datetime.feature +++ b/tests/bdd/features/datetime.feature @@ -69,7 +69,7 @@ Feature: Reading and writing to journal with custom date formats Scenario: Writing an entry at the prompt with custom date Given we use the config "little_endian_dates.yaml" - When we run "jrnl" and pipe "2013-05-10: I saw Elvis. He's alive." + When we run "jrnl" and type "2013-05-10: I saw Elvis. He's alive." Then we should get no error When we run "jrnl -999" Then the output should contain "10.05.2013 09:00 I saw Elvis." diff --git a/tests/bdd/features/override.feature b/tests/bdd/features/override.feature index ef761d9ca..90b7e40b7 100644 --- a/tests/bdd/features/override.feature +++ b/tests/bdd/features/override.feature @@ -3,7 +3,7 @@ Feature: Implementing Runtime Overrides for Select Configuration Keys Scenario: Override configured editor with built-in input === editor:'' Given we use the config "basic_encrypted.yaml" And we use the password "test" if prompted - When we run "jrnl --config-override editor ''" and pipe + When we run "jrnl --config-override editor ''" and type This is a journal entry Then the stdin prompt should have been called And the editor should not have been called diff --git a/tests/bdd/features/write.feature b/tests/bdd/features/write.feature index 0c860b54b..ede1934b6 100644 --- a/tests/bdd/features/write.feature +++ b/tests/bdd/features/write.feature @@ -172,7 +172,7 @@ Feature: Writing new entries. Scenario Outline: Writing an entry at the prompt (no editor) should store the entry Given we use the config "" And we use the password "bad doggie no biscuit" if prompted - When we run "jrnl" and pipe "25 jul 2013: I saw Elvis. He's alive." + When we run "jrnl" and type "25 jul 2013: I saw Elvis. He's alive." Then we should get no error When we run "jrnl -on '2013-07-25'" Then the output should contain "2013-07-25 09:00 I saw Elvis." diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index 7f510585e..3080f2ae4 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -273,8 +273,8 @@ def stdin_input(request, is_tty): @fixture def is_tty(input_method): - assert input_method in ["", "enter", "pipe"] - return input_method != "pipe" + assert input_method in ["", "enter", "pipe", "type"] + return input_method not in ["pipe", "type"] @fixture diff --git a/tests/lib/when_steps.py b/tests/lib/when_steps.py index 6e60f61cd..bc1d742d8 100644 --- a/tests/lib/when_steps.py +++ b/tests/lib/when_steps.py @@ -21,7 +21,7 @@ def when_we_change_directory(directory_name): # These variables are used in the `@when(re(...))` section below command = '(?P[^"]*)' -input_method = "(?Penter|pipe)" +input_method = "(?Penter|pipe|type)" all_input = '("(?P[^"]*)")' From f1c5cbad30adbf8d3edf6a65f61a1f43ccb81d36 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 11 Jun 2022 12:16:56 -0700 Subject: [PATCH 64/66] get rid of old commented code --- tests/lib/fixtures.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index 3080f2ae4..6fc7cc2a1 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -201,26 +201,6 @@ def should_not(): return False -# @todo is this named properly? does it need to be merged with user_input? does -# it only mock piped input? or also user input? -# @fixture -# def mock_stdin_input(request, is_tty): -# def _mock_stdin_input(): -# stdin_input = get_fixture(request, "all_input", None) - -# if stdin_input is None: -# stdin_input = Exception("Unexpected call for piped input") -# else: -# stdin_input = [stdin_input] - -# if is_tty: -# stdin_input = [] - -# return patch("sys.stdin.read", side_effect=stdin_input) - -# return {"stdin_input": _mock_stdin_input} - - @fixture def mock_user_input(request, password_input, stdin_input): def _mock_user_input(): @@ -232,7 +212,6 @@ def _mock_user_input(): user_input = iter(user_input.splitlines()) def mock_console_input(**kwargs): - # import ipdb; ipdb.sset_trace() if kwargs["password"] and not isinstance(password_input, Exception): return password_input From c1b4d1bcd91a6130cace19d693fc2bc65e651457 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 11 Jun 2022 12:29:58 -0700 Subject: [PATCH 65/66] get rid of unused code --- jrnl/messages.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/jrnl/messages.py b/jrnl/messages.py index 6483850ee..7393f4197 100644 --- a/jrnl/messages.py +++ b/jrnl/messages.py @@ -20,11 +20,6 @@ class MsgDecoration(Enum): "callback": lambda x, **_: x, "args": {}, } - BRACKET = { - # @todo this should be a more robust function - "callback": lambda x, **_: f"[ {x} ]", - "args": {}, - } BOX = { "callback": Panel, "args": { @@ -292,14 +287,6 @@ def __str__(self) -> str: class MsgStyle(Enum): - BARE = { - "decoration": MsgDecoration.NONE, - "color": _MsgColor("white"), - } - PLAIN = { - "decoration": MsgDecoration.BRACKET, - "color": _MsgColor("white"), - } PROMPT = { "decoration": MsgDecoration.NONE, "color": _MsgColor("white"), From 15c0eea6fdb6ef4221f479a318659c558cda1931 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Sat, 11 Jun 2022 13:13:46 -0700 Subject: [PATCH 66/66] move some files around --- jrnl/messages/Message.py | 11 +++ jrnl/messages/MsgStyle.py | 89 +++++++++++++++++++++ jrnl/{messages.py => messages/MsgText.py} | 95 ----------------------- jrnl/messages/__init__.py | 7 ++ 4 files changed, 107 insertions(+), 95 deletions(-) create mode 100644 jrnl/messages/Message.py create mode 100644 jrnl/messages/MsgStyle.py rename jrnl/{messages.py => messages/MsgText.py} (78%) create mode 100644 jrnl/messages/__init__.py diff --git a/jrnl/messages/Message.py b/jrnl/messages/Message.py new file mode 100644 index 000000000..2c02e8a0c --- /dev/null +++ b/jrnl/messages/Message.py @@ -0,0 +1,11 @@ +from typing import NamedTuple +from typing import Mapping + +from .MsgText import MsgText +from .MsgStyle import MsgStyle + + +class Message(NamedTuple): + text: MsgText + style: MsgStyle = MsgStyle.NORMAL + params: Mapping = {} diff --git a/jrnl/messages/MsgStyle.py b/jrnl/messages/MsgStyle.py new file mode 100644 index 000000000..41daa5353 --- /dev/null +++ b/jrnl/messages/MsgStyle.py @@ -0,0 +1,89 @@ +from enum import Enum +from typing import NamedTuple +from typing import Callable +from rich.panel import Panel +from rich import box + +from .MsgText import MsgText + + +class MsgStyle(Enum): + class _Color(NamedTuple): + """ + String representing a standard color to display + see: https://rich.readthedocs.io/en/stable/appendix/colors.html + """ + + color: str + + class _Decoration(Enum): + NONE = { + "callback": lambda x, **_: x, + "args": {}, + } + BOX = { + "callback": Panel, + "args": { + "expand": False, + "padding": (0, 2), + "title_align": "left", + "box": box.HEAVY, + }, + } + + @property + def callback(self) -> Callable: + return self.value["callback"] + + @property + def args(self) -> dict: + return self.value["args"] + + PROMPT = { + "decoration": _Decoration.NONE, + "color": _Color("white"), + "append_space": True, + } + TITLE = { + "decoration": _Decoration.BOX, + "color": _Color("cyan"), + } + NORMAL = { + "decoration": _Decoration.BOX, + "color": _Color("white"), + } + WARNING = { + "decoration": _Decoration.BOX, + "color": _Color("yellow"), + } + ERROR = { + "decoration": _Decoration.BOX, + "color": _Color("red"), + "box_title": str(MsgText.Error), + } + ERROR_ON_NEW_LINE = { + "decoration": _Decoration.BOX, + "color": _Color("red"), + "prepend_newline": True, + "box_title": str(MsgText.Error), + } + + @property + def decoration(self) -> _Decoration: + return self.value["decoration"] + + @property + def color(self) -> _Color: + return self.value["color"].color + + @property + def prepend_newline(self) -> bool: + return self.value.get("prepend_newline", False) + + @property + def append_space(self) -> bool: + return self.value.get("append_space", False) + + @property + def box_title(self) -> MsgText: + return self.value.get("box_title", None) diff --git a/jrnl/messages.py b/jrnl/messages/MsgText.py similarity index 78% rename from jrnl/messages.py rename to jrnl/messages/MsgText.py index 7393f4197..f8e85b604 100644 --- a/jrnl/messages.py +++ b/jrnl/messages/MsgText.py @@ -1,42 +1,4 @@ from enum import Enum -from typing import NamedTuple -from typing import Mapping -from typing import Callable -from rich.panel import Panel -from rich import box - - -class _MsgColor(NamedTuple): - """ - String representing a standard color to display - see: https://rich.readthedocs.io/en/stable/appendix/colors.html - """ - - color: str - - -class MsgDecoration(Enum): - NONE = { - "callback": lambda x, **_: x, - "args": {}, - } - BOX = { - "callback": Panel, - "args": { - "expand": False, - "padding": (0, 2), - "title_align": "left", - "box": box.HEAVY, - }, - } - - @property - def callback(self) -> Callable: - return self.value["callback"] - - @property - def args(self) -> dict: - return self.value["args"] class MsgText(Enum): @@ -284,60 +246,3 @@ def __str__(self) -> str: The command {old_cmd} is deprecated and will be removed from jrnl soon. Please use {new_cmd} instead. """ - - -class MsgStyle(Enum): - PROMPT = { - "decoration": MsgDecoration.NONE, - "color": _MsgColor("white"), - "append_space": True, - } - TITLE = { - "decoration": MsgDecoration.BOX, - "color": _MsgColor("cyan"), - } - NORMAL = { - "decoration": MsgDecoration.BOX, - "color": _MsgColor("white"), - } - WARNING = { - "decoration": MsgDecoration.BOX, - "color": _MsgColor("yellow"), - } - ERROR = { - "decoration": MsgDecoration.BOX, - "color": _MsgColor("red"), - "box_title": str(MsgText.Error), - } - ERROR_ON_NEW_LINE = { - "decoration": MsgDecoration.BOX, - "color": _MsgColor("red"), - "prepend_newline": True, - "box_title": str(MsgText.Error), - } - - @property - def decoration(self) -> MsgDecoration: - return self.value["decoration"] - - @property - def color(self) -> _MsgColor: - return self.value["color"].color - - @property - def prepend_newline(self) -> bool: - return self.value.get("prepend_newline", False) - - @property - def append_space(self) -> bool: - return self.value.get("append_space", False) - - @property - def box_title(self) -> MsgText: - return self.value.get("box_title", None) - - -class Message(NamedTuple): - text: MsgText - style: MsgStyle = MsgStyle.NORMAL - params: Mapping = {} diff --git a/jrnl/messages/__init__.py b/jrnl/messages/__init__.py new file mode 100644 index 000000000..930fbe750 --- /dev/null +++ b/jrnl/messages/__init__.py @@ -0,0 +1,7 @@ +from .Message import Message +from .MsgStyle import MsgStyle +from .MsgText import MsgText + +Message = Message +MsgStyle = MsgStyle +MsgText = MsgText