mirror of
https://github.com/jrnl-org/jrnl.git
synced 2025-05-10 16:48:31 +02:00
* Add ruff * Add ruff config * Add ruff rules that look useful and are already passing * Add more ruff rules after talking with Jonathan * Add line length exception for acceptably long indented line * Resolve ruff line length 88 rule in args. Changing small lines but adding a noqa ignore directive to longer lines that look best as they are. Their dedented length is still less than 88 * poe format * Resolve all remaining ruff line length errors * Replace flake* and isort with ruff calls * Add black --check as final lint step. ruff catches most but not all black formatting issues * Remove unneeded flakeheaven setting * Remove flake* and isort now that ruff is handling all their business * Update pyproject, lockfile with latest version of ruff * Document each ruff rule with comment * Add black --version call before black --check * Remove extraneous period
455 lines
14 KiB
Python
455 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 :)""" # noqa: E501
|
|
),
|
|
)
|
|
|
|
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 = (
|
|
" " # Preserves indentation
|
|
"""
|
|
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 selected 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 "
|
|
"occurrences",
|
|
)
|
|
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
|
|
""", # noqa: E501
|
|
)
|
|
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
|
|
""", # noqa: E501
|
|
)
|
|
|
|
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
|