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

View file

@ -13,8 +13,10 @@ avoid any possible overhead for these standalone commands.
"""
import platform
import sys
from .exception import JrnlException
from .exception import JrnlExceptionMessage
from jrnl.exception import JrnlException
from jrnl.messages import Message
from jrnl.messages import MsgText
from jrnl.messages import MsgType
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:
raise JrnlException(
JrnlExceptionMessage.CannotEncryptJournalType,
journal_name=args.journal_name,
journal_type=journal.__class__.__name__,
Message(
MsgText.CannotEncryptJournalType,
MsgType.ERROR,
{
"journal_name": args.journal_name,
"journal_type": journal.__class__.__name__,
},
)
)
journal.config["encrypt"] = True

View file

@ -7,8 +7,11 @@ import yaml
import xdg.BaseDirectory
from . import __version__
from .exception import JrnlException
from .exception import JrnlExceptionMessage
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
@ -70,11 +73,17 @@ def get_config_path():
config_directory_path = xdg.BaseDirectory.save_config_path(XDG_RESOURCE)
except FileExistsError:
raise JrnlException(
JrnlExceptionMessage.ConfigDirectoryIsFile,
config_directory_path=os.path.join(
xdg.BaseDirectory.xdg_config_home, XDG_RESOURCE
Message(
MsgText.ConfigDirectoryIsFile,
MsgType.ERROR,
{
"config_directory_path": os.path.join(
xdg.BaseDirectory.xdg_config_home, XDG_RESOURCE
)
},
),
)
return os.path.join(
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 split_args
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 get_text_from_editor(config, template=""):
@ -50,17 +53,25 @@ def get_text_from_editor(config, template=""):
def get_text_from_stdin():
_how_to_quit = "Ctrl+z and then Enter" if on_windows() else "Ctrl+d"
print_msg(
"Writing Entry",
f"To finish writing, press {_how_to_quit} on a blank line.",
msg=Message.NORMAL,
Message(
MsgText.WritingEntryStart,
MsgType.TITLE,
{
"how_to_quit": MsgText.HowToQuitWindows.value
if on_windows()
else MsgText.HowToQuitLinux.value
},
)
)
try:
raw = sys.stdin.read()
except KeyboardInterrupt:
logging.error("Write mode: keyboard interrupt")
print_msg("Entry NOT saved to journal", msg=Message.NORMAL)
raise JrnlException("KeyboardInterrupt")
raise JrnlException(
Message(MsgText.KeyboardInterruptMsg, MsgType.ERROR),
Message(MsgText.JournalNotSaved, MsgType.WARNING),
)
return raw

View file

@ -1,8 +1,7 @@
# Copyright (C) 2012-2021 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html
import textwrap
from enum import Enum
from jrnl.messages import Message
from jrnl.output import print_msg
class UserAbort(Exception):
@ -15,53 +14,12 @@ class UpgradeValidationException(Exception):
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):
"""Common exceptions raised by jrnl."""
def __init__(self, exception_msg: JrnlExceptionMessage, **kwargs):
self.exception_msg = exception_msg
self.title = str(exception_msg.name)
self.message = self._get_error_message(**kwargs)
def __init__(self, *messages: Message):
self.messages = messages
def _get_error_message(self, **kwargs):
msg = self.exception_msg.value
msg = msg.format(**kwargs)
msg = textwrap.dedent(msg)
return msg
def print(self) -> None:
for msg in self.messages:
print_msg(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
# License: https://www.gnu.org/licenses/gpl-3.0.html
from enum import Enum
import logging
import sys
from collections import namedtuple
import textwrap
from jrnl.color import colorize
from jrnl.color import RESET_COLOR
from jrnl.color import WARNING_COLOR
from jrnl.messages import Message
def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs):
@ -39,25 +38,14 @@ def list_journals(configuration):
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):
NORMAL = MessageProps(0, "cyan")
WARNING = MessageProps(1, "yellow")
ERROR = MessageProps(2, "red")
# 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)}]"
@property
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)
print("\n".join(msg_text), file=sys.stderr)

View file

@ -3,7 +3,9 @@
# License: https://www.gnu.org/licenses/gpl-3.0.html
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 .text_exporter import TextExporter
@ -86,8 +88,13 @@ def check_provided_linewrap_viability(linewrap, card, journal):
if len(card[0]) > linewrap:
width_violation = len(card[0]) - linewrap
raise JrnlException(
JrnlExceptionMessage.LineWrapTooSmallForDateFormat,
config_linewrap=linewrap,
columns=width_violation,
journal=journal,
Message(
MsgText.LineWrapTooSmallForDateFormat,
MsgType.NORMAL,
{
"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
from jrnl.exception import JrnlException
from jrnl.exception import JrnlExceptionMessage
from jrnl.plugins.fancy_exporter import check_provided_linewrap_viability
@ -24,8 +24,5 @@ class TestFancy:
total_linewrap = 12
with pytest.raises(JrnlException) as e:
with pytest.raises(JrnlException):
check_provided_linewrap_viability(total_linewrap, [content], journal)
assert (
e.value.exception_msg == JrnlExceptionMessage.LineWrapTooSmallForDateFormat
)