From b01a4bebe887f05391db12e8afd7c68c3a722413 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Oct 2022 12:42:22 -0500 Subject: [PATCH 01/10] build: Add argcomplete, optional dependency --- poetry.lock | 20 +++++++++++++++++++- pyproject.toml | 3 +++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 90668025345..225e4204919 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,6 +14,20 @@ category = "dev" optional = false python-versions = "*" +[[package]] +name = "argcomplete" +version = "2.0.0" +description = "Bash tab completion for argparse" +category = "dev" +optional = true +python-versions = ">=3.6" + +[package.dependencies] +importlib-metadata = {version = ">=0.23,<5", markers = "python_version == \"3.7\""} + +[package.extras] +test = ["coverage", "flake8", "pexpect", "wheel"] + [[package]] name = "attrs" version = "22.1.0" @@ -1020,7 +1034,7 @@ test = [] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "44f3d12d61307a3b9811ff20816c0860900e29399af5d0dd5823d28dd65b5ded" +content-hash = "8dee2cb8b566502e2cfda01df8dc096fb26ce477c7043bfa3190c9a0f039ac7a" [metadata.files] aafigure = [ @@ -1031,6 +1045,10 @@ alabaster = [ {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, ] +argcomplete = [ + {file = "argcomplete-2.0.0-py2.py3-none-any.whl", hash = "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e"}, + {file = "argcomplete-2.0.0.tar.gz", hash = "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20"}, +] attrs = [ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, diff --git a/pyproject.toml b/pyproject.toml index 02ce57a30af..d4f6e885b54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,6 +127,9 @@ lint = [ "types-PyYAML", ] +[tool.poetry.group.completion.dependencies] +argcomplete = {version = "^2.0.0", optional = true} + [tool.coverage.run] branch = true source = [ From a7677874415dfcd03ccba4d92fb2870e626f91e5 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Oct 2022 13:13:36 -0500 Subject: [PATCH 02/10] :wrench: argcomplete: Add Completions --- src/tmuxp/cli/completions.py | 96 ++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/tmuxp/cli/completions.py diff --git a/src/tmuxp/cli/completions.py b/src/tmuxp/cli/completions.py new file mode 100644 index 00000000000..ad930e11ff3 --- /dev/null +++ b/src/tmuxp/cli/completions.py @@ -0,0 +1,96 @@ +import os + +from libtmux.server import Server +from tmuxp import config +from tmuxp.cli.utils import get_config_dir + +config_dir = get_config_dir() + +try: + import argcomplete +except ImportError: + + class ArgComplete: + class Completers: + class Completer: + def __call__(self, *args: object, **kwargs: object) -> object: + ... + + FilesCompleter = Completer + + completers = Completers() + + argcomplete = ArgComplete() # type:ignore + + +class ConfigFileCompleter(argcomplete.completers.FilesCompleter): + + """argcomplete completer for tmuxp files.""" + + def __call__(self, prefix, **kwargs): + + completion = argcomplete.completers.FilesCompleter.__call__( + self, prefix, **kwargs + ) + + completion += [os.path.join(config_dir, c) for c in config.in_dir(config_dir)] + + return completion + + +class TmuxinatorCompleter(argcomplete.completers.FilesCompleter): + + """argcomplete completer for Tmuxinator files.""" + + def __call__(self, prefix, **kwargs): + from tmuxp.cli.import_config import get_tmuxinator_dir + + tmuxinator_config_dir = get_tmuxinator_dir() + completion = argcomplete.completers.FilesCompleter.__call__( + self, prefix, **kwargs + ) + tmuxinator_configs = config.in_dir(tmuxinator_config_dir, extensions="yml") + completion += [ + os.path.join(tmuxinator_config_dir, f) for f in tmuxinator_configs + ] + + return completion + + +class TeamocilCompleter(argcomplete.completers.FilesCompleter): + + """argcomplete completer for Teamocil files.""" + + def __call__(self, prefix, **kwargs): + from tmuxp.cli.import_config import get_teamocil_dir + + teamocil_config_dir = get_teamocil_dir() + + completion = argcomplete.completers.FilesCompleter.__call__( + self, prefix, **kwargs + ) + teamocil_configs = config.in_dir(teamocil_config_dir, extensions="yml") + completion += [os.path.join(teamocil_config_dir, f) for f in teamocil_configs] + + return completion + + +def SessionCompleter(prefix, parsed_args, **kwargs): + """Return list of session names for argcomplete completer.""" + + t = Server(socket_name=parsed_args.socket_name, socket_path=parsed_args.socket_path) + + sessions_available = [ + s.get("session_name") + for s in t._sessions + if s.get("session_name").startswith(" ".join(prefix)) + ] + + if parsed_args.session_name and sessions_available: + return [] + + return [ + s.get("session_name") + for s in t._sessions + if s.get("session_name").startswith(prefix) + ] From 9054c228fd0cd003a8b585b5f0e2bce26155116f Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Oct 2022 13:16:23 -0500 Subject: [PATCH 03/10] ci(mypy): Skip argcomplete for now --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d4f6e885b54..de1557602a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,7 +128,7 @@ lint = [ ] [tool.poetry.group.completion.dependencies] -argcomplete = {version = "^2.0.0", optional = true} +argcomplete = { version = "^2.0.0", optional = true } [tool.coverage.run] branch = true @@ -161,6 +161,7 @@ files = [ [[tool.mypy.overrides]] module = [ "shtab", + "argcomplete.*", "aafigure", "IPython.*", "ptpython.*", From db8110b666d79d4d7328b4fb377ca5a710bb743d Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Oct 2022 14:43:52 -0500 Subject: [PATCH 04/10] !squash fix build --- poetry.lock | 11 ++++++----- pyproject.toml | 5 ++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index 225e4204919..1e2b943531d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -18,7 +18,7 @@ python-versions = "*" name = "argcomplete" version = "2.0.0" description = "Bash tab completion for argparse" -category = "dev" +category = "main" optional = true python-versions = ">=3.6" @@ -264,7 +264,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" name = "importlib-metadata" version = "4.13.0" description = "Read metadata from Python packages" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" @@ -984,7 +984,7 @@ python-versions = "*" name = "typing-extensions" version = "4.4.0" description = "Backported and Experimental Type Hints for Python 3.7+" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" @@ -1016,7 +1016,7 @@ watchmedo = ["PyYAML (>=3.10)"] name = "zipp" version = "3.9.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "dev" +category = "main" optional = false python-versions = ">=3.7" @@ -1025,6 +1025,7 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [extras] +completion = ["argcomplete"] coverage = [] docs = [] format = [] @@ -1034,7 +1035,7 @@ test = [] [metadata] lock-version = "1.1" python-versions = "^3.7" -content-hash = "8dee2cb8b566502e2cfda01df8dc096fb26ce477c7043bfa3190c9a0f039ac7a" +content-hash = "c8d56d52067a52277751d1746b9783bb39a5e3735be989acf80d7eee8e875813" [metadata.files] aafigure = [ diff --git a/pyproject.toml b/pyproject.toml index de1557602a0..6699056c412 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ python = "^3.7" libtmux = "~0.15.8" colorama = ">=0.3.9" PyYAML = "^6.0" +argcomplete = { version = "^2.0.0", optional = true } [tool.poetry.dev-dependencies] ### Docs ### @@ -98,6 +99,7 @@ types-PyYAML = "*" importlib-metadata = "<5" # https://github.com/PyCQA/flake8/issues/1701 [tool.poetry.extras] +completion = ["argcomplete"] docs = [ "docutils", "sphinx", @@ -127,9 +129,6 @@ lint = [ "types-PyYAML", ] -[tool.poetry.group.completion.dependencies] -argcomplete = { version = "^2.0.0", optional = true } - [tool.coverage.run] branch = true source = [ From a63f93425c6aae41501d5778e70c344f89c2b94e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Oct 2022 14:43:58 -0500 Subject: [PATCH 05/10] !squash more --- src/tmuxp/cli/__init__.py | 14 ++++++++++++++ src/tmuxp/cli/completions.py | 32 ++++++++++++++++++++++++++------ src/tmuxp/cli/load.py | 13 +++++++++---- 3 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/tmuxp/cli/__init__.py b/src/tmuxp/cli/__init__.py index e676bed51d9..c880e38200f 100644 --- a/src/tmuxp/cli/__init__.py +++ b/src/tmuxp/cli/__init__.py @@ -93,6 +93,13 @@ def create_parser() -> argparse.ArgumentParser: ) create_freeze_subparser(freeze_parser) + try: + import argcomplete + + argcomplete.autocomplete(parser) + except ImportError: + pass + return parser @@ -123,6 +130,13 @@ def cli(_args: t.Optional[t.List[str]] = None) -> None: sys.exit() parser = create_parser() + + try: + import argcomplete + + argcomplete.autocomplete(parser) + except ImportError: + pass args = parser.parse_args(_args, namespace=ns) setup_logger(logger=logger, level=args.log_level.upper()) diff --git a/src/tmuxp/cli/completions.py b/src/tmuxp/cli/completions.py index ad930e11ff3..c583dbaa7ad 100644 --- a/src/tmuxp/cli/completions.py +++ b/src/tmuxp/cli/completions.py @@ -1,4 +1,5 @@ import os +import typing as t from libtmux.server import Server from tmuxp import config @@ -8,6 +9,7 @@ try: import argcomplete + import argcomplete.completers except ImportError: class ArgComplete: @@ -24,22 +26,40 @@ def __call__(self, *args: object, **kwargs: object) -> object: class ConfigFileCompleter(argcomplete.completers.FilesCompleter): - """argcomplete completer for tmuxp files.""" - def __call__(self, prefix, **kwargs): - - completion = argcomplete.completers.FilesCompleter.__call__( + def __init__( + self, + allowednames: t.Sequence[str] = ( + "yml", + "yaml", + "json", + ), + directories: bool = False, + **kwargs: object + ): + if isinstance(allowednames, (str, bytes)): + allowednames = [allowednames] + + self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames] + self.directories = directories + # super().__init__( + # self, allowednames=allowednames, directories=directories, **kwargs + # ) + + def __call__(self, prefix: str, **kwargs): + completion: t.List[str] = argcomplete.completers.FilesCompleter.__call__( self, prefix, **kwargs ) - completion += [os.path.join(config_dir, c) for c in config.in_dir(config_dir)] + completion.extend( + [os.path.join(config_dir, c) for c in config.in_dir(config_dir)] + ) return completion class TmuxinatorCompleter(argcomplete.completers.FilesCompleter): - """argcomplete completer for Tmuxinator files.""" def __call__(self, prefix, **kwargs): diff --git a/src/tmuxp/cli/load.py b/src/tmuxp/cli/load.py index ec016c9fa9a..dc819c33ac1 100644 --- a/src/tmuxp/cli/load.py +++ b/src/tmuxp/cli/load.py @@ -566,11 +566,16 @@ def create_load_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentP ) try: - import shtab + import argcomplete + import argcomplete.completers - config_file.complete = shtab.FILE # type: ignore - tmux_config_file.complete = shtab.FILE # type: ignore - log_file.complete = shtab.FILE # type: ignore + from tmuxp.cli.completions import ConfigFileCompleter + + config_file.completer = ConfigFileCompleter() # type: ignore + tmux_config_file.completer = ( # type: ignore + argcomplete.completers.FilesCompleter + ) + log_file.completer = argcomplete.completers.FilesCompleter # type: ignore except ImportError: pass From 8bec0f6bb17455c3da5b49374badf65e030be0d9 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Oct 2022 14:50:01 -0500 Subject: [PATCH 06/10] tmuxp load that actually works --- src/tmuxp/cli/completions.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/tmuxp/cli/completions.py b/src/tmuxp/cli/completions.py index c583dbaa7ad..177a13aca71 100644 --- a/src/tmuxp/cli/completions.py +++ b/src/tmuxp/cli/completions.py @@ -1,4 +1,5 @@ import os +import pathlib import typing as t from libtmux.server import Server @@ -38,22 +39,22 @@ def __init__( directories: bool = False, **kwargs: object ): - if isinstance(allowednames, (str, bytes)): - allowednames = [allowednames] - - self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames] - self.directories = directories - # super().__init__( - # self, allowednames=allowednames, directories=directories, **kwargs - # ) + # assert not isinstance(allowednames, (str, bytes)) + # + # self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames] + # self.directories = directories + # # Does not work, unknown why + super().__init__(allowednames=allowednames, directories=directories, **kwargs) def __call__(self, prefix: str, **kwargs): - completion: t.List[str] = argcomplete.completers.FilesCompleter.__call__( - self, prefix, **kwargs - ) + completion: t.List[str] = super().__call__(prefix, **kwargs) completion.extend( - [os.path.join(config_dir, c) for c in config.in_dir(config_dir)] + [ + # os.path.join(config_dir, c).replace(str(pathlib.Path.home()), "~") + pathlib.Path(c).stem + for c in config.in_dir(config_dir) + ] ) return completion From 6cccde4bfc86b03000f2038d2974a7d28524d186 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Oct 2022 14:55:30 -0500 Subject: [PATCH 07/10] completion --- src/tmuxp/cli/completions.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/tmuxp/cli/completions.py b/src/tmuxp/cli/completions.py index 177a13aca71..1b050094ae2 100644 --- a/src/tmuxp/cli/completions.py +++ b/src/tmuxp/cli/completions.py @@ -31,31 +31,15 @@ class ConfigFileCompleter(argcomplete.completers.FilesCompleter): def __init__( self, - allowednames: t.Sequence[str] = ( - "yml", - "yaml", - "json", - ), + allowednames: t.Sequence[str] = ("yml", "yaml", "json"), directories: bool = False, **kwargs: object ): - # assert not isinstance(allowednames, (str, bytes)) - # - # self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames] - # self.directories = directories - # # Does not work, unknown why super().__init__(allowednames=allowednames, directories=directories, **kwargs) def __call__(self, prefix: str, **kwargs): completion: t.List[str] = super().__call__(prefix, **kwargs) - - completion.extend( - [ - # os.path.join(config_dir, c).replace(str(pathlib.Path.home()), "~") - pathlib.Path(c).stem - for c in config.in_dir(config_dir) - ] - ) + completion.extend([pathlib.Path(c).stem for c in config.in_dir(config_dir)]) return completion From b5aa21cd3d7ea71dfd72c3f92fe0534484c1e3cf Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Oct 2022 16:51:41 -0500 Subject: [PATCH 08/10] !squash file completer --- src/tmuxp/cli/load.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tmuxp/cli/load.py b/src/tmuxp/cli/load.py index dc819c33ac1..4cee1434597 100644 --- a/src/tmuxp/cli/load.py +++ b/src/tmuxp/cli/load.py @@ -573,9 +573,9 @@ def create_load_subparser(parser: argparse.ArgumentParser) -> argparse.ArgumentP config_file.completer = ConfigFileCompleter() # type: ignore tmux_config_file.completer = ( # type: ignore - argcomplete.completers.FilesCompleter + argcomplete.completers.FilesCompleter() ) - log_file.completer = argcomplete.completers.FilesCompleter # type: ignore + log_file.completer = argcomplete.completers.FilesCompleter() # type: ignore except ImportError: pass From d88026db144b2d86d50fc4c8bae8f3aa805842d6 Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Oct 2022 16:55:03 -0500 Subject: [PATCH 09/10] !squash --- src/tmuxp/cli/import_config.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/tmuxp/cli/import_config.py b/src/tmuxp/cli/import_config.py index 14a40076ce7..4d26ef94d9a 100644 --- a/src/tmuxp/cli/import_config.py +++ b/src/tmuxp/cli/import_config.py @@ -105,10 +105,15 @@ def create_import_subparser( ) try: - import shtab + import argcomplete + import argcomplete.completers - teamocil_config_file.complete = shtab.FILE # type: ignore - tmuxinator_config_file.complete = shtab.FILE # type: ignore + teamocil_config_file.completer = ( # type: ignore + argcomplete.completers.FilesCompleter() + ) + tmuxinator_config_file.completer = ( # type:ignore + argcomplete.completers.FilesCompleter() + ) except ImportError: pass From 4ff3c6c8584403a6ecb577a1e5939230bfaa3d3e Mon Sep 17 00:00:00 2001 From: Tony Narlock Date: Sat, 15 Oct 2022 16:58:01 -0500 Subject: [PATCH 10/10] !squash more --- src/tmuxp/cli/convert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tmuxp/cli/convert.py b/src/tmuxp/cli/convert.py index be9548004f5..cd82739bc8d 100644 --- a/src/tmuxp/cli/convert.py +++ b/src/tmuxp/cli/convert.py @@ -18,9 +18,9 @@ def create_convert_subparser( help="checks tmuxp and current directory for config files.", ) try: - import shtab + from tmuxp.cli.completions import ConfigFileCompleter - config_file.complete = shtab.FILE # type: ignore + config_file.completer = ConfigFileCompleter() # type:ignore except ImportError: pass