diff --git a/CHANGES b/CHANGES index 4c9f1cee741..fb52cfd62e5 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,16 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force +## tmuxp 1.17.1 (2022-10-15) + +### Minor completion improvements + +- Improved shtab completions for files with `tmuxp load [tab]` (#834) + +### Internal + +- Remove unused completion code leftover from click (#834) + ## tmuxp 1.17.0 (2022-10-09) ### Breaking changes diff --git a/docs/api.md b/docs/api.md index 9341259b79a..b416dcb22cd 100644 --- a/docs/api.md +++ b/docs/api.md @@ -49,10 +49,6 @@ If you need an internal API stabilized please [file an issue](https://github.com .. automethod:: tmuxp.cli.utils.get_config_dir ``` -```{eval-rst} -.. automethod:: tmuxp.cli.utils._validate_choices -``` - ```{eval-rst} .. automethod:: tmuxp.cli.import_config.get_teamocil_dir ``` diff --git a/pyproject.toml b/pyproject.toml index 10e1b3803e2..a56f90bd544 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,6 +157,7 @@ files = [ [[tool.mypy.overrides]] module = [ + "shtab", "aafigure", "IPython.*", "ptpython.*", diff --git a/src/tmuxp/cli/convert.py b/src/tmuxp/cli/convert.py index 6ec65d85d17..be9548004f5 100644 --- a/src/tmuxp/cli/convert.py +++ b/src/tmuxp/cli/convert.py @@ -11,12 +11,19 @@ def create_convert_subparser( parser: argparse.ArgumentParser, ) -> argparse.ArgumentParser: - parser.add_argument( + config_file = parser.add_argument( dest="config_file", type=str, metavar="config-file", help="checks tmuxp and current directory for config files.", ) + try: + import shtab + + config_file.complete = shtab.FILE # type: ignore + except ImportError: + pass + parser.add_argument( "--yes", "-y", diff --git a/src/tmuxp/cli/import_config.py b/src/tmuxp/cli/import_config.py index 9d1ad2fef10..14a40076ce7 100644 --- a/src/tmuxp/cli/import_config.py +++ b/src/tmuxp/cli/import_config.py @@ -7,14 +7,7 @@ from tmuxp.config_reader import ConfigReader from .. import config -from .utils import ( - get_abs_path, - prompt, - prompt_choices, - prompt_yes_no, - scan_config, - tmuxp_echo, -) +from .utils import prompt, prompt_choices, prompt_yes_no, scan_config, tmuxp_echo def get_tmuxinator_dir() -> str: @@ -55,10 +48,10 @@ def get_teamocil_dir() -> str: def _resolve_path_no_overwrite(config: str) -> str: - path = get_abs_path(config) - if os.path.exists(path): + path = pathlib.Path(config).resolve() + if path.exists(): raise ValueError("%s exists. Pick a new filename." % path) - return path + return str(path) def command_import( @@ -81,7 +74,7 @@ def create_import_subparser( ) import_teamocilgroup = import_teamocil.add_mutually_exclusive_group(required=True) - import_teamocilgroup.add_argument( + teamocil_config_file = import_teamocilgroup.add_argument( dest="config_file", type=str, nargs="?", @@ -99,7 +92,7 @@ def create_import_subparser( import_tmuxinatorgroup = import_tmuxinator.add_mutually_exclusive_group( required=True ) - import_tmuxinatorgroup.add_argument( + tmuxinator_config_file = import_tmuxinatorgroup.add_argument( dest="config_file", type=str, nargs="?", @@ -111,6 +104,14 @@ def create_import_subparser( callback=command_import_tmuxinator, import_subparser_name="tmuxinator" ) + try: + import shtab + + teamocil_config_file.complete = shtab.FILE # type: ignore + tmuxinator_config_file.complete = shtab.FILE # type: ignore + except ImportError: + pass + return parser @@ -175,24 +176,6 @@ def command_import_tmuxinator( import_config(config_file, config.import_tmuxinator) -def create_convert_subparser( - parser: argparse.ArgumentParser, -) -> argparse.ArgumentParser: - parser.add_argument( - dest="config_file", - type=str, - help="checks current ~/.teamocil and current directory for yaml files", - ) - parser.add_argument( - "--yes", - "-y", - dest="answer_yes", - action="store_true", - help="always answer yes", - ) - return parser - - def command_import_teamocil( config_file: str, parser: t.Optional[argparse.ArgumentParser] = None, diff --git a/src/tmuxp/cli/load.py b/src/tmuxp/cli/load.py index 3db6c530033..ec016c9fa9a 100644 --- a/src/tmuxp/cli/load.py +++ b/src/tmuxp/cli/load.py @@ -430,7 +430,6 @@ def load_workspace( ) options = ["y", "n", "a"] choice = prompt_choices(msg, choices=options) - # value_proc=_validate_choices(options)) if choice == "y": _load_attached(builder, detached) @@ -450,7 +449,6 @@ def load_workspace( choice = prompt_choices( "Error loading workspace. (k)ill, (a)ttach, (d)etach?", choices=["k", "a", "d"], - # value_proc=_validate_choices(["k", "a", "d"]), default="k", ) @@ -488,7 +486,7 @@ def config_file_completion(ctx, params, incomplete): def create_load_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: - parser.add_argument( + config_file = parser.add_argument( "config_file", metavar="config-file", help="filepath to session or filename of session if in tmuxp config directory", @@ -508,12 +506,13 @@ def create_load_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentP help="passthru to tmux(1) -S", ) - parser.add_argument( + tmux_config_file = parser.add_argument( "-f", dest="tmux_config_file", metavar="tmux_config_file", help="passthru to tmux(1) -f", ) + parser.add_argument( "-s", dest="new_session_name", @@ -557,14 +556,24 @@ def create_load_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentP const=88, help="like -2, but indicates that the terminal supports 88 colours.", ) - parser.set_defaults(colors=None) - parser.add_argument( + + log_file = parser.add_argument( "--log-file", metavar="file_path", action="store", help="file to log errors/output to", ) + + try: + import shtab + + config_file.complete = shtab.FILE # type: ignore + tmux_config_file.complete = shtab.FILE # type: ignore + log_file.complete = shtab.FILE # type: ignore + except ImportError: + pass + return parser diff --git a/src/tmuxp/cli/utils.py b/src/tmuxp/cli/utils.py index 5b47f376c49..dfb6936eae2 100644 --- a/src/tmuxp/cli/utils.py +++ b/src/tmuxp/cli/utils.py @@ -6,7 +6,7 @@ from colorama import Fore -from .. import config, log +from .. import log from .constants import VALID_CONFIG_DIR_FILE_EXTENSIONS logger = logging.getLogger(__name__) @@ -63,82 +63,6 @@ def get_config_dir() -> str: return path -def _validate_choices(options: t.List[str]) -> t.Callable: - """ - Callback wrapper for validating click.prompt input. - - Parameters - ---------- - options : list - List of allowed choices - - Returns - ------- - :func:`callable` - callback function for value_proc in :func:`click.prompt`. - - Raises - ------ - :class:`click.BadParameter` - """ - - def func(value): - if value not in options: - raise ValueError("Possible choices are: {}.".format(", ".join(options))) - return value - - return func - - -class ConfigPath: - def __init__( - self, config_dir: t.Optional[t.Union[t.Callable, str]] = None, *args, **kwargs - ) -> None: - super().__init__(*args, **kwargs) - self.config_dir = config_dir - - def convert( - self, value: str, param: t.Any, ctx: t.Any - ) -> t.Optional[t.Union[str, pathlib.Path]]: - config_dir = self.config_dir() if callable(self.config_dir) else self.config_dir - - return scan_config(value, config_dir=config_dir) - - -def scan_config_argument(ctx, param, value, config_dir=None): - """Validate / translate config name/path values for click config arg. - - Wrapper on top of :func:`cli.scan_config`.""" - if callable(config_dir): - config_dir = config_dir() - - if not config: - tmuxp_echo("Enter at least one CONFIG") - tmuxp_echo(ctx.get_help()) - ctx.exit() - - if isinstance(value, str): - value = scan_config(value, config_dir=config_dir) - - elif isinstance(value, tuple): - value = tuple(scan_config(v, config_dir=config_dir) for v in value) - - return value - - -def get_abs_path(config: str) -> str: - path = os.path - join, isabs = path.join, path.isabs - dirname, normpath = path.dirname, path.normpath - cwd = os.getcwd() - - config = os.path.expanduser(config) - if not isabs(config) or len(dirname(config)) > 1: - config = normpath(join(cwd, config)) - - return config - - def scan_config( config: t.Union[pathlib.Path, str], config_dir: t.Optional[t.Union[pathlib.Path, str]] = None, diff --git a/tests/test_cli.py b/tests/test_cli.py index d3660a586b3..024aa8250fd 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -23,14 +23,7 @@ load_plugins, load_workspace, ) -from tmuxp.cli.utils import ( - _validate_choices, - get_abs_path, - get_config_dir, - is_pure_name, - scan_config, - tmuxp_echo, -) +from tmuxp.cli.utils import get_config_dir, is_pure_name, scan_config, tmuxp_echo from tmuxp.config_reader import ConfigReader from tmuxp.workspacebuilder import WorkspaceBuilder @@ -1127,13 +1120,15 @@ def test_freeze_overwrite( assert yaml_config_path.exists() -def test_get_abs_path(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> None: +def test_resolve_behavior( + tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch +) -> None: expect = str(tmp_path) monkeypatch.chdir(tmp_path) - get_abs_path("../") == os.path.dirname(expect) - get_abs_path(".") == expect - get_abs_path("./") == expect - get_abs_path(expect) == expect + pathlib.Path("../").resolve() == os.path.dirname(expect) + pathlib.Path(".").resolve() == expect + pathlib.Path("./").resolve() == expect + pathlib.Path(expect).resolve() == expect def test_get_tmuxinator_dir(monkeypatch: pytest.MonkeyPatch) -> None: @@ -1152,16 +1147,6 @@ def test_get_teamocil_dir(monkeypatch: pytest.MonkeyPatch) -> None: assert get_teamocil_dir() == os.path.expanduser("~/.teamocil/") -def test_validate_choices() -> None: - validate = _validate_choices(["choice1", "choice2"]) - - assert validate("choice1") - assert validate("choice2") - - with pytest.raises(ValueError): - assert validate("choice3") - - def test_pass_config_dir_ClickPath( tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch,