add new message handling

This commit is contained in:
Jonathan Wren 2022-02-26 18:38:50 -08:00
parent f8b4460872
commit 85a45ab32c
10 changed files with 175 additions and 134 deletions

View file

@ -5,11 +5,13 @@ import logging
import sys import sys
import traceback import traceback
from .jrnl import run from jrnl.jrnl import run
from .args import parse_args from jrnl.args import parse_args
from .exception import JrnlException
from jrnl.output import print_msg from jrnl.output import print_msg
from jrnl.output import Message from jrnl.exception import JrnlException
from jrnl.messages import Message
from jrnl.messages import MsgText
from jrnl.messages import MsgType
def configure_logger(debug=False): def configure_logger(debug=False):
@ -37,23 +39,22 @@ def cli(manual_args=None):
return run(args) return run(args)
except JrnlException as e: except JrnlException as e:
print_msg(e.title, e.message, msg=Message.ERROR) e.print()
return 1 return 1
except KeyboardInterrupt: except KeyboardInterrupt:
print_msg("KeyboardInterrupt", "Aborted by user", msg=Message.ERROR) print_msg(Message(MsgText.KeyboardInterruptMsg, MsgType.WARNING))
return 1 return 1
except Exception as e: except Exception as e:
# uncaught exception try:
if args.debug: is_debug = args.debug # type: ignore
print("\n") except NameError:
traceback.print_tb(sys.exc_info()[2]) # error happened before args were parsed
return 1 is_debug = "--debug" in sys.argv[1:]
file_issue = ( if is_debug:
"\n\nThis is probably a bug. Please file an issue at:" traceback.print_tb(sys.exc_info()[2])
+ "\nhttps://github.com/jrnl-org/jrnl/issues/new/choose"
) print_msg(Message(MsgText.UncaughtException, MsgType.ERROR, {"exception": e}))
print_msg(f"{type(e).__name__}\n", f"{e}{file_issue}", msg=Message.ERROR)
return 1 return 1

View file

@ -13,8 +13,10 @@ avoid any possible overhead for these standalone commands.
""" """
import platform import platform
import sys import sys
from .exception import JrnlException from jrnl.exception import JrnlException
from .exception import JrnlExceptionMessage from jrnl.messages import Message
from jrnl.messages import MsgText
from jrnl.messages import MsgType
def preconfig_diagnostic(_): def preconfig_diagnostic(_):
@ -72,9 +74,14 @@ def postconfig_encrypt(args, config, original_config, **kwargs):
if hasattr(journal, "can_be_encrypted") and not journal.can_be_encrypted: if hasattr(journal, "can_be_encrypted") and not journal.can_be_encrypted:
raise JrnlException( raise JrnlException(
JrnlExceptionMessage.CannotEncryptJournalType, Message(
journal_name=args.journal_name, MsgText.CannotEncryptJournalType,
journal_type=journal.__class__.__name__, MsgType.ERROR,
{
"journal_name": args.journal_name,
"journal_type": journal.__class__.__name__,
},
)
) )
journal.config["encrypt"] = True journal.config["encrypt"] = True

View file

@ -7,8 +7,11 @@ import yaml
import xdg.BaseDirectory import xdg.BaseDirectory
from . import __version__ from . import __version__
from .exception import JrnlException from jrnl.exception import JrnlException
from .exception import JrnlExceptionMessage from jrnl.messages import Message
from jrnl.messages import MsgText
from jrnl.messages import MsgType
from .color import ERROR_COLOR from .color import ERROR_COLOR
from .color import RESET_COLOR from .color import RESET_COLOR
from .output import list_journals from .output import list_journals
@ -70,11 +73,17 @@ def get_config_path():
config_directory_path = xdg.BaseDirectory.save_config_path(XDG_RESOURCE) config_directory_path = xdg.BaseDirectory.save_config_path(XDG_RESOURCE)
except FileExistsError: except FileExistsError:
raise JrnlException( raise JrnlException(
JrnlExceptionMessage.ConfigDirectoryIsFile, Message(
config_directory_path=os.path.join( MsgText.ConfigDirectoryIsFile,
xdg.BaseDirectory.xdg_config_home, XDG_RESOURCE MsgType.ERROR,
{
"config_directory_path": os.path.join(
xdg.BaseDirectory.xdg_config_home, XDG_RESOURCE
)
},
), ),
) )
return os.path.join( return os.path.join(
config_directory_path or os.path.expanduser("~"), DEFAULT_CONFIG_NAME config_directory_path or os.path.expanduser("~"), DEFAULT_CONFIG_NAME
) )

View file

@ -11,8 +11,11 @@ from jrnl.color import RESET_COLOR
from jrnl.os_compat import on_windows from jrnl.os_compat import on_windows
from jrnl.os_compat import split_args from jrnl.os_compat import split_args
from jrnl.output import print_msg from jrnl.output import print_msg
from jrnl.output import Message
from jrnl.exception import JrnlException from jrnl.exception import JrnlException
from jrnl.messages import Message
from jrnl.messages import MsgText
from jrnl.messages import MsgType
def get_text_from_editor(config, template=""): def get_text_from_editor(config, template=""):
@ -50,17 +53,25 @@ def get_text_from_editor(config, template=""):
def get_text_from_stdin(): def get_text_from_stdin():
_how_to_quit = "Ctrl+z and then Enter" if on_windows() else "Ctrl+d"
print_msg( print_msg(
"Writing Entry", Message(
f"To finish writing, press {_how_to_quit} on a blank line.", MsgText.WritingEntryStart,
msg=Message.NORMAL, MsgType.TITLE,
{
"how_to_quit": MsgText.HowToQuitWindows.value
if on_windows()
else MsgText.HowToQuitLinux.value
},
)
) )
try: try:
raw = sys.stdin.read() raw = sys.stdin.read()
except KeyboardInterrupt: except KeyboardInterrupt:
logging.error("Write mode: keyboard interrupt") logging.error("Write mode: keyboard interrupt")
print_msg("Entry NOT saved to journal", msg=Message.NORMAL) raise JrnlException(
raise JrnlException("KeyboardInterrupt") Message(MsgText.KeyboardInterruptMsg, MsgType.ERROR),
Message(MsgText.JournalNotSaved, MsgType.WARNING),
)
return raw return raw

View file

@ -1,8 +1,7 @@
# Copyright (C) 2012-2021 jrnl contributors # Copyright (C) 2012-2021 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
import textwrap from jrnl.messages import Message
from jrnl.output import print_msg
from enum import Enum
class UserAbort(Exception): class UserAbort(Exception):
@ -15,53 +14,12 @@ class UpgradeValidationException(Exception):
pass pass
class JrnlExceptionMessage(Enum):
ConfigDirectoryIsFile = """
The path to your jrnl configuration directory is a file, not a directory:
{config_directory_path}
Removing this file will allow jrnl to save its configuration.
"""
LineWrapTooSmallForDateFormat = """
The provided linewrap value of {config_linewrap} is too small by
{columns} columns to display the timestamps in the configured time
format for journal {journal}.
You can avoid this error by specifying a linewrap value that is larger
by at least {columns} in the configuration file or by using
--config-override at the command line
"""
CannotEncryptJournalType = """
The journal {journal_name} can't be encrypted because it is a
{journal_type} journal.
To encrypt it, create a new journal referencing a file, export
this journal to the new journal, then encrypt the new journal.
"""
KeyboardInterrupt = "Aborted by user"
SomeTest = """
Some error or something
This is a thing to test with this message or whatever and maybe it just
keeps going forever because it's super long for no apparent reason
"""
class JrnlException(Exception): class JrnlException(Exception):
"""Common exceptions raised by jrnl.""" """Common exceptions raised by jrnl."""
def __init__(self, exception_msg: JrnlExceptionMessage, **kwargs): def __init__(self, *messages: Message):
self.exception_msg = exception_msg self.messages = messages
self.title = str(exception_msg.name)
self.message = self._get_error_message(**kwargs)
def _get_error_message(self, **kwargs): def print(self) -> None:
msg = self.exception_msg.value for msg in self.messages:
msg = msg.format(**kwargs) print_msg(msg)
msg = textwrap.dedent(msg)
return msg

84
jrnl/messages.py Normal file
View file

@ -0,0 +1,84 @@
from enum import Enum
from typing import NamedTuple
from typing import Mapping
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
color: str
class MsgType(Enum):
TITLE = _MsgColor("cyan")
NORMAL = _MsgColor("white")
WARNING = _MsgColor("yellow")
ERROR = _MsgColor("red")
@property
def color(self) -> _MsgColor:
return self.value.color
class MsgText(Enum):
# --- Exceptions ---#
UncaughtException = """
ERROR OF SOME SORT
{exception}
This is probably a bug. Please file an issue at:
https://github.com/jrnl-org/jrnl/issues/new/choose
"""
ConfigDirectoryIsFile = """
The path to your jrnl configuration directory is a file, not a directory:
{config_directory_path}
Removing this file will allow jrnl to save its configuration.
"""
LineWrapTooSmallForDateFormat = """
The provided linewrap value of {config_linewrap} is too small by
{columns} columns to display the timestamps in the configured time
format for journal {journal}.
You can avoid this error by specifying a linewrap value that is larger
by at least {columns} in the configuration file or by using
--config-override at the command line
"""
CannotEncryptJournalType = """
The journal {journal_name} can't be encrypted because it is a
{journal_type} journal.
To encrypt it, create a new journal referencing a file, export
this journal to the new journal, then encrypt the new journal.
"""
KeyboardInterruptMsg = "Aborted by user"
SomeTest = """
Some error or something
This is a thing to test with this message or whatever and maybe it just
keeps going forever because it's super long for no apparent reason
"""
# --- Journal status ---#
JournalNotSaved = "Entry NOT saved to journal"
# --- Editor ---#
WritingEntryStart = """
Writing Entry
To finish writing, press {how_to_quit} on a blank line.
"""
HowToQuitWindows = "Ctrl+z and then Enter"
HowToQuitLinux = "Ctrl+d"
class Message(NamedTuple):
text: MsgText
type: MsgType = MsgType.NORMAL
params: Mapping = {}

View file

@ -1,15 +1,14 @@
# Copyright (C) 2012-2021 jrnl contributors # Copyright (C) 2012-2021 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
from enum import Enum
import logging import logging
import sys import sys
from collections import namedtuple
import textwrap import textwrap
from jrnl.color import colorize from jrnl.color import colorize
from jrnl.color import RESET_COLOR from jrnl.color import RESET_COLOR
from jrnl.color import WARNING_COLOR from jrnl.color import WARNING_COLOR
from jrnl.messages import Message
def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs): def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs):
@ -39,25 +38,14 @@ def list_journals(configuration):
return result return result
MessageProps = namedtuple("MessageProps", ["value", "color"]) def print_msg(msg: Message):
msg_text = textwrap.dedent(msg.text.value.format(**msg.params)).strip().split("\n")
longest_string = len(max(msg_text, key=len))
msg_text = [f"[ {line:<{longest_string}} ]" for line in msg_text]
class Message(Enum): # colorize can't be called until after the lines are padded,
NORMAL = MessageProps(0, "cyan") # because python gets confused by the ansi color codes
WARNING = MessageProps(1, "yellow") msg_text[0] = f"[{colorize(msg_text[0][1:-1], msg.type.color)}]"
ERROR = MessageProps(2, "red")
@property print("\n".join(msg_text), file=sys.stderr)
def color(self):
return self.value.color
def print_msg(title: str, body: str = "", msg: Message = Message.NORMAL):
# @todo Add some polish around colorization of multi-line messages
result = colorize(title, msg.color)
if body:
result += f"\n{body}"
print(result, file=sys.stderr)

View file

@ -3,7 +3,9 @@
# License: https://www.gnu.org/licenses/gpl-3.0.html # License: https://www.gnu.org/licenses/gpl-3.0.html
from jrnl.exception import JrnlException from jrnl.exception import JrnlException
from jrnl.exception import JrnlExceptionMessage from jrnl.messages import Message
from jrnl.messages import MsgText
from jrnl.messages import MsgType
from textwrap import TextWrapper from textwrap import TextWrapper
from .text_exporter import TextExporter from .text_exporter import TextExporter
@ -86,8 +88,13 @@ def check_provided_linewrap_viability(linewrap, card, journal):
if len(card[0]) > linewrap: if len(card[0]) > linewrap:
width_violation = len(card[0]) - linewrap width_violation = len(card[0]) - linewrap
raise JrnlException( raise JrnlException(
JrnlExceptionMessage.LineWrapTooSmallForDateFormat, Message(
config_linewrap=linewrap, MsgText.LineWrapTooSmallForDateFormat,
columns=width_violation, MsgType.NORMAL,
journal=journal, {
"config_linewrap": linewrap,
"columns": width_violation,
"journal": journal,
},
)
) )

View file

@ -1,21 +0,0 @@
import textwrap
from jrnl.exception import JrnlException
from jrnl.exception import JrnlExceptionMessage
def test_config_directory_exception_message():
ex = JrnlException(
JrnlExceptionMessage.ConfigDirectoryIsFile,
config_directory_path="/config/directory/path",
)
assert ex.message == textwrap.dedent(
"""
The path to your jrnl configuration directory is a file, not a directory:
/config/directory/path
Removing this file will allow jrnl to save its configuration.
"""
)

View file

@ -1,7 +1,7 @@
import pytest import pytest
from jrnl.exception import JrnlException from jrnl.exception import JrnlException
from jrnl.exception import JrnlExceptionMessage
from jrnl.plugins.fancy_exporter import check_provided_linewrap_viability from jrnl.plugins.fancy_exporter import check_provided_linewrap_viability
@ -24,8 +24,5 @@ class TestFancy:
total_linewrap = 12 total_linewrap = 12
with pytest.raises(JrnlException) as e: with pytest.raises(JrnlException):
check_provided_linewrap_viability(total_linewrap, [content], journal) check_provided_linewrap_viability(total_linewrap, [content], journal)
assert (
e.value.exception_msg == JrnlExceptionMessage.LineWrapTooSmallForDateFormat
)