From 0f54c13a1b310d65acddefb0ce4d0fa51260d449 Mon Sep 17 00:00:00 2001 From: Brent Yi Date: Thu, 22 Sep 2022 17:44:19 -0700 Subject: [PATCH] Tab complete for paths in bash --- dcargs/_arguments.py | 21 +++++++++++++++++++-- pyproject.toml | 2 +- tests/test_dcargs.py | 19 +++++++++++++++---- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/dcargs/_arguments.py b/dcargs/_arguments.py index 1842c304..b69d93e0 100644 --- a/dcargs/_arguments.py +++ b/dcargs/_arguments.py @@ -8,6 +8,7 @@ import functools import itertools import shlex +from pathlib import Path from typing import ( Any, Dict, @@ -21,7 +22,9 @@ Union, ) -from . import _fields, _instantiators, _resolver, _strings +from . import _fields, _instantiators, _resolver +from . import _shtab as shtab +from . import _strings from .conf import _markers try: @@ -77,7 +80,21 @@ def add_argument( kwargs["choices"] = _PatchedList(kwargs["choices"]) # Note that the name must be passed in as a position argument. - parser.add_argument(name_or_flag, **kwargs) + arg = parser.add_argument(name_or_flag, **kwargs) + + # Do our best to tab complete paths. + # There will be false positives here, but if choices is unset they should be + # harmless. + if "choices" not in kwargs: + if "dir" in self.field.name: + arg.complete = shtab.DIRECTORY # type: ignore + elif ( + # Catch types like Path, List[Path], Tuple[Path, ...] etc. + "Path" in str(self.field.typ) + or "path" in self.field.name + or "file" in self.field.name + ): + arg.complete = shtab.FILE # type: ignore @cached_property def lowered(self) -> LoweredArgumentDefinition: diff --git a/pyproject.toml b/pyproject.toml index 1489a637..55c3c532 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "dcargs" -version = "0.3.8" +version = "0.3.9" description = "Strongly typed, zero-effort CLI interfaces" authors = ["brentyi "] include = ["./dcargs/**/*"] diff --git a/tests/test_dcargs.py b/tests/test_dcargs.py index ede75c57..5cdf0adc 100644 --- a/tests/test_dcargs.py +++ b/tests/test_dcargs.py @@ -99,7 +99,7 @@ class InitFalseDataclass: i: int s: str f: float - p: pathlib.Path + dir: pathlib.Path ignored: str = dataclasses.field(default="hello", init=False) assert dcargs.cli( @@ -111,15 +111,26 @@ class InitFalseDataclass: "5", "--f", "5", - "--p", + "--dir", "~", ], - ) == InitFalseDataclass(i=5, s="5", f=5.0, p=pathlib.Path("~")) + ) == InitFalseDataclass(i=5, s="5", f=5.0, dir=pathlib.Path("~")) with pytest.raises(SystemExit): dcargs.cli( InitFalseDataclass, - args=["--i", "5", "--s", "5", "--f", "5", "--p", "~", "--ignored", "blah"], + args=[ + "--i", + "5", + "--s", + "5", + "--f", + "5", + "--dir", + "~", + "--ignored", + "blah", + ], )