mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 08:38:32 +02:00
Reformat additional messages and finish centralizing exception handling (#1424)
* Update and modularize exception handling cc #1024 #1141 - Stack traces are no longer shown to users unless the --debug flag is being used - Errors, warnings, and other messages contain color as needed - Converted error messages to Enum - Adds print_msg function to centralize output (this should replace all other output in other modules) Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com> * format with black * add message to catch-all exception block * Unskip some tests (#1399) * remove skip_editor test and tag * remove useless test * unskip blank input test * formatting * rename test so it doesn't overwrite other test * unskip some dayone tests that now work * Bump ipython from 7.28.0 to 7.31.1 (#1401) Bumps [ipython](https://github.com/ipython/ipython) from 7.28.0 to 7.31.1. - [Release notes](https://github.com/ipython/ipython/releases) - [Commits](https://github.com/ipython/ipython/compare/7.28.0...7.31.1) --- updated-dependencies: - dependency-name: ipython dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update changelog [ci skip] * Bump asteval from 0.9.25 to 0.9.26 (#1400) Bumps [asteval](https://github.com/newville/asteval) from 0.9.25 to 0.9.26. - [Release notes](https://github.com/newville/asteval/releases) - [Commits](https://github.com/newville/asteval/compare/0.9.25...0.9.26) --- updated-dependencies: - dependency-name: asteval dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update changelog [ci skip] * Bump black from 21.12b0 to 22.1.0 (#1404) * Bump black from 21.12b0 to 22.1.0 Bumps [black](https://github.com/psf/black) from 21.12b0 to 22.1.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](https://github.com/psf/black/commits/22.1.0) --- updated-dependencies: - dependency-name: black dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> * Run make format Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com> * Update changelog [ci skip] * Add reference documentation to docs site and separate out "Tips and Tricks" and "External Editors" from "Recipes" (#1332) * First draft of command line reference, mostly pulled from help screen * Add first draft of config file reference, mostly pulled from advanced.md * Clean up config file doc for readability * Add --config-file and remove examples from CLI reference * Add warning about time zone in timeformat * More small changes, and adding template config keyword * Cleaning up and re-ordering config file reference * Clean up reference and anything else from advanced documentation that can live elsewhere and linking to config file reference wherever config file is mentioned * Fix syntax highlighting in command line reference, clean up content a bit, include --diagnostic * Mention version config key * Apply minor changes suggested in PR review * Rename "recipes" to "Tips and Tricks", pull "External Editors" out of it into its own page, and redirect old recipes link to tips-and-tricks * Revert broken mkdocs-redirects usage from last commit * Update changelog [ci skip] * Add --co alias for --config-override (#1397) * Add hash as a default tag symbol (#1398) * Update changelog [ci skip] * Increment version to v2.8.4-beta2 * Update changelog [ci skip] * Increment version to v2.8.4 * Update changelog [ci skip] * Bump pytest from 6.2.5 to 7.0.0 (#1407) Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.5 to 7.0.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/6.2.5...7.0.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update changelog [ci skip] * Drop support for Python 3.7 and 3.8 (#1412) * Remove Python 3.7 and 3.8 from github actions workflows * Update lockfile after running poetry update a couple times * Update poetry lock * Remove Python 3.7 and 3.8 from pyproject.toml and run poetry lock * Update changelog [ci skip] * Tidy up git ignore (#1414) * cleaned gitignore and add comments * removed colon for readbility * alphabetize files in sections Co-authored-by: nelnog <nel.nogales@gmail.com> * fix behavior that was confusing pytest * update test to match new message * whitespace change * clean up error for manually stopping the inline editor * udpate error to use new exception handling * move some exceptions and errors to the new exception handling * add line breaks to keyboard interrupt so it looks more like other exceptions * add handling for exceptions that happen earlier in the flow * add new 'NothingToDelete' error to replace old behavior * get rid of old exception * add new exception handling to 'nothing saved to file' errors * move exception for no editor configured into new handling * move exception for no alt config to new handling * get rid of old exception handling for encrypted journal * Move error for too many wrong passwords into new handling * fix merge errors * replace sys.exit call with new exception handling * replace sys.exit call with new exception handling * replace sys.exit call with new exception handling * reformat with black * clean up old code * clean up old code * clean up linting issue * update uncaught exception for new handling * update test * fix mangled lock file Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jrnl Bot <jrnl.bot@gmail.com> Co-authored-by: Nelson <35701520+nelnog@users.noreply.github.com> Co-authored-by: nelnog <nel.nogales@gmail.com>
This commit is contained in:
parent
a1117918dd
commit
bc42f74b2b
16 changed files with 204 additions and 153 deletions
|
@ -21,6 +21,11 @@ from .Journal import Journal
|
||||||
from .Journal import LegacyJournal
|
from .Journal import LegacyJournal
|
||||||
from .prompt import create_password
|
from .prompt import create_password
|
||||||
|
|
||||||
|
from jrnl.exception import JrnlException
|
||||||
|
from jrnl.messages import Message
|
||||||
|
from jrnl.messages import MsgText
|
||||||
|
from jrnl.messages import MsgType
|
||||||
|
|
||||||
|
|
||||||
def make_key(password):
|
def make_key(password):
|
||||||
password = password.encode("utf-8")
|
password = password.encode("utf-8")
|
||||||
|
@ -53,11 +58,11 @@ def decrypt_content(
|
||||||
password = getpass.getpass()
|
password = getpass.getpass()
|
||||||
result = decrypt_func(password)
|
result = decrypt_func(password)
|
||||||
attempt += 1
|
attempt += 1
|
||||||
if result is not None:
|
|
||||||
|
if result is None:
|
||||||
|
raise JrnlException(Message(MsgText.PasswordMaxTriesExceeded, MsgType.ERROR))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
else:
|
|
||||||
print("Extremely wrong password.", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
class EncryptedJournal(Journal):
|
class EncryptedJournal(Journal):
|
||||||
|
@ -121,15 +126,11 @@ class EncryptedJournal(Journal):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_journal(cls, other: Journal):
|
def from_journal(cls, other: Journal):
|
||||||
new_journal = super().from_journal(other)
|
new_journal = super().from_journal(other)
|
||||||
try:
|
|
||||||
new_journal.password = (
|
new_journal.password = (
|
||||||
other.password
|
other.password
|
||||||
if hasattr(other, "password")
|
if hasattr(other, "password")
|
||||||
else create_password(other.name)
|
else create_password(other.name)
|
||||||
)
|
)
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("[Interrupted while creating new journal]", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
return new_journal
|
return new_journal
|
||||||
|
|
||||||
|
|
|
@ -431,13 +431,6 @@ def open_journal(journal_name, config, legacy=False):
|
||||||
|
|
||||||
from . import EncryptedJournal
|
from . import EncryptedJournal
|
||||||
|
|
||||||
try:
|
|
||||||
if legacy:
|
if legacy:
|
||||||
return EncryptedJournal.LegacyEncryptedJournal(
|
return EncryptedJournal.LegacyEncryptedJournal(journal_name, **config).open()
|
||||||
journal_name, **config
|
|
||||||
).open()
|
|
||||||
return EncryptedJournal.EncryptedJournal(journal_name, **config).open()
|
return EncryptedJournal.EncryptedJournal(journal_name, **config).open()
|
||||||
except KeyboardInterrupt:
|
|
||||||
# Since encrypted journals prompt for a password, it's easy for a user to ctrl+c out
|
|
||||||
print("[Interrupted while opening journal]", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
40
jrnl/cli.py
40
jrnl/cli.py
|
@ -5,9 +5,10 @@ import logging
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from jrnl.jrnl import run
|
from .jrnl import run
|
||||||
from jrnl.args import parse_args
|
from .args import parse_args
|
||||||
from jrnl.output import print_msg
|
from jrnl.output import print_msg
|
||||||
|
|
||||||
from jrnl.exception import JrnlException
|
from jrnl.exception import JrnlException
|
||||||
from jrnl.messages import Message
|
from jrnl.messages import Message
|
||||||
from jrnl.messages import MsgText
|
from jrnl.messages import MsgText
|
||||||
|
@ -36,25 +37,40 @@ def cli(manual_args=None):
|
||||||
configure_logger(args.debug)
|
configure_logger(args.debug)
|
||||||
logging.debug("Parsed args: %s", args)
|
logging.debug("Parsed args: %s", args)
|
||||||
|
|
||||||
return run(args)
|
status_code = run(args)
|
||||||
|
|
||||||
except JrnlException as e:
|
except JrnlException as e:
|
||||||
|
status_code = 1
|
||||||
e.print()
|
e.print()
|
||||||
return 1
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print_msg(Message(MsgText.KeyboardInterruptMsg, MsgType.WARNING))
|
status_code = 1
|
||||||
return 1
|
print_msg("\nKeyboardInterrupt", "\nAborted by user", msg=Message.ERROR)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
# uncaught exception
|
||||||
|
status_code = 1
|
||||||
|
debug = False
|
||||||
try:
|
try:
|
||||||
is_debug = args.debug # type: ignore
|
if args.debug: # type: ignore
|
||||||
|
debug = True
|
||||||
except NameError:
|
except NameError:
|
||||||
# error happened before args were parsed
|
# This should only happen when the exception
|
||||||
is_debug = "--debug" in sys.argv[1:]
|
# happened before the args were parsed
|
||||||
|
if "--debug" in sys.argv:
|
||||||
|
debug = True
|
||||||
|
|
||||||
if is_debug:
|
if debug:
|
||||||
|
print("\n")
|
||||||
traceback.print_tb(sys.exc_info()[2])
|
traceback.print_tb(sys.exc_info()[2])
|
||||||
|
|
||||||
print_msg(Message(MsgText.UncaughtException, MsgType.ERROR, {"exception": e}))
|
print_msg(
|
||||||
return 1
|
Message(
|
||||||
|
MsgText.UncaughtException,
|
||||||
|
MsgType.ERROR,
|
||||||
|
{"name": type(e).__name__, "exception": e},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# This should be the only exit point
|
||||||
|
return status_code
|
||||||
|
|
|
@ -197,9 +197,13 @@ def get_journal_name(args, config):
|
||||||
args.text = args.text[1:]
|
args.text = args.text[1:]
|
||||||
|
|
||||||
if args.journal_name not in config["journals"]:
|
if args.journal_name not in config["journals"]:
|
||||||
print("No default journal configured.", file=sys.stderr)
|
raise JrnlException(
|
||||||
print(list_journals(config), file=sys.stderr)
|
Message(
|
||||||
sys.exit(1)
|
MsgText.NoDefaultJournal,
|
||||||
|
MsgType.ERROR,
|
||||||
|
{"journals": list_journals(config)},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
logging.debug("Using journal name: %s", args.journal_name)
|
logging.debug("Using journal name: %s", args.journal_name)
|
||||||
return args
|
return args
|
||||||
|
|
|
@ -3,11 +3,8 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import textwrap
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from jrnl.color import ERROR_COLOR
|
|
||||||
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
|
||||||
|
@ -32,22 +29,21 @@ def get_text_from_editor(config, template=""):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.call(split_args(config["editor"]) + [tmpfile])
|
subprocess.call(split_args(config["editor"]) + [tmpfile])
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError:
|
||||||
error_msg = f"""
|
raise JrnlException(
|
||||||
{ERROR_COLOR}{str(e)}{RESET_COLOR}
|
Message(
|
||||||
|
MsgText.EditorMisconfigured,
|
||||||
Please check the 'editor' key in your config file for errors:
|
MsgType.ERROR,
|
||||||
{repr(config['editor'])}
|
{"editor_key": config["editor"]},
|
||||||
"""
|
)
|
||||||
print(textwrap.dedent(error_msg).strip(), file=sys.stderr)
|
)
|
||||||
exit(1)
|
|
||||||
|
|
||||||
with open(tmpfile, "r", encoding="utf-8") as f:
|
with open(tmpfile, "r", encoding="utf-8") as f:
|
||||||
raw = f.read()
|
raw = f.read()
|
||||||
os.remove(tmpfile)
|
os.remove(tmpfile)
|
||||||
|
|
||||||
if not raw:
|
if not raw:
|
||||||
print("[Nothing saved to file]", file=sys.stderr)
|
raise JrnlException(Message(MsgText.NoTextReceived, MsgType.ERROR))
|
||||||
|
|
||||||
return raw
|
return raw
|
||||||
|
|
||||||
|
|
|
@ -4,16 +4,6 @@ from jrnl.messages import Message
|
||||||
from jrnl.output import print_msg
|
from jrnl.output import print_msg
|
||||||
|
|
||||||
|
|
||||||
class UserAbort(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UpgradeValidationException(Exception):
|
|
||||||
"""Raised when the contents of an upgraded journal do not match the old journal"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class JrnlException(Exception):
|
class JrnlException(Exception):
|
||||||
"""Common exceptions raised by jrnl."""
|
"""Common exceptions raised by jrnl."""
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,14 @@ from .config import get_default_journal_path
|
||||||
from .config import load_config
|
from .config import load_config
|
||||||
from .config import save_config
|
from .config import save_config
|
||||||
from .config import verify_config_colors
|
from .config import verify_config_colors
|
||||||
from .exception import UserAbort
|
|
||||||
from .prompt import yesno
|
from .prompt import yesno
|
||||||
from .upgrade import is_old_version
|
from .upgrade import is_old_version
|
||||||
|
|
||||||
|
from jrnl.exception import JrnlException
|
||||||
|
from jrnl.messages import Message
|
||||||
|
from jrnl.messages import MsgText
|
||||||
|
from jrnl.messages import MsgType
|
||||||
|
|
||||||
|
|
||||||
def upgrade_config(config_data, alt_config_path=None):
|
def upgrade_config(config_data, alt_config_path=None):
|
||||||
"""Checks if there are keys missing in a given config dict, and if so, updates the config file accordingly.
|
"""Checks if there are keys missing in a given config dict, and if so, updates the config file accordingly.
|
||||||
|
@ -47,14 +51,14 @@ def find_default_config():
|
||||||
|
|
||||||
|
|
||||||
def find_alt_config(alt_config):
|
def find_alt_config(alt_config):
|
||||||
if os.path.exists(alt_config):
|
if not os.path.exists(alt_config):
|
||||||
return alt_config
|
raise JrnlException(
|
||||||
else:
|
Message(
|
||||||
print(
|
MsgText.AltConfigNotFound, MsgType.ERROR, {"config_file": alt_config}
|
||||||
"Alternate configuration file not found at path specified.", file=sys.stderr
|
|
||||||
)
|
)
|
||||||
print("Exiting.", file=sys.stderr)
|
)
|
||||||
sys.exit(1)
|
|
||||||
|
return alt_config
|
||||||
|
|
||||||
|
|
||||||
def load_or_install_jrnl(alt_config_path):
|
def load_or_install_jrnl(alt_config_path):
|
||||||
|
@ -72,32 +76,16 @@ def load_or_install_jrnl(alt_config_path):
|
||||||
config = load_config(config_path)
|
config = load_config(config_path)
|
||||||
|
|
||||||
if is_old_version(config_path):
|
if is_old_version(config_path):
|
||||||
from . import upgrade
|
from jrnl import upgrade
|
||||||
|
|
||||||
try:
|
|
||||||
upgrade.upgrade_jrnl(config_path)
|
upgrade.upgrade_jrnl(config_path)
|
||||||
except upgrade.UpgradeValidationException:
|
|
||||||
print("Aborting upgrade.", file=sys.stderr)
|
|
||||||
print(
|
|
||||||
"Please tell us about this problem at the following URL:",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
"https://github.com/jrnl-org/jrnl/issues/new?title=UpgradeValidationException",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
print("Exiting.", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
upgrade_config(config, alt_config_path)
|
upgrade_config(config, alt_config_path)
|
||||||
verify_config_colors(config)
|
verify_config_colors(config)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logging.debug("Configuration file not found, installing jrnl...")
|
logging.debug("Configuration file not found, installing jrnl...")
|
||||||
try:
|
|
||||||
config = install()
|
config = install()
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise UserAbort("Installation aborted")
|
|
||||||
|
|
||||||
logging.debug('Using configuration "%s"', config)
|
logging.debug('Using configuration "%s"', config)
|
||||||
return config
|
return config
|
||||||
|
|
47
jrnl/jrnl.py
47
jrnl/jrnl.py
|
@ -7,17 +7,19 @@ import sys
|
||||||
from . import install
|
from . import install
|
||||||
from . import plugins
|
from . import plugins
|
||||||
from .Journal import open_journal
|
from .Journal import open_journal
|
||||||
from .color import ERROR_COLOR
|
|
||||||
from .color import RESET_COLOR
|
|
||||||
from .config import get_journal_name
|
from .config import get_journal_name
|
||||||
from .config import scope_config
|
from .config import scope_config
|
||||||
from .config import get_config_path
|
from .config import get_config_path
|
||||||
from .editor import get_text_from_editor
|
from .editor import get_text_from_editor
|
||||||
from .editor import get_text_from_stdin
|
from .editor import get_text_from_stdin
|
||||||
from .exception import UserAbort
|
|
||||||
from . import time
|
from . import time
|
||||||
from .override import apply_overrides
|
from .override import apply_overrides
|
||||||
|
|
||||||
|
from jrnl.exception import JrnlException
|
||||||
|
from jrnl.messages import Message
|
||||||
|
from jrnl.messages import MsgText
|
||||||
|
from jrnl.messages import MsgType
|
||||||
|
|
||||||
|
|
||||||
def run(args):
|
def run(args):
|
||||||
"""
|
"""
|
||||||
|
@ -35,7 +37,6 @@ def run(args):
|
||||||
return args.preconfig_cmd(args)
|
return args.preconfig_cmd(args)
|
||||||
|
|
||||||
# Load the config, and extract journal name
|
# Load the config, and extract journal name
|
||||||
try:
|
|
||||||
config = install.load_or_install_jrnl(args.config_file_path)
|
config = install.load_or_install_jrnl(args.config_file_path)
|
||||||
original_config = config.copy()
|
original_config = config.copy()
|
||||||
|
|
||||||
|
@ -44,9 +45,6 @@ def run(args):
|
||||||
|
|
||||||
args = get_journal_name(args, config)
|
args = get_journal_name(args, config)
|
||||||
config = scope_config(config, args.journal_name)
|
config = scope_config(config, args.journal_name)
|
||||||
except UserAbort as err:
|
|
||||||
print(f"\n{err}", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Run post-config command now that config is ready
|
# Run post-config command now that config is ready
|
||||||
if callable(args.postconfig_cmd):
|
if callable(args.postconfig_cmd):
|
||||||
|
@ -138,7 +136,9 @@ def write_mode(args, config, journal, **kwargs):
|
||||||
|
|
||||||
if not raw:
|
if not raw:
|
||||||
logging.error("Write mode: couldn't get raw text")
|
logging.error("Write mode: couldn't get raw text")
|
||||||
sys.exit()
|
raise JrnlException(
|
||||||
|
Message(MsgText.JrnlExceptionMessage.NoTextReceived, MsgType.ERROR)
|
||||||
|
)
|
||||||
|
|
||||||
logging.debug(
|
logging.debug(
|
||||||
'Write mode: appending raw text to journal "%s": %s', args.journal_name, raw
|
'Write mode: appending raw text to journal "%s": %s', args.journal_name, raw
|
||||||
|
@ -202,11 +202,13 @@ def _get_editor_template(config, **kwargs):
|
||||||
logging.debug("Write mode: template loaded: %s", template)
|
logging.debug("Write mode: template loaded: %s", template)
|
||||||
except OSError:
|
except OSError:
|
||||||
logging.error("Write mode: template not loaded")
|
logging.error("Write mode: template not loaded")
|
||||||
print(
|
raise JrnlException(
|
||||||
f"[Could not read template at '{config['template']}']",
|
Message(
|
||||||
file=sys.stderr,
|
MsgText.CantReadTemplate,
|
||||||
|
MsgType.ERROR,
|
||||||
|
{"template": config["template"]},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
return template
|
return template
|
||||||
|
|
||||||
|
@ -243,16 +245,13 @@ def _edit_search_results(config, journal, old_entries, **kwargs):
|
||||||
3. Write modifications to journal
|
3. Write modifications to journal
|
||||||
"""
|
"""
|
||||||
if not config["editor"]:
|
if not config["editor"]:
|
||||||
print(
|
raise JrnlException(
|
||||||
f"""
|
Message(
|
||||||
[{ERROR_COLOR}ERROR{RESET_COLOR}: There is no editor configured.]
|
MsgText.EditorNotConfigured,
|
||||||
|
MsgType.ERROR,
|
||||||
Please specify an editor in config file ({get_config_path()})
|
{"config_file": get_config_path()},
|
||||||
to use the --edit option.
|
)
|
||||||
""",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# separate entries we are not editing
|
# separate entries we are not editing
|
||||||
other_entries = [e for e in old_entries if e not in journal.entries]
|
other_entries = [e for e in old_entries if e not in journal.entries]
|
||||||
|
@ -310,11 +309,7 @@ def _pluralize_entry(num):
|
||||||
|
|
||||||
def _delete_search_results(journal, old_entries, **kwargs):
|
def _delete_search_results(journal, old_entries, **kwargs):
|
||||||
if not journal.entries:
|
if not journal.entries:
|
||||||
print(
|
raise JrnlException(Message(MsgText.NothingToDelete, MsgType.ERROR))
|
||||||
"[No entries deleted, because the search returned no results.]",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
entries_to_delete = journal.prompt_delete_entries()
|
entries_to_delete = journal.prompt_delete_entries()
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class MsgText(Enum):
|
||||||
|
|
||||||
# --- Exceptions ---#
|
# --- Exceptions ---#
|
||||||
UncaughtException = """
|
UncaughtException = """
|
||||||
ERROR
|
{name}
|
||||||
{exception}
|
{exception}
|
||||||
|
|
||||||
This is probably a bug. Please file an issue at:
|
This is probably a bug. Please file an issue at:
|
||||||
|
@ -61,6 +61,14 @@ class MsgText(Enum):
|
||||||
|
|
||||||
KeyboardInterruptMsg = "Aborted by user"
|
KeyboardInterruptMsg = "Aborted by user"
|
||||||
|
|
||||||
|
CantReadTemplate = """
|
||||||
|
Unreadable template
|
||||||
|
Could not read template file at:
|
||||||
|
{template}
|
||||||
|
"""
|
||||||
|
|
||||||
|
NoDefaultJournal = "No default journal configured\n{journals}"
|
||||||
|
|
||||||
# --- Journal status ---#
|
# --- Journal status ---#
|
||||||
JournalNotSaved = "Entry NOT saved to journal"
|
JournalNotSaved = "Entry NOT saved to journal"
|
||||||
|
|
||||||
|
@ -72,6 +80,56 @@ class MsgText(Enum):
|
||||||
HowToQuitWindows = "Ctrl+z and then Enter"
|
HowToQuitWindows = "Ctrl+z and then Enter"
|
||||||
HowToQuitLinux = "Ctrl+d"
|
HowToQuitLinux = "Ctrl+d"
|
||||||
|
|
||||||
|
EditorMisconfigured = """
|
||||||
|
No such file or directory: '{editor_key}'
|
||||||
|
|
||||||
|
Please check the 'editor' key in your config file for errors:
|
||||||
|
editor: '{editor_key}'
|
||||||
|
"""
|
||||||
|
|
||||||
|
EditorNotConfigured = """
|
||||||
|
There is no editor configured
|
||||||
|
|
||||||
|
To use the --edit option, please specify an editor your config file:
|
||||||
|
{config_file}
|
||||||
|
|
||||||
|
For examples of how to configure an external editor, see:
|
||||||
|
https://jrnl.sh/en/stable/external-editors/
|
||||||
|
"""
|
||||||
|
|
||||||
|
NoTextReceived = """
|
||||||
|
Nothing saved to file
|
||||||
|
"""
|
||||||
|
|
||||||
|
# --- Upgrade --- #
|
||||||
|
JournalFailedUpgrade = """
|
||||||
|
The following journal{s} failed to upgrade:
|
||||||
|
{failed_journals}
|
||||||
|
|
||||||
|
Please tell us about this problem at the following URL:
|
||||||
|
https://github.com/jrnl-org/jrnl/issues/new?title=JournalFailedUpgrade
|
||||||
|
"""
|
||||||
|
|
||||||
|
UpgradeAborted = "jrnl was NOT upgraded"
|
||||||
|
|
||||||
|
ImportAborted = "Entries were NOT imported"
|
||||||
|
|
||||||
|
# -- Config --- #
|
||||||
|
AltConfigNotFound = """
|
||||||
|
Alternate configuration file not found at the given path:
|
||||||
|
{config_file}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# --- Password --- #
|
||||||
|
PasswordMaxTriesExceeded = """
|
||||||
|
Too many attempts with wrong password
|
||||||
|
"""
|
||||||
|
|
||||||
|
# --- Search --- #
|
||||||
|
NothingToDelete = """
|
||||||
|
No entries to delete, because the search returned no results
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Message(NamedTuple):
|
class Message(NamedTuple):
|
||||||
text: MsgText
|
text: MsgText
|
||||||
|
|
|
@ -29,7 +29,7 @@ def list_journals(configuration):
|
||||||
from . import config
|
from . import config
|
||||||
|
|
||||||
"""List the journals specified in the configuration file"""
|
"""List the journals specified in the configuration file"""
|
||||||
result = f"Journals defined in {config.get_config_path()}\n"
|
result = f"Journals defined in config ({config.get_config_path()})\n"
|
||||||
ml = min(max(len(k) for k in configuration["journals"]), 20)
|
ml = min(max(len(k) for k in configuration["journals"]), 20)
|
||||||
for journal, cfg in configuration["journals"].items():
|
for journal, cfg in configuration["journals"].items():
|
||||||
result += " * {:{}} -> {}\n".format(
|
result += " * {:{}} -> {}\n".format(
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from jrnl.exception import JrnlException
|
||||||
|
from jrnl.messages import Message
|
||||||
|
from jrnl.messages import MsgText
|
||||||
|
from jrnl.messages import MsgType
|
||||||
|
|
||||||
|
|
||||||
class JRNLImporter:
|
class JRNLImporter:
|
||||||
"""This plugin imports entries from other jrnl files."""
|
"""This plugin imports entries from other jrnl files."""
|
||||||
|
@ -22,8 +27,11 @@ class JRNLImporter:
|
||||||
try:
|
try:
|
||||||
other_journal_txt = sys.stdin.read()
|
other_journal_txt = sys.stdin.read()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("[Entries NOT imported into journal.]", file=sys.stderr)
|
raise JrnlException(
|
||||||
sys.exit(0)
|
Message(MsgText.KeyboardInterruptMsg, MsgType.ERROR),
|
||||||
|
Message(MsgText.ImportAborted, MsgType.WARNING),
|
||||||
|
)
|
||||||
|
|
||||||
journal.import_(other_journal_txt)
|
journal.import_(other_journal_txt)
|
||||||
new_cnt = len(journal.entries)
|
new_cnt = len(journal.entries)
|
||||||
print(
|
print(
|
||||||
|
|
|
@ -10,10 +10,15 @@ from .EncryptedJournal import EncryptedJournal
|
||||||
from .config import is_config_json
|
from .config import is_config_json
|
||||||
from .config import load_config
|
from .config import load_config
|
||||||
from .config import scope_config
|
from .config import scope_config
|
||||||
from .exception import UpgradeValidationException
|
|
||||||
from .exception import UserAbort
|
|
||||||
from .prompt import yesno
|
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
|
||||||
|
from jrnl.messages import MsgType
|
||||||
|
|
||||||
|
|
||||||
def backup(filename, binary=False):
|
def backup(filename, binary=False):
|
||||||
print(f" Created a backup at {filename}.backup", file=sys.stderr)
|
print(f" Created a backup at {filename}.backup", file=sys.stderr)
|
||||||
|
@ -27,13 +32,9 @@ def backup(filename, binary=False):
|
||||||
backup.write(contents)
|
backup.write(contents)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
print(f"\nError: {filename} does not exist.")
|
print(f"\nError: {filename} does not exist.")
|
||||||
try:
|
|
||||||
cont = yesno(f"\nCreate {filename}?", default=False)
|
cont = yesno(f"\nCreate {filename}?", default=False)
|
||||||
if not cont:
|
if not cont:
|
||||||
raise KeyboardInterrupt
|
raise JrnlException(Message(MsgText.UpgradeAborted), MsgType.WARNING)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise UserAbort("jrnl NOT upgraded, exiting.")
|
|
||||||
|
|
||||||
|
|
||||||
def check_exists(path):
|
def check_exists(path):
|
||||||
|
@ -121,12 +122,9 @@ older versions of jrnl anymore.
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
|
||||||
cont = yesno("\nContinue upgrading jrnl?", default=False)
|
cont = yesno("\nContinue upgrading jrnl?", default=False)
|
||||||
if not cont:
|
if not cont:
|
||||||
raise KeyboardInterrupt
|
raise JrnlException(Message(MsgText.UpgradeAborted), MsgType.WARNING)
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise UserAbort("jrnl NOT upgraded, exiting.")
|
|
||||||
|
|
||||||
for journal_name, path in encrypted_journals.items():
|
for journal_name, path in encrypted_journals.items():
|
||||||
print(
|
print(
|
||||||
|
@ -154,15 +152,18 @@ older versions of jrnl anymore.
|
||||||
failed_journals = [j for j in all_journals if not j.validate_parsing()]
|
failed_journals = [j for j in all_journals if not j.validate_parsing()]
|
||||||
|
|
||||||
if len(failed_journals) > 0:
|
if len(failed_journals) > 0:
|
||||||
print(
|
print_msg("Aborting upgrade.", msg=Message.NORMAL)
|
||||||
"\nThe following journal{} failed to upgrade:\n{}".format(
|
|
||||||
"s" if len(failed_journals) > 1 else "",
|
|
||||||
"\n".join(j.name for j in failed_journals),
|
|
||||||
),
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
raise UpgradeValidationException
|
raise JrnlException(
|
||||||
|
Message(
|
||||||
|
MsgText.JournalFailedUpgrade,
|
||||||
|
MsgType.ERROR,
|
||||||
|
{
|
||||||
|
"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
|
# write all journals - or - don't
|
||||||
for j in all_journals:
|
for j in all_journals:
|
||||||
|
|
|
@ -41,6 +41,7 @@ Feature: Delete entries from journal
|
||||||
Scenario Outline: Delete flag with nonsense input deletes nothing (issue #932)
|
Scenario Outline: Delete flag with nonsense input deletes nothing (issue #932)
|
||||||
Given we use the config "<config_file>"
|
Given we use the config "<config_file>"
|
||||||
When we run "jrnl --delete asdfasdf"
|
When we run "jrnl --delete asdfasdf"
|
||||||
|
Then the output should contain "No entries to delete"
|
||||||
When we run "jrnl -99 --short"
|
When we run "jrnl -99 --short"
|
||||||
Then the output should be
|
Then the output should be
|
||||||
2020-08-29 11:11 Entry the first.
|
2020-08-29 11:11 Entry the first.
|
||||||
|
|
|
@ -78,7 +78,7 @@ Feature: Writing new entries.
|
||||||
And we write nothing to the editor if opened
|
And we write nothing to the editor if opened
|
||||||
And we use the password "test" if prompted
|
And we use the password "test" if prompted
|
||||||
When we run "jrnl --edit"
|
When we run "jrnl --edit"
|
||||||
Then the error output should contain "[Nothing saved to file]"
|
Then the error output should contain "Nothing saved to file"
|
||||||
And the editor should have been called
|
And the editor should have been called
|
||||||
|
|
||||||
Examples: configs
|
Examples: configs
|
||||||
|
|
|
@ -2,6 +2,7 @@ import pytest
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from jrnl.install import find_alt_config
|
from jrnl.install import find_alt_config
|
||||||
|
from jrnl.exception import JrnlException
|
||||||
|
|
||||||
|
|
||||||
def test_find_alt_config(request):
|
def test_find_alt_config(request):
|
||||||
|
@ -14,9 +15,9 @@ def test_find_alt_config(request):
|
||||||
|
|
||||||
def test_find_alt_config_not_exist(request):
|
def test_find_alt_config_not_exist(request):
|
||||||
bad_config_path = os.path.join(
|
bad_config_path = os.path.join(
|
||||||
request.fspath.dirname, "..", "data", "configs", "not-existing-config.yaml"
|
request.fspath.dirname, "..", "data", "configs", "does-not-exist.yaml"
|
||||||
)
|
)
|
||||||
with pytest.raises(SystemExit) as ex:
|
with pytest.raises(JrnlException) as ex:
|
||||||
found_alt_config = find_alt_config(bad_config_path)
|
found_alt_config = find_alt_config(bad_config_path)
|
||||||
assert found_alt_config is not None
|
assert found_alt_config is not None
|
||||||
assert isinstance(ex.value, SystemExit)
|
assert isinstance(ex.value, JrnlException)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from jrnl.exception import JrnlException
|
from jrnl.exception import JrnlException
|
||||||
|
|
||||||
from jrnl.plugins.fancy_exporter import check_provided_linewrap_viability
|
from jrnl.plugins.fancy_exporter import check_provided_linewrap_viability
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue