Clean up help screen, get rid of util.py (#1027)

* More refactoring of cli.py

break up code from cli.py (now in jrnl.py) up into smaller functions
get rid of export mode
move --encrypt and --decrypt to commands.py
clean up the help screen even more
update flag name for import

* reorganize code, move around lots of functions

* clean up import statements

* move run function out of cli and into jrnl

* rename confusingly named function

* move editor function into editor file

* rename parse_args.py to args.py to make room for more args functions

* Fix error in test suite for windows

I accidentally flipped the conditional, so this fixes it.

Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>

* Update app description on help screen

Co-authored-by: Micah Jerome Ellison <micah.jerome.ellison@gmail.com>
This commit is contained in:
Jonathan Wren 2020-08-22 11:40:39 -07:00 committed by GitHub
parent 7c3abb2625
commit 631e08a557
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 981 additions and 775 deletions

View file

@ -1,5 +1,4 @@
#!/usr/bin/env python
"""
jrnl
@ -7,8 +6,7 @@
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
the Free Software Foundation, either version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
@ -18,94 +16,11 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
import packaging.version
import platform
import sys
from . import install, plugins, util
from .parse_args import parse_args
from .Journal import PlainJournal, open_journal
from .util import ERROR_COLOR, RESET_COLOR, UserAbort
from .util import get_journal_name
log = logging.getLogger(__name__)
logging.getLogger("keyring.backend").setLevel(logging.ERROR)
def guess_mode(args, config):
"""Guesses the mode (compose, read or export) from the given arguments"""
compose = True
export = False
if (
args.decrypt is not False
or args.encrypt is not False
or args.export is not False
or any((args.short, args.tags, args.edit, args.delete))
):
compose = False
export = True
elif any(
(
args.start_date,
args.end_date,
args.on_date,
args.limit,
args.strict,
args.starred,
args.contains,
)
):
# Any sign of displaying stuff?
compose = False
elif args.text and all(
word[0] in config["tagsymbols"] for word in " ".join(args.text).split()
):
# No date and only tags?
compose = False
return compose, export
def encrypt(journal, filename=None):
""" Encrypt into new file. If filename is not set, we encrypt the journal file itself. """
from .EncryptedJournal import EncryptedJournal
journal.config["encrypt"] = True
new_journal = EncryptedJournal.from_journal(journal)
new_journal.write(filename)
print(
"Journal encrypted to {}.".format(filename or new_journal.config["journal"]),
file=sys.stderr,
)
def decrypt(journal, filename=None):
""" Decrypts into new file. If filename is not set, we encrypt the journal file itself. """
journal.config["encrypt"] = False
new_journal = PlainJournal.from_journal(journal)
new_journal.write(filename)
print(
"Journal decrypted to {}.".format(filename or new_journal.config["journal"]),
file=sys.stderr,
)
def update_config(config, new_config, scope, force_local=False):
"""Updates a config dict with new values - either global if scope is None
or config['journals'][scope] is just a string pointing to a journal file,
or within the scope"""
if scope and type(config["journals"][scope]) is dict: # Update to journal specific
config["journals"][scope].update(new_config)
elif scope and force_local: # Convert to dict
config["journals"][scope] = {"journal": config["journals"][scope]}
config["journals"][scope].update(new_config)
else:
config.update(new_config)
from .jrnl import run
from .args import parse_args
def configure_logger(debug=False):
@ -113,194 +28,20 @@ def configure_logger(debug=False):
level=logging.DEBUG if debug else logging.ERROR,
format="%(levelname)-8s %(name)-12s %(message)s",
)
logging.getLogger("parsedatetime").setLevel(
logging.INFO
) # disable parsedatetime debug logging
logging.getLogger("parsedatetime").setLevel(logging.INFO)
logging.getLogger("keyring.backend").setLevel(logging.ERROR)
def run(manual_args=None):
if packaging.version.parse(platform.python_version()) < packaging.version.parse(
"3.7"
):
print(
f"""{ERROR_COLOR}
ERROR: Python version {platform.python_version()} not supported.
Please update to Python 3.7 (or higher) in order to use jrnl.
{RESET_COLOR}""",
file=sys.stderr,
)
sys.exit(1)
if manual_args is None:
manual_args = sys.argv[1:]
args = parse_args(manual_args)
configure_logger(args.debug)
# Run command if possible before config is available
if callable(args.preconfig_cmd):
args.preconfig_cmd(args)
sys.exit(0)
# Load the config
def cli(manual_args=None):
try:
config = install.load_or_install_jrnl()
original_config = config.copy()
args = get_journal_name(args, config)
config = util.scope_config(config, args.journal_name)
except UserAbort as err:
print(f"\n{err}", file=sys.stderr)
sys.exit(1)
if manual_args is None:
manual_args = sys.argv[1:]
# Run post-config command now that config is ready
if callable(args.postconfig_cmd):
args.postconfig_cmd(args=args, config=config)
sys.exit(0)
args = parse_args(manual_args)
configure_logger(args.debug)
logging.debug("Parsed args: %s", args)
# --- All the standalone commands are now done --- #
return run(args)
# Get the journal we're going to be working with
journal = open_journal(args.journal_name, config)
mode_compose, mode_export = guess_mode(args, config)
if mode_compose and not args.text:
if not sys.stdin.isatty():
# Piping data into jrnl
raw = sys.stdin.read()
elif config["editor"]:
template = ""
if config["template"]:
try:
template = open(config["template"]).read()
except OSError:
print(
f"[Could not read template at '{config['template']}']",
file=sys.stderr,
)
sys.exit(1)
raw = util.get_text_from_editor(config, template)
else:
try:
_how_to_quit = (
"Ctrl+z and then Enter" if "win32" in sys.platform else "Ctrl+d"
)
print(
f"[Writing Entry; on a blank line, press {_how_to_quit} to finish writing]\n",
file=sys.stderr,
)
raw = sys.stdin.read()
except KeyboardInterrupt:
print("[Entry NOT saved to journal]", file=sys.stderr)
sys.exit(0)
if raw:
args.text = [raw]
else:
sys.exit()
# Writing mode
if mode_compose:
raw = " ".join(args.text).strip()
log.debug('Appending raw line "%s" to journal "%s"', raw, args.journal_name)
journal.new_entry(raw)
print(f"[Entry added to {args.journal_name} journal]", file=sys.stderr)
journal.write()
if not mode_compose:
old_entries = journal.entries
if args.on_date:
args.start_date = args.end_date = args.on_date
journal.filter(
tags=args.text,
start_date=args.start_date,
end_date=args.end_date,
strict=args.strict,
starred=args.starred,
exclude=args.excluded,
contains=args.contains,
)
journal.limit(args.limit)
# Reading mode
if not mode_compose and not mode_export:
print(journal.pprint())
# Various export modes
elif args.short:
print(journal.pprint(short=True))
elif args.tags:
print(plugins.get_exporter("tags").export(journal))
elif args.export is not False:
exporter = plugins.get_exporter(args.export)
print(exporter.export(journal, args.output))
elif args.encrypt is not False:
encrypt(journal, filename=args.encrypt)
# Not encrypting to a separate file: update config!
if not args.encrypt:
update_config(
original_config, {"encrypt": True}, args.journal_name, force_local=True
)
install.save_config(original_config)
elif args.decrypt is not False:
decrypt(journal, filename=args.decrypt)
# Not decrypting to a separate file: update config!
if not args.decrypt:
update_config(
original_config, {"encrypt": False}, args.journal_name, force_local=True
)
install.save_config(original_config)
elif args.edit:
if not config["editor"]:
print(
"[{1}ERROR{2}: You need to specify an editor in {0} to use the --edit function.]".format(
install.CONFIG_FILE_PATH, ERROR_COLOR, RESET_COLOR
),
file=sys.stderr,
)
sys.exit(1)
other_entries = [e for e in old_entries if e not in journal.entries]
# Edit
old_num_entries = len(journal)
edited = util.get_text_from_editor(config, journal.editable_str())
journal.parse_editable_str(edited)
num_deleted = old_num_entries - len(journal)
num_edited = len([e for e in journal.entries if e.modified])
prompts = []
if num_deleted:
prompts.append(
"{} {} deleted".format(
num_deleted, "entry" if num_deleted == 1 else "entries"
)
)
if num_edited:
prompts.append(
"{} {} modified".format(
num_edited, "entry" if num_deleted == 1 else "entries"
)
)
if prompts:
print("[{}]".format(", ".join(prompts).capitalize()), file=sys.stderr)
journal.entries += other_entries
journal.sort()
journal.write()
elif args.delete:
if journal.entries:
entries_to_delete = journal.prompt_delete_entries()
if entries_to_delete:
journal.entries = old_entries
journal.delete_entries(entries_to_delete)
journal.write()
else:
print(
"No entries deleted, because the search returned no results.",
file=sys.stderr,
)
except KeyboardInterrupt:
return 1