jrnl/jrnl/args.py
Aaron Lichtman a2b217fdfc
Add ability to use template with --template (#1667)
* Add ability to pass template path with --template

Update jrnl/args.py

* Fix tests
2023-03-25 11:47:00 -07:00

446 lines
14 KiB
Python

# Copyright © 2012-2023 jrnl contributors
# License: https://www.gnu.org/licenses/gpl-3.0.html
import argparse
import re
import textwrap
from jrnl.commands import postconfig_decrypt
from jrnl.commands import postconfig_encrypt
from jrnl.commands import postconfig_import
from jrnl.commands import postconfig_list
from jrnl.commands import preconfig_diagnostic
from jrnl.commands import preconfig_version
from jrnl.output import deprecated_cmd
from jrnl.plugins import EXPORT_FORMATS
from jrnl.plugins import IMPORT_FORMATS
from jrnl.plugins import util
class WrappingFormatter(argparse.RawTextHelpFormatter):
"""Used in help screen"""
def _split_lines(self, text: str, width: int) -> list[str]:
text = text.split("\n\n")
text = map(lambda t: self._whitespace_matcher.sub(" ", t).strip(), text)
text = map(lambda t: textwrap.wrap(t, width=56), text)
text = [item for sublist in text for item in sublist]
return text
class IgnoreNoneAppendAction(argparse._AppendAction):
"""
Pass -not without a following string and avoid appending
a None value to the excluded list
"""
def __call__(self, parser, namespace, values, option_string=None):
if values is not None:
super().__call__(parser, namespace, values, option_string)
def parse_not_arg(
args: list[str], parsed_args: argparse.Namespace, parser: argparse.ArgumentParser
) -> argparse.Namespace:
"""
It's possible to use -not as a precursor to -starred and -tagged
to reverse their behaviour, however this requires some extra logic
to parse, and to ensure we still do not allow passing an empty -not
"""
parsed_args.exclude_starred = False
parsed_args.exclude_tagged = False
if "-not-starred" in "".join(args):
parsed_args.starred = False
parsed_args.exclude_starred = True
if "-not-tagged" in "".join(args):
parsed_args.tagged = False
parsed_args.exclude_tagged = True
if "-not" in args and not any(
[parsed_args.exclude_starred, parsed_args.exclude_tagged, parsed_args.excluded]
):
parser.error("argument -not: expected 1 argument")
return parsed_args
def parse_args(args: list[str] = []) -> argparse.Namespace:
"""
Argument parsing that is doable before the config is available.
Everything else goes into "text" for later parsing.
"""
parser = argparse.ArgumentParser(
formatter_class=WrappingFormatter,
add_help=False,
description="Collect your thoughts and notes without leaving the command line",
epilog=textwrap.dedent(
"""
We gratefully thank all 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")
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="Print version information",
)
standalone.add_argument(
"-v",
action="store_const",
const=preconfig_version,
dest="preconfig_cmd",
help=argparse.SUPPRESS,
)
standalone.add_argument(
"--diagnostic",
action="store_const",
const=preconfig_diagnostic,
dest="preconfig_cmd",
help=argparse.SUPPRESS,
)
standalone.add_argument(
"--list",
action="store_const",
const=postconfig_list,
dest="postconfig_cmd",
help="""
List all configured journals.
Optional parameters:
--format [json or yaml]
""",
)
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", "--list or --ls", callback=postconfig_list, **kwargs
),
dest="postconfig_cmd",
help=argparse.SUPPRESS,
)
standalone.add_argument(
"--encrypt",
help="Encrypt selected journal with a password",
action="store_const",
metavar="TYPE",
const=postconfig_encrypt,
dest="postconfig_cmd",
)
standalone.add_argument(
"--decrypt",
help="Decrypt selected journal and store it in plain text",
action="store_const",
metavar="TYPE",
const=postconfig_decrypt,
dest="postconfig_cmd",
)
standalone.add_argument(
"--import",
action="store_const",
metavar="TYPE",
const=postconfig_import,
dest="postconfig_cmd",
help=f"""
Import entries from another journal.
Optional parameters:
--file FILENAME (default: uses stdin)
--format [{util.oxford_list(IMPORT_FORMATS)}] (default: jrnl)
""",
)
standalone.add_argument(
"--file",
metavar="FILENAME",
dest="filename",
help=argparse.SUPPRESS,
default=None,
)
standalone.add_argument("-i", dest="filename", help=argparse.SUPPRESS)
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.
Also, you can mark extra special entries ("star" them) with an asterisk:
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 quotes around
your entire entry:
jrnl "saturday at 2am: *Then I was like 'That log had a child!'" """
composing = parser.add_argument_group(
"Writing", textwrap.dedent(compose_msg).strip()
)
composing.add_argument("text", metavar="", nargs="*")
composing.add_argument(
"--template",
dest="template",
help="Path to template file. Can be a local path, absolute path, or a path relative to $XDG_DATA_HOME/jrnl/templates/",
)
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(
"-today-in-history",
dest="today_in_history",
action="store_true",
help="Show entries of today over the years",
)
reading.add_argument(
"-month",
dest="month",
metavar="DATE",
help="Show entries on this month of any year",
)
reading.add_argument(
"-day",
dest="day",
metavar="DATE",
help="Show entries on this day of any month",
)
reading.add_argument(
"-year",
dest="year",
metavar="DATE",
help="Show entries of a specific year",
)
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(
"-tagged",
dest="tagged",
action="store_true",
help="Show only entries that have at least one tag",
)
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/FLAG",
action=IgnoreNoneAppendAction,
help=(
"If passed a string, will exclude entries with that tag. "
"Can be also used before -starred or -tagged flags, to exclude "
"starred or tagged entries respectively."
),
)
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(
"Searching Options", textwrap.dedent(search_options_msg)
)
exporting.add_argument(
"--edit",
dest="edit",
help="Opens the selected entries in your configured editor",
action="store_true",
)
exporting.add_argument(
"--delete",
dest="delete",
action="store_true",
help="Interactively deletes selected entries",
)
exporting.add_argument(
"--change-time",
dest="change_time",
nargs="?",
metavar="DATE",
const="now",
help="Change timestamp for seleted entries (default: now)",
)
exporting.add_argument(
"--format",
metavar="TYPE",
dest="export",
choices=EXPORT_FORMATS,
help=f"""
Display selected entries in an alternate format.
TYPE can be: {util.oxford_list(EXPORT_FORMATS)}.
Optional parameters:
--file FILENAME Write output to file instead of stdout
""",
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",
dest="filename",
help=argparse.SUPPRESS,
)
config_overrides = parser.add_argument_group(
"Config file override",
textwrap.dedent("Apply a one-off override of the config file option"),
)
config_overrides.add_argument(
"--config-override",
dest="config_override",
action="append",
type=str,
nargs=2,
default=[],
metavar="CONFIG_KV_PAIR",
help="""
Override configured key-value pair with CONFIG_KV_PAIR for this command invocation only.
Examples: \n
\t - Use a different editor for this jrnl entry, call: \n
\t jrnl --config-override editor "nano" \n
\t - Override color selections\n
\t jrnl --config-override colors.body blue --config-override colors.title green
""",
)
config_overrides.add_argument(
"--co",
dest="config_override",
action="append",
type=str,
nargs=2,
default=[],
help=argparse.SUPPRESS,
)
alternate_config = parser.add_argument_group(
"Specifies alternate config to be used",
textwrap.dedent("Applies alternate config for current session"),
)
alternate_config.add_argument(
"--config-file",
dest="config_file_path",
type=str,
default="",
help="""
Overrides default (created when first installed) config file for this command only.
Examples: \n
\t - Use a work config file for this jrnl entry, call: \n
\t jrnl --config-file /home/user1/work_config.yaml
\t - Use a personal config file stored on a thumb drive: \n
\t jrnl --config-file /media/user1/my-thumb-drive/personal_config.yaml
""",
)
alternate_config.add_argument(
"--cf", dest="config_file_path", type=str, default="", help=argparse.SUPPRESS
)
# Handle '-123' as a shortcut for '-n 123'
num = re.compile(r"^-(\d+)$")
args = [num.sub(r"-n \1", arg) for arg in args]
parsed_args = parser.parse_intermixed_args(args)
parsed_args = parse_not_arg(args, parsed_args, parser)
return parsed_args