From a54ed90259f4ab2685e9d5a8dbd0107150dea1a1 Mon Sep 17 00:00:00 2001 From: Jonathan Wren Date: Fri, 3 Jul 2020 14:29:00 -0700 Subject: [PATCH] Move import to be standalone command to reduce clutter in cli.py --- features/core.feature | 40 ++++++++++++++++++++++++-- features/steps/core.py | 8 ++++-- jrnl/Journal.py | 21 +++++++++----- jrnl/cli.py | 62 ++++++++++++++-------------------------- jrnl/commands.py | 11 +++++++ jrnl/install.py | 5 ++-- jrnl/parsing.py | 37 +++++++++++------------- jrnl/util.py | 2 +- tests/test_parse_args.py | 11 +++---- 9 files changed, 113 insertions(+), 84 deletions(-) diff --git a/features/core.feature b/features/core.feature index 53d37f8b..6f4787cb 100644 --- a/features/core.feature +++ b/features/core.feature @@ -136,6 +136,40 @@ Feature: Basic reading and writing to a journal Then the output should contain "jrnl" And the output should contain "Python" - Scenario: Version warning appears for versions below 3.7 - When we run "jrnl --diagnostic" - Then the Python version warning should appear if our version is below 3.7 + Scenario: --import allows new entry to journal + Given we use the config "basic.yaml" + When we run "jrnl --import" and pipe "[2020-07-05 15:00] Observe and import." + And we run "jrnl -1" + Then the journal should contain "[2020-07-05 15:00] Observe and import." + And the output should contain "Observe and import" + + Scenario: --import allows new large entry to journal + Given we use the config "basic.yaml" + When we run "jrnl --import" and pipe + """ + [2020-07-05 15:00] Observe and import. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent malesuada quis + est ac dignissim. Aliquam dignissim rutrum pretium. Phasellus pellentesque augue + et venenatis facilisis. Suspendisse potenti. Sed dignissim sed nisl eu consequat. + Aenean ante ex, elementum ut interdum et, mattis eget lacus. In commodo nulla nec + tellus placerat, sed ultricies metus bibendum. Duis eget venenatis erat. In at + dolor dui end of entry. + """ + And we run "jrnl -1" + Then the journal should contain "[2020-07-05 15:00] Observe and import." + And the output should contain "Observe and import" + And the output should contain "Lorem ipsum" + And the output should contain "end of entry." + + Scenario: --import allows import of multiple entries to journal + Given we use the config "basic.yaml" + When we run "jrnl --import" and pipe + """ + [2020-07-05 15:00] Observe and import. + Lorem ipsum dolor sit amet, consectetur adipiscing elit. + + [2020-07-05 15:01] Twice as nice. + Sed dignissim sed nisl eu consequat. + """ + Then the journal should contain "[2020-07-05 15:00] Observe and import." + Then the journal should contain "[2020-07-05 15:01] Twice as nice." diff --git a/features/steps/core.py b/features/steps/core.py index 9c8fa8e6..d1446a48 100644 --- a/features/steps/core.py +++ b/features/steps/core.py @@ -210,8 +210,12 @@ def run_with_input(context, command, inputs=""): @when('we run "{command}"') +@when('we run "{command}" and pipe') +@when('we run "{command}" and pipe "{text}"') @when('we run "{command}" with cache directory "{cache_dir}"') -def run(context, command, cache_dir=None): +def run(context, command, text="", cache_dir=None): + text = text or context.text or "" + if cache_dir is not None: cache_dir = os.path.join("features", "cache", cache_dir) command = command.format(cache_dir=cache_dir) @@ -224,7 +228,7 @@ def run(context, command, cache_dir=None): try: with patch("sys.argv", args), patch( "subprocess.call", side_effect=_mock_editor - ): + ), patch("sys.stdin.read", side_effect=lambda: text): cli.run(args[1:]) context.exit_status = 0 except SystemExit as e: diff --git a/jrnl/Journal.py b/jrnl/Journal.py index 00aa84f3..8968cb5f 100644 --- a/jrnl/Journal.py +++ b/jrnl/Journal.py @@ -371,7 +371,7 @@ class LegacyJournal(Journal): return entries -def open_journal(name, config, legacy=False): +def open_journal(journal_name, config, legacy=False): """ Creates a normal, encrypted or DayOne journal based on the passed config. If legacy is True, it will open Journals with legacy classes build for @@ -394,11 +394,18 @@ def open_journal(name, config, legacy=False): if not config["encrypt"]: if legacy: - return LegacyJournal(name, **config).open() - return PlainJournal(name, **config).open() - else: - from . import EncryptedJournal + return LegacyJournal(journal_name, **config).open() + return PlainJournal(journal_name, **config).open() + from . import EncryptedJournal + + try: if legacy: - return EncryptedJournal.LegacyEncryptedJournal(name, **config).open() - return EncryptedJournal.EncryptedJournal(name, **config).open() + return EncryptedJournal.LegacyEncryptedJournal( + 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) diff --git a/jrnl/cli.py b/jrnl/cli.py index 33712f47..c70bfccb 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -25,10 +25,10 @@ import platform import sys from . import install, plugins, util -from .parsing import parse_args_before_config -from .parsing import parse_args_after_config +from .parsing import parse_args from .Journal import PlainJournal, open_journal from .util import WARNING_COLOR, ERROR_COLOR, RESET_COLOR, UserAbort +from .util import get_journal_name log = logging.getLogger(__name__) logging.getLogger("keyring.backend").setLevel(logging.ERROR) @@ -38,12 +38,7 @@ def guess_mode(args, config): """Guesses the mode (compose, read or export) from the given arguments""" compose = True export = False - import_ = False - if args.import_ is not False: - compose = False - export = False - import_ = True - elif ( + if ( args.decrypt is not False or args.encrypt is not False or args.export is not False @@ -70,7 +65,7 @@ def guess_mode(args, config): # No date and only tags? compose = False - return compose, export, import_ + return compose, export def encrypt(journal, filename=None): @@ -139,7 +134,7 @@ Python 3.7 (or higher) soon. if manual_args is None: manual_args = sys.argv[1:] - args = parse_args_before_config(manual_args) + args = parse_args(manual_args) configure_logger(args.debug) # Run command if possible before config is available @@ -150,38 +145,24 @@ Python 3.7 (or higher) soon. # Load the config 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) - # Run command now that config is available + # Run post-config command now that config is ready if callable(args.postconfig_cmd): - args.postconfig_cmd(config=config, args=args) + args.postconfig_cmd(args=args, config=config) sys.exit(0) - args = parse_args_after_config(args, config) + # --- All the standalone commands are now done --- # - log.debug('Using configuration "%s"', config) - original_config = config.copy() + # Get the journal we're going to be working with + journal = open_journal(args.journal_name, config) - config = util.scope_config(config, args.journal_name) - - log.debug('Using journal "%s"', args.journal_name) - - mode_compose, mode_export, mode_import = guess_mode(args, config) - - # How to quit writing? - if "win32" in sys.platform: - _exit_multiline_code = "on a blank line, press Ctrl+Z and then Enter" - else: - _exit_multiline_code = "press Ctrl+D" - - # This is where we finally open the journal! - try: - journal = open_journal(args.journal_name, config) - except KeyboardInterrupt: - print("[Interrupted while opening journal]", file=sys.stderr) - sys.exit(1) + mode_compose, mode_export = guess_mode(args, config) if mode_compose and not args.text: if not sys.stdin.isatty(): @@ -201,25 +182,24 @@ Python 3.7 (or higher) soon. 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( - "[Compose Entry; " + _exit_multiline_code + " to finish writing]\n", + 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) + print("[Entry NOT saved to journal]", file=sys.stderr) sys.exit(0) if raw: args.text = [raw] else: sys.exit() - # Import mode - if mode_import: - plugins.get_importer(args.import_).import_(journal, args.input) - # Writing mode - elif mode_compose: + 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) @@ -242,7 +222,7 @@ Python 3.7 (or higher) soon. journal.limit(args.limit) # Reading mode - if not mode_compose and not mode_export and not mode_import: + if not mode_compose and not mode_export: print(journal.pprint()) # Various export modes diff --git a/jrnl/commands.py b/jrnl/commands.py index 66326393..8461d64a 100644 --- a/jrnl/commands.py +++ b/jrnl/commands.py @@ -21,3 +21,14 @@ def postconfig_list(config, **kwargs): from .util import list_journals print(list_journals(config)) + + +def postconfig_import(args, config, **kwargs): + from .plugins import get_importer + from .Journal import open_journal + + # Requires opening the journal + journal = open_journal(args.journal_name, config) + + format = args.export if args.export else "jrnl" + get_importer(format).import_(journal, args.input) diff --git a/jrnl/install.py b/jrnl/install.py index 78745161..80934a71 100644 --- a/jrnl/install.py +++ b/jrnl/install.py @@ -117,14 +117,15 @@ def load_or_install_jrnl(): upgrade_config(config) verify_config(config) - return config else: log.debug("Configuration file not found, installing jrnl...") try: config = install() except KeyboardInterrupt: raise UserAbort("Installation aborted") - return config + + log.debug('Using configuration "%s"', config) + return config def install(): diff --git a/jrnl/parsing.py b/jrnl/parsing.py index 5299f75e..46df31c3 100644 --- a/jrnl/parsing.py +++ b/jrnl/parsing.py @@ -8,8 +8,8 @@ from .plugins import EXPORT_FORMATS from .commands import preconfig_version from .commands import preconfig_diagnostic from .commands import postconfig_list +from .commands import postconfig_import from .util import deprecated_cmd -from .util import get_journal_name class WrappingFormatter(argparse.RawDescriptionHelpFormatter): @@ -18,7 +18,7 @@ class WrappingFormatter(argparse.RawDescriptionHelpFormatter): return textwrap.wrap(text, width=56) -def parse_args_before_config(args=[]): +def parse_args(args=[]): """ Argument parsing that is doable before the config is available. Everything else goes into "text" for later parsing. @@ -27,7 +27,12 @@ def parse_args_before_config(args=[]): formatter_class=WrappingFormatter, add_help=False, description="The command-line note-taking and journaling app.", - epilog="", + epilog=textwrap.dedent( + """ + Thank you to all of our contributors! Come see the whole list of code and + financial contributors at https://github.com/jrnl-org/jrnl. And special + thanks to Bad Lip Reading for the Yoda joke in the Writing section above.""" + ), ) optional = parser.add_argument_group("Optional Arguments") @@ -108,13 +113,11 @@ def parse_args_before_config(args=[]): ) standalone.add_argument( "--import", + action="store_const", metavar="TYPE", - dest="import_", - choices=IMPORT_FORMATS, + dest="postconfig_cmd", + const=postconfig_import, help=f"Import entries into your journal. TYPE can be: {util.oxford_list(IMPORT_FORMATS)} (default: jrnl)", - default=False, - const="jrnl", - nargs="?", ) standalone.add_argument( "-i", @@ -132,17 +135,17 @@ def parse_args_before_config(args=[]): The date and the following colon ("yesterday:") are optional. If you leave them out, "now" will be used: - jrnl Then I rolled the log over, and underneath was a tiny little stick. + jrnl Then I rolled the log over. Also, you can mark extra special entries ("star" them) with an asterisk: - jrnl *That log had a child! + jrnl *And underneath was a tiny little stick. Please note that asterisks might be a special character in your shell, so you - might have to escape them. When in doubt about escaping, put single quotes - around your entire entry: + might have to escape them. When in doubt about escaping, put quotes around + your entire entry: - jrnl 'saturday at 8pm: *Always pass on what you have learned. -Yoda'""" + jrnl "saturday at 2am: *Then I was like 'That log had a child!'" """ composing = parser.add_argument_group( "Writing", textwrap.dedent(compose_msg).strip() @@ -270,11 +273,3 @@ def parse_args_before_config(args=[]): # return parser.parse_args(args) return parser.parse_intermixed_args(args) - - -def parse_args_after_config(args, config): - # print(str(args)) # @todo take this out - - args = get_journal_name(args, config) - - return args diff --git a/jrnl/util.py b/jrnl/util.py index f95a783b..9e1fb3df 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -150,7 +150,6 @@ def scope_config(config, journal_name): else: # But also just give them a string to point to the journal file config["journal"] = journal_conf - config.pop("journals") return config @@ -335,4 +334,5 @@ def get_journal_name(args, config): print(list_journals(config), file=sys.stderr) sys.exit(1) + log.debug("Using journal name: %s", args.journal_name) return args diff --git a/tests/test_parse_args.py b/tests/test_parse_args.py index 1be4e196..dbec5a6b 100644 --- a/tests/test_parse_args.py +++ b/tests/test_parse_args.py @@ -1,4 +1,4 @@ -from jrnl.cli import parse_args_before_config +from jrnl.cli import parse_args import pytest import shlex @@ -6,7 +6,7 @@ import shlex def cli_as_dict(str): cli = shlex.split(str) - args = parse_args_before_config(cli) + args = parse_args(cli) return vars(args) @@ -21,7 +21,6 @@ def expected_args(**kwargs): "end_date": None, "excluded": [], "export": False, - "import_": False, "input": False, "limit": None, "on_date": None, @@ -106,11 +105,9 @@ def test_export_alone(): def test_import_alone(): - assert cli_as_dict("--import jrnl") == expected_args(import_="jrnl") + from jrnl.commands import postconfig_import - -def test_import_defaults_to_jrnl(): - assert cli_as_dict("--import") == expected_args(import_="jrnl") + assert cli_as_dict("--import") == expected_args(postconfig_cmd=postconfig_import) def test_input_flag_alone():