mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-17 19:48:31 +02:00
add new message handling
This commit is contained in:
parent
f8b4460872
commit
85a45ab32c
10 changed files with 175 additions and 134 deletions
33
jrnl/cli.py
33
jrnl/cli.py
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
84
jrnl/messages.py
Normal 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 = {}
|
|
@ -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)
|
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -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.
|
|
||||||
"""
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue