diff --git a/jrnl/cli.py b/jrnl/cli.py index 50bde3ff..e7beeca7 100644 --- a/jrnl/cli.py +++ b/jrnl/cli.py @@ -25,7 +25,7 @@ import platform import sys from . import install, plugins, util -from .commands import list_journals +from .util import list_journals from .parsing import parse_args_before_config from .Journal import PlainJournal, open_journal from .util import WARNING_COLOR, ERROR_COLOR, RESET_COLOR, UserAbort @@ -148,7 +148,7 @@ Python 3.7 (or higher) soon. configure_logger(args.debug) # Run command if possible before config is available - if args.preconfig_cmd is not None: + if callable(args.preconfig_cmd): args.preconfig_cmd(args) sys.exit(0) @@ -160,7 +160,7 @@ Python 3.7 (or higher) soon. sys.exit(1) # Run command now that config is available - if args.postconfig_cmd is not None: + if callable(args.postconfig_cmd): args.postconfig_cmd(config=config, args=args) sys.exit(0) diff --git a/jrnl/commands.py b/jrnl/commands.py index 2c1f2d90..66326393 100644 --- a/jrnl/commands.py +++ b/jrnl/commands.py @@ -1,17 +1,3 @@ -def deprecated_cmd(old_cmd, new_cmd, callback, **kwargs): - import sys - from .util import RESET_COLOR, WARNING_COLOR - - print( - WARNING_COLOR, - f"\nThe command {old_cmd} is deprecated and will be removed from jrnl soon.\n" - f"Please use {new_cmd} instead.\n", - RESET_COLOR, - file=sys.stderr, - ) - callback(**kwargs) - - def preconfig_diagnostic(_): import platform import sys @@ -31,22 +17,7 @@ def preconfig_version(_): print(version_str) -def preconfig_command(args): - print("this is a pre-config command") - - def postconfig_list(config, **kwargs): + from .util import list_journals + print(list_journals(config)) - - -def list_journals(config): - from . import install - - """List the journals specified in the configuration file""" - result = f"Journals defined in {install.CONFIG_FILE_PATH}\n" - ml = min(max(len(k) for k in config["journals"]), 20) - for journal, cfg in config["journals"].items(): - result += " * {:{}} -> {}\n".format( - journal, ml, cfg["journal"] if isinstance(cfg, dict) else cfg - ) - return result diff --git a/jrnl/parsing.py b/jrnl/parsing.py index b84ab1f3..b5dc31f5 100644 --- a/jrnl/parsing.py +++ b/jrnl/parsing.py @@ -1,11 +1,20 @@ import argparse import re +import textwrap -from . import plugins +from .plugins import util +from .plugins import IMPORT_FORMATS +from .plugins import EXPORT_FORMATS from .commands import preconfig_version from .commands import preconfig_diagnostic from .commands import postconfig_list -from .commands import deprecated_cmd +from .util import deprecated_cmd + + +class WrappingFormatter(argparse.RawDescriptionHelpFormatter): + def _split_lines(self, text, width): + text = self._whitespace_matcher.sub(" ", text).strip() + return textwrap.wrap(text, width=56) def parse_args_before_config(args=None): @@ -13,165 +22,72 @@ def parse_args_before_config(args=None): Argument parsing that is doable before the config is available. Everything else goes into "text" for later parsing. """ - parser = argparse.ArgumentParser() - parser.add_argument( - "-v", + parser = argparse.ArgumentParser( + formatter_class=WrappingFormatter, + add_help=False, + description="The command-line note-taking and journaling app.", + epilog="", + ) + + optional = parser.add_argument_group("Optional Arguments") + optional.add_argument( + "--debug", + dest="debug", + action="store_true", + help="Print information useful for troubleshooting", + ) + + standalone = parser.add_argument_group( + "Standalone Commands", + "These commands will exit after they complete. You may only run one at a time.", + ) + standalone.add_argument("--help", action="help", help="Show this help message") + standalone.add_argument("-h", action="help", help=argparse.SUPPRESS) + standalone.add_argument( "--version", action="store_const", const=preconfig_version, dest="preconfig_cmd", - help="prints version information and exits", + help="prints version information", ) - - parser.add_argument( - "--cmd1", + standalone.add_argument( + "-v", action="store_const", - const=lambda: print("cmd1"), + const=preconfig_version, dest="preconfig_cmd", + help=argparse.SUPPRESS, ) - parser.add_argument( + standalone.add_argument( "--diagnostic", action="store_const", const=preconfig_diagnostic, dest="preconfig_cmd", - help="outputs diagnostic information and exits", + help=argparse.SUPPRESS, ) - - parser.add_argument( - "--ls", + standalone.add_argument( "--list", action="store_const", const=postconfig_list, dest="postconfig_cmd", - help="lists all configured journals", + help="list all configured journals", ) - - parser.add_argument( + standalone.add_argument( + "--ls", + action="store_const", + const=postconfig_list, + dest="postconfig_cmd", + help=argparse.SUPPRESS, + ) + standalone.add_argument( "-ls", action="store_const", const=lambda **kwargs: deprecated_cmd( - "-ls", "--ls or --list", callback=postconfig_list, **kwargs + "-ls", "--list or --ls", callback=postconfig_list, **kwargs ), dest="postconfig_cmd", - help="displays accessible journals", + help=argparse.SUPPRESS, ) - - parser.add_argument( - "-d", "--debug", dest="debug", action="store_true", help="execute in debug mode" - ) - - composing = parser.add_argument_group( - "Composing", - 'To write an entry simply write it on the command line, e.g. "jrnl yesterday at 1pm: Went to the gym."', - ) - - reading = parser.add_argument_group( - "Reading", - "Specifying either of these parameters will display posts of your journal", - ) - reading.add_argument( - "-from", dest="start_date", metavar="DATE", help="View entries after this date" - ) - reading.add_argument( - "-until", - "-to", - dest="end_date", - metavar="DATE", - help="View entries before this date", - ) - reading.add_argument( - "-contains", dest="contains", help="View entries containing a specific string" - ) - reading.add_argument( - "-on", dest="on_date", metavar="DATE", help="View entries on this date" - ) - reading.add_argument( - "-and", - dest="strict", - action="store_true", - help="Filter by tags using AND (default: OR)", - ) - reading.add_argument( - "-starred", - dest="starred", - action="store_true", - help="Show only starred entries", - ) - reading.add_argument( - "-n", - dest="limit", - default=None, - metavar="N", - help="Shows the last n entries matching the filter. '-n 3' and '-3' have the same effect.", - nargs="?", - type=int, - ) - reading.add_argument( - "-not", - dest="excluded", - nargs="?", - default=[], - metavar="E", - action="append", - help="Exclude entries with these tags", - ) - - exporting = parser.add_argument_group( - "Export / Import", "Options for transmogrifying your journal" - ) - exporting.add_argument( - "-s", - "--short", - dest="short", - action="store_true", - help="Show only titles or line containing the search tags", - ) - exporting.add_argument( - "--tags", - dest="tags", - action="store_true", - help="Returns a list of all tags and number of occurences", - ) - exporting.add_argument( - "--export", - metavar="TYPE", - dest="export", - choices=plugins.EXPORT_FORMATS, - help="Export your journal. TYPE can be {}.".format( - plugins.util.oxford_list(plugins.EXPORT_FORMATS) - ), - default=False, - const=None, - ) - exporting.add_argument( - "-o", - metavar="OUTPUT", - dest="output", - help="Optionally specifies output file when using --export. If OUTPUT is a directory, exports each entry into an individual file instead.", - default=False, - const=None, - ) - exporting.add_argument( - "--import", - metavar="TYPE", - dest="import_", - choices=plugins.IMPORT_FORMATS, - help="Import entries into your journal. TYPE can be {}, and it defaults to jrnl if nothing else is specified.".format( - plugins.util.oxford_list(plugins.IMPORT_FORMATS) - ), - default=False, - const="jrnl", - nargs="?", - ) - exporting.add_argument( - "-i", - metavar="INPUT", - dest="input", - help="Optionally specifies input file when using --import.", - default=False, - const=None, - ) - exporting.add_argument( + standalone.add_argument( "--encrypt", metavar="FILENAME", dest="encrypt", @@ -180,7 +96,7 @@ def parse_args_before_config(args=None): default=False, const=None, ) - exporting.add_argument( + standalone.add_argument( "--decrypt", metavar="FILENAME", dest="decrypt", @@ -189,22 +105,163 @@ def parse_args_before_config(args=None): default=False, const=None, ) + standalone.add_argument( + "--import", + metavar="TYPE", + dest="import_", + choices=IMPORT_FORMATS, + 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", + metavar="FILENAME", + dest="input", + help="Optionally specifies input file when using --import.", + default=False, + const=None, + ) + + compose_msg = """ To add a new entry into your journal, simply write it on the command line: + + jrnl yesterday: I was walking and I found this big log. + + 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. + + Also, you can mark extra special entries ("star" them) with an asterisk: + + jrnl *That log had a child! + + 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: + + jrnl 'saturday at 8pm: *Always pass on what you have learned. -Yoda'""" + + composing = parser.add_argument_group( + "Writing", textwrap.dedent(compose_msg).strip() + ) + composing.add_argument("text", metavar="", nargs="*") + + read_msg = ( + "To find entries from your journal, use any combination of the below filters." + ) + reading = parser.add_argument_group("Searching", textwrap.dedent(read_msg)) + reading.add_argument( + "-on", dest="on_date", metavar="DATE", help="Show entries on this date" + ) + reading.add_argument( + "-from", + dest="start_date", + metavar="DATE", + help="Show entries after, or on, this date", + ) + reading.add_argument( + "-to", + dest="end_date", + metavar="DATE", + help="Show entries before, or on, this date (alias: -until)", + ) + reading.add_argument( + "-until", dest="end_date", help=argparse.SUPPRESS, + ) + reading.add_argument( + "-contains", + dest="contains", + metavar="TEXT", + help="Show entries containing specific text (put quotes around text with spaces)", + ) + reading.add_argument( + "-and", + dest="strict", + action="store_true", + help='Show only entries that match all conditions, like saying "x AND y" (default: OR)', + ) + reading.add_argument( + "-starred", + dest="starred", + action="store_true", + help="Show only starred entries (marked with *)", + ) + reading.add_argument( + "-n", + dest="limit", + default=None, + metavar="NUMBER", + help="Show a maximum of NUMBER entries (note: '-n 3' and '-3' have the same effect)", + nargs="?", + type=int, + ) + reading.add_argument( + "-not", + dest="excluded", + nargs="?", + default=[], + metavar="TAG", + action="append", + help="Exclude entries with this tag", + ) + + search_options_msg = """ These help you do various tasks with the selected entries from your search. + If used on their own (with no search), they will act on your entire journal""" + exporting = parser.add_argument_group( + "Options for Searching", textwrap.dedent(search_options_msg) + ) exporting.add_argument( "--edit", dest="edit", - help="Opens your editor to edit the selected entries.", + help="Opens the selected entries in your configured editor", action="store_true", ) - exporting.add_argument( "--delete", dest="delete", action="store_true", - help="Opens an interactive interface for deleting entries.", + help="Interactively deletes selected entries", + ) + exporting.add_argument( + "--format", + metavar="TYPE", + dest="export", + choices=EXPORT_FORMATS, + help=f"Display selected entries in an alternate format (other than jrnl). TYPE can be: {util.oxford_list(EXPORT_FORMATS)}.", + default=False, + ) + exporting.add_argument( + "--export", + metavar="TYPE", + dest="export", + choices=EXPORT_FORMATS, + help=argparse.SUPPRESS, + ) + exporting.add_argument( + "--tags", + dest="tags", + action="store_true", + help="Alias for '--format tags'. Returns a list of all tags and number of occurences", + ) + exporting.add_argument( + "--short", + dest="short", + action="store_true", + help="Show only titles or line containing the search tags", + ) + exporting.add_argument( + "-s", dest="short", action="store_true", help=argparse.SUPPRESS, + ) + exporting.add_argument( + "-o", + metavar="FILENAME", + dest="output", + help="Optionally specifies output file (or directory) when using --format.", + default=False, + const=None, ) - - # Everything else - composing.add_argument("text", metavar="", nargs="*") if not args: args = [] diff --git a/jrnl/util.py b/jrnl/util.py index 8ebaea41..3e03d99f 100644 --- a/jrnl/util.py +++ b/jrnl/util.py @@ -289,3 +289,35 @@ def split_title(text): if not sep: return text, "" return text[: sep.end()].strip(), text[sep.end() :].strip() + + +def deprecated_cmd(old_cmd, new_cmd, callback=None, **kwargs): + import sys + import textwrap + from .util import RESET_COLOR, WARNING_COLOR + + log = logging.getLogger(__name__) + + warning_msg = f""" + The command {old_cmd} is deprecated and will be removed from jrnl soon. + Please us {new_cmd} instead. + """ + warning_msg = textwrap.dedent(warning_msg) + log.warning(warning_msg) + print(f"{WARNING_COLOR}{warning_msg}{RESET_COLOR}", file=sys.stderr) + + if callback is not None: + callback(**kwargs) + + +def list_journals(config): + from . import install + + """List the journals specified in the configuration file""" + result = f"Journals defined in {install.CONFIG_FILE_PATH}\n" + ml = min(max(len(k) for k in config["journals"]), 20) + for journal, cfg in config["journals"].items(): + result += " * {:{}} -> {}\n".format( + journal, ml, cfg["journal"] if isinstance(cfg, dict) else cfg + ) + return result diff --git a/tests/test_parse_args.py b/tests/test_parse_args.py index 9df8c200..1be4e196 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 as parse_args +from jrnl.cli import parse_args_before_config import pytest import shlex @@ -6,7 +6,7 @@ import shlex def cli_as_dict(str): cli = shlex.split(str) - args = parse_args(cli) + args = parse_args_before_config(cli) return vars(args) @@ -48,7 +48,6 @@ def test_contains_alone(): def test_debug_alone(): assert cli_as_dict("--debug") == expected_args(debug=True) - assert cli_as_dict("-d") == expected_args(debug=True) def test_delete_alone():