From cebfd8e34e896de5a2db4b2d7ced3b1e30bdbba2 Mon Sep 17 00:00:00 2001 From: "John T. Wodder II" Date: Thu, 2 May 2024 10:17:00 -0400 Subject: [PATCH] [WIP] Command completion for task arguments --- src/habits/__main__.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/habits/__main__.py b/src/habits/__main__.py index de2f675..545a919 100644 --- a/src/habits/__main__.py +++ b/src/habits/__main__.py @@ -2,6 +2,7 @@ from configparser import ConfigParser from dataclasses import dataclass from datetime import datetime, time, timedelta +from difflib import get_close_matches import json import os from pathlib import Path @@ -11,6 +12,7 @@ from cachecontrol import CacheControl from cachecontrol.caches.file_cache import FileCache import click +from click.shell_completion import CompletionItem from dateutil.parser import isoparse from dateutil.tz import tzstr from platformdirs import PlatformDirs @@ -188,6 +190,26 @@ def show_json(self) -> None: print_json(self.response_json) +class TaskParam(click.ParamType): + def convert( + self, value: str, _param: click.Parameter | None, ctx: click.Context | None + ) -> str: + if ctx is None or value in ctx.obj.aliases: + return value + else: + candidates = get_close_matches(value, list(ctx.obj.aliases.keys())) + if candidates: + dym = " (Did you mean:" + "".join(f" {c}?" for c in candidates) + ")" + else: + dym = "" + self.fail(f"{value!r}: unknown task{dym}") + + def shell_complete( + self, ctx: click.Context, _param: click.Parameter, incomplete: str + ) -> list[CompletionItem]: + return [CompletionItem(c) for c in ctx.obj.aliases if c.startswith(incomplete)] + + @click.group(context_settings={"help_option_names": ["-h", "--help"]}) @click.option( "-c", @@ -211,7 +233,7 @@ def main(ctx: click.Context, config: Path) -> None: @main.command() @click.option("-J", "--show-json", is_flag=True) @click.option("--no-cron", is_flag=True) -@click.argument("task", nargs=-1) +@click.argument("task", type=TaskParam(), nargs=-1) @click.pass_obj def up(hb: Habitica, task: str, no_cron: bool, show_json: bool) -> None: """Check-off or +1 a task""" @@ -234,7 +256,7 @@ def up(hb: Habitica, task: str, no_cron: bool, show_json: bool) -> None: @main.command() @click.option("-J", "--show-json", is_flag=True) @click.option("--no-cron", is_flag=True) -@click.argument("task", nargs=-1) +@click.argument("task", type=TaskParam(), nargs=-1) @click.pass_obj def down(hb: Habitica, task: str, no_cron: bool, show_json: bool) -> None: """Uncheck or -1 a task"""