From 0a7c1200d968a73b3fecf321271f158f556a5370 Mon Sep 17 00:00:00 2001 From: Bryan Bugyi Date: Sat, 20 Jan 2024 00:41:41 -0500 Subject: [PATCH] Add 'edit' command (no tests) --- README.md | 10 ++++- requirements.in | 2 + src/zorg/__main__.py | 25 ++---------- src/zorg/config.py | 79 +++++++++++++++++++++++++++++++++++++ src/zorg/runners.py | 92 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 185 insertions(+), 23 deletions(-) create mode 100644 src/zorg/config.py create mode 100644 src/zorg/runners.py diff --git a/README.md b/README.md index 20dc6d0b..adaaade7 100644 --- a/README.md +++ b/README.md @@ -61,13 +61,17 @@ print("```", stdout.decode().strip(), "```", sep="\n") ]]]]] --> ``` usage: zorg [-h] [-c CONFIG_FILE] [-L [FILE[:LEVEL][@FORMAT]]] [-v] + [-d ZETTEL_DIR] + {edit} ... -Contains the zorg package's main entry point. +The zettel note manager of the future. options: -c CONFIG_FILE, --config CONFIG_FILE Absolute or relative path to a YAML file that contains this application's configuration. + -d ZETTEL_DIR, --dir ZETTEL_DIR + The directory where all of your notes are stored. -h, --help show this help message and exit -L [FILE[:LEVEL][@FORMAT]], --log [FILE[:LEVEL][@FORMAT]] This option can be used to enable a new logging @@ -85,6 +89,10 @@ options: multiple times and has a default argument of '+'. -v, --verbose How verbose should the output be? This option can be specified multiple times (e.g. -v, -vv, -vvv, ...). + +subcommands: + {edit} + edit Open day log in editor. ``` diff --git a/requirements.in b/requirements.in index 6623759e..d73bead7 100644 --- a/requirements.in +++ b/requirements.in @@ -1,2 +1,4 @@ bolton-clack ~= 0.3.1 bolton-logrus ~= 0.1.1 +jinja2 +vimala ~= 0.1.2 diff --git a/src/zorg/__main__.py b/src/zorg/__main__.py index 9e19215b..b4c0a4c0 100644 --- a/src/zorg/__main__.py +++ b/src/zorg/__main__.py @@ -2,31 +2,12 @@ from __future__ import annotations -from typing import Sequence - import clack - -class Config(clack.Config): - """Command-line arguments.""" - - @classmethod - def from_cli_args(cls, argv: Sequence[str]) -> Config: - """Parses command-line arguments.""" - parser = clack.Parser() - - args = parser.parse_args(argv[1:]) - kwargs = clack.filter_cli_args(args) - - return cls(**kwargs) - - -def run(cfg: Config) -> int: - """This function acts as this tool's main entry point.""" - del cfg - return 0 +from .config import clack_parser +from .runners import RUNNERS -main = clack.main_factory("zorg", run) +main = clack.main_factory("zorg", parser=clack_parser, runners=RUNNERS) if __name__ == "__main__": main() diff --git a/src/zorg/config.py b/src/zorg/config.py new file mode 100644 index 00000000..eb7688bb --- /dev/null +++ b/src/zorg/config.py @@ -0,0 +1,79 @@ +"""Clack config for zorg.""" + +from __future__ import annotations + +import itertools as it +from pathlib import Path +from typing import Any, Literal, Sequence + +import clack + + +Command = Literal["edit"] + + +class Config(clack.Config): + """Shared clack configuration class.""" + + command: Command + + zettel_dir: Path = Path.home() / "org" + + +class EditConfig(Config): + """Clack config for the 'edit' command.""" + + command: Literal["edit"] + + day_log_template: str = "bujo_day_log_tmpl.zo" + habit_template: str = "habit_tmpl.zo" + done_template: str = "done_tmpl.zo" + vim_commands: list[str] + + +def clack_parser(argv: Sequence[str]) -> dict[str, Any]: + """Parser we pass to the `main_factory()` `parser` kwarg.""" + # HACK: Make 'tui' the default sub-command. + if not list(it.dropwhile(lambda x: x.startswith("-"), argv[1:])): + argv = list(argv) + ["edit"] + + parser = clack.Parser(description="The zettel note manager of the future.") + parser.add_argument( + "-d", + "--dir", + dest="zettel_dir", + type=Path, + help="The directory where all of your notes are stored.", + ) + + new_command = clack.new_command_factory(parser) + edit_parser = new_command("edit", help="Open day log in editor.") + edit_parser.add_argument( + "-D", + "--day-log-template-name", + help="Template used to generate day logs.", + ) + edit_parser.add_argument( + "-H", + "--habit-template", + help="Template used to generate habit trackers.", + ) + edit_parser.add_argument( + "-X", + "--done-template", + help="Template used to generate files for done todos.", + ) + + args = parser.parse_args(argv[1:]) + kwargs = clack.filter_cli_args(args) + + _preprocess_template_name(kwargs, "day_log_template") + _preprocess_template_name(kwargs, "habit_template") + _preprocess_template_name(kwargs, "done_template") + + return kwargs + + +def _preprocess_template_name(kwargs: dict[str, Any], key: str) -> None: + if key in kwargs: + kwargs[key] = kwargs[key] + "_tmpl.zo" diff --git a/src/zorg/runners.py b/src/zorg/runners.py new file mode 100644 index 00000000..08e1a51f --- /dev/null +++ b/src/zorg/runners.py @@ -0,0 +1,92 @@ +"""Contains this project's clack runners.""" + +from __future__ import annotations + +import datetime as dt +from pathlib import Path +from typing import Iterable, Iterator, List + +from clack.types import ClackRunner +import jinja2 +from logrus import Logger +import metaman +import vimala + +from .config import EditConfig + + +RUNNERS: List[ClackRunner] = [] +runner = metaman.register_function_factory(RUNNERS) + +logger = Logger(__name__) + + +@runner +def run_edit(cfg: EditConfig) -> int: + """Runner for the 'edit' command.""" + template_loader = jinja2.FileSystemLoader(searchpath=cfg.zettel_dir) + template_env = jinja2.Environment(loader=template_loader) + template = template_env.get_template(cfg.day_log_template) + habit_template = template_env.get_template(cfg.habit_template) + done_template = template_env.get_template(cfg.done_template) + + today = dt.datetime.now() + yesterday = today - dt.timedelta(days=1) + two_days_ago = today - dt.timedelta(days=2) + tomorrow = today + dt.timedelta(days=1) + + day_log_vars = { + "year": str(today.year), + "month": f"{today.month:02d}", + "day": f"{today.day:02d}", + "weekday": today.strftime("%a"), + "yesterday": yesterday.strftime("%Y%m%d"), + "tomorrow": tomorrow.strftime("%Y%m%d"), + } + day_log_contents = template.render(day_log_vars) + done_log_contents = done_template.render(day_log_vars) + habit_tracker_contents = habit_template.render( + year=str(yesterday.year), + month=f"{yesterday.month:02d}", + day=f"{yesterday.day:02d}", + weekday=yesterday.strftime("%a"), + day_before=two_days_ago.strftime("%Y%m%d"), + day_after=today.strftime("%Y%m%d"), + ) + + day_log_path = _get_day_path(cfg.zettel_dir, today) + if not day_log_path.exists(): + day_log_path.write_text(day_log_contents) + + habit_tracker_path = _get_day_path( + cfg.zettel_dir, yesterday, suffix="habit" + ) + if not habit_tracker_path.exists(): + habit_tracker_path.write_text(habit_tracker_contents) + + done_log_path = _get_day_path(cfg.zettel_dir, today, suffix="done") + if not done_log_path.exists(): + done_log_path.write_text(done_log_contents) + + vimala.vim( + day_log_path, + commands=_process_vim_commands(cfg.zettel_dir, cfg.vim_commands), + ) + return 0 + + +def _get_day_path( + zettel_dir: Path, date: dt.date, *, suffix: str = None +) -> Path: + suffix = f"_{suffix}" if suffix else "" + return zettel_dir / str(date.year) / date.strftime(f"%Y%m%d{suffix}.zo") + + +def _process_vim_commands( + zettel_dir: Path, vim_commands: Iterable[str] +) -> Iterator[str]: + for vim_cmd in vim_commands: + if "{zdir}" in vim_cmd: + yield vim_cmd.format(zdir=zettel_dir) + else: + yield vim_cmd