diff --git a/CHANGES b/CHANGES index bbee3c57d37..4cef97c8a8e 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,14 @@ $ pipx install --suffix=@next 'tmuxp' --pip-args '\--pre' --force +### Development + +- Code quality improved via [ruff] rules (#879) + + This includes fixes made by hand alongside ruff's automated fixes. The more + stringent rules include import sorting, and still runs almost instantly + against the whole codebase. + ## tmuxp 1.29.1 (2023-09-02) ### Development diff --git a/conftest.py b/conftest.py index 5ee456980de..024ccbf9d96 100644 --- a/conftest.py +++ b/conftest.py @@ -10,10 +10,9 @@ import typing as t import pytest - from _pytest.doctest import DoctestItem - from libtmux.test import namer + from tests.fixtures import utils as test_utils from tmuxp.workspace.finders import get_workspace_dir @@ -24,7 +23,7 @@ USING_ZSH = "zsh" in os.getenv("SHELL", "") -@pytest.mark.skipif(USING_ZSH, reason="Using ZSH") +@pytest.mark.skipif(not USING_ZSH, reason="Using ZSH") @pytest.fixture(autouse=USING_ZSH, scope="session") def zshrc(user_path: pathlib.Path) -> pathlib.Path: """This quiets ZSH default message. @@ -70,7 +69,7 @@ def monkeypatch_plugin_test_packages(monkeypatch: pytest.MonkeyPatch) -> None: "tests/fixtures/pluginsystem/plugins/tmuxp_test_plugin_fail/", ] for path in paths: - monkeypatch.syspath_prepend(os.path.abspath(os.path.relpath(path))) + monkeypatch.syspath_prepend(str(pathlib.Path(path).resolve())) @pytest.fixture(scope="function") diff --git a/docs/_ext/aafig.py b/docs/_ext/aafig.py index 2c5775043c0..3770613d68e 100644 --- a/docs/_ext/aafig.py +++ b/docs/_ext/aafig.py @@ -12,6 +12,7 @@ """ import logging import posixpath +import typing as t from hashlib import sha1 as sha from os import path @@ -28,11 +29,11 @@ logger = logging.getLogger(__name__) -DEFAULT_FORMATS = dict(html="svg", latex="pdf", text=None) +DEFAULT_FORMATS = {"html": "svg", "latex": "pdf", "text": None} def merge_dict(dst, src): - for (k, v) in src.items(): + for k, v in src.items(): if k not in dst: dst[k] = v return dst @@ -58,22 +59,22 @@ class AafigDirective(images.Image): has_content = True required_arguments = 0 - own_option_spec = dict( - line_width=float, - background=str, - foreground=str, - fill=str, - aspect=nonnegative_int, - textual=flag, - proportional=flag, - ) + own_option_spec: t.ClassVar = { + "line_width": float, + "background": str, + "foreground": str, + "fill": str, + "aspect": nonnegative_int, + "textual": flag, + "proportional": flag, + } option_spec = images.Image.option_spec.copy() option_spec.update(own_option_spec) def run(self): - aafig_options = dict() - own_options_keys = [self.own_option_spec.keys()] + ["scale"] - for (k, v) in self.options.items(): + aafig_options = {} + own_options_keys = [self.own_option_spec.keys(), "scale"] + for k, v in self.options.items(): if k in own_options_keys: # convert flags to booleans if v is None: @@ -88,7 +89,7 @@ def run(self): if isinstance(image_node, nodes.system_message): return [image_node] text = "\n".join(self.content) - image_node.aafig = dict(options=aafig_options, text=text) + image_node.aafig = {"options": aafig_options, "text": text} return [image_node] @@ -138,13 +139,18 @@ def render_aafig_images(app, doctree): img["height"] = height +class AafigureNotInstalled(AafigError): + def __init__(self, *args: object, **kwargs: object) -> None: + return super().__init__("aafigure module not installed", *args, **kwargs) + + def render_aafigure(app, text, options): """ Render an ASCII art figure into the requested format output file. """ if aafigure is None: - raise AafigError("aafigure module not installed") + raise AafigureNotInstalled() fname = get_basename(text, options) fname = "{}.{}".format(get_basename(text, options), options["format"]) @@ -173,10 +179,10 @@ def render_aafigure(app, text, options): f = None try: try: - f = open(metadata_fname) - extra = f.read() - except Exception: - raise AafigError() + with open(metadata_fname) as f: + extra = f.read() + except Exception as e: + raise AafigError() from e finally: if f is not None: f.close() @@ -190,14 +196,13 @@ def render_aafigure(app, text, options): (visitor, output) = aafigure.render(text, outfn, options) output.close() except aafigure.UnsupportedFormatError as e: - raise AafigError(str(e)) + raise AafigError(str(e)) from e extra = None if options["format"].lower() == "svg": extra = visitor.get_size_attrs() - f = open(metadata_fname, "w") - f.write(extra) - f.close() + with open(metadata_fname, "w") as f: + f.write(extra) return relfn, outfn, id, extra @@ -206,7 +211,7 @@ def setup(app): app.add_directive("aafig", AafigDirective) app.connect("doctree-read", render_aafig_images) app.add_config_value("aafig_format", DEFAULT_FORMATS, "html") - app.add_config_value("aafig_default_options", dict(), "html") + app.add_config_value("aafig_default_options", {}, "html") # vim: set expandtab shiftwidth=4 softtabstop=4 : diff --git a/docs/conf.py b/docs/conf.py index 5bf2406606b..fea1c2e785f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,10 +1,10 @@ # flake8: NOQA: E501 import contextlib import inspect +import pathlib import sys import typing as t from os.path import relpath -import pathlib import tmuxp @@ -172,12 +172,12 @@ } # aafig format, try to get working with pdf -aafig_format = dict(latex="pdf", html="gif") +aafig_format = {"latex": "pdf", "html": "gif"} -aafig_default_options = dict(scale=0.75, aspect=0.5, proportional=True) +aafig_default_options = {"scale": 0.75, "aspect": 0.5, "proportional": True} -def linkcode_resolve(domain, info): # NOQA: C901 +def linkcode_resolve(domain, info): """ Determine the URL corresponding to Python object @@ -200,7 +200,7 @@ def linkcode_resolve(domain, info): # NOQA: C901 for part in fullname.split("."): try: obj = getattr(obj, part) - except Exception: + except Exception: # NOQA: PERF203 return None # strip decorators, which would resolve to the source of the decorator @@ -224,10 +224,7 @@ def linkcode_resolve(domain, info): # NOQA: C901 except Exception: lineno = None - if lineno: - linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) - else: - linespec = "" + linespec = "#L%d-L%d" % (lineno, lineno + len(source) - 1) if lineno else "" fn = relpath(fn, start=pathlib.Path(tmuxp.__file__).parent) diff --git a/pyproject.toml b/pyproject.toml index 85e938d9dd8..e90bba676ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -155,6 +155,35 @@ module = [ ] ignore_missing_imports = true +[tool.ruff] +target-version = "py37" +select = [ + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "Q", # flake8-quotes + "PTH", # flake8-use-pathlib + "SIM", # flake8-simplify + "TRY", # Trycertatops + "PERF", # Perflint + "RUF" # Ruff-specific rules +] + +[tool.ruff.isort] +known-first-party = [ + "tmuxp" +] +combine-as-imports = true + +[tool.ruff.per-file-ignores] +"*/__init__.py" = ["F401"] +"src/tmuxp/workspace/finders.py" = ["PTH"] +"src/tmuxp/cli/*.py" = ["PTH"] +"docs/_ext/aafig.py" = ["PTH"] + [build-system] requires = ["poetry_core>=1.0.0"] build-backend = "poetry.core.masonry.api" diff --git a/src/tmuxp/cli/convert.py b/src/tmuxp/cli/convert.py index d10e5bd4359..96251cec721 100644 --- a/src/tmuxp/cli/convert.py +++ b/src/tmuxp/cli/convert.py @@ -6,6 +6,7 @@ from tmuxp.config_reader import ConfigReader from tmuxp.workspace.finders import find_workspace_file, get_workspace_dir +from .. import exc from .utils import prompt_yes_no if t.TYPE_CHECKING: @@ -40,6 +41,13 @@ def create_convert_subparser( return parser +class ConvertUnknownFileType(exc.TmuxpException): + def __init__(self, ext: str, *args: object, **kwargs: object) -> None: + return super().__init__( + f"Unknown filetype: {ext} (valid: [.json, .yaml, .yml])" + ) + + def command_convert( workspace_file: t.Union[str, pathlib.Path], answer_yes: bool, @@ -61,7 +69,7 @@ def command_convert( elif ext in [".yaml", ".yml"]: to_filetype = "json" else: - raise Exception(f"Unknown filetype: {ext} (valid: [.json, .yaml, .yml])") + raise ConvertUnknownFileType(ext) configparser = ConfigReader.from_file(workspace_file) newfile = workspace_file.parent / (str(workspace_file.stem) + f".{to_filetype}") @@ -72,13 +80,14 @@ def command_convert( **{"default_flow_style": False} if to_filetype == "yaml" else {}, ) - if not answer_yes: - if prompt_yes_no(f"Convert to <{workspace_file}> to {to_filetype}?"): - if prompt_yes_no("Save workspace to %s?" % newfile): - answer_yes = True + if ( + not answer_yes + and prompt_yes_no(f"Convert to <{workspace_file}> to {to_filetype}?") + and prompt_yes_no("Save workspace to %s?" % newfile) + ): + answer_yes = True if answer_yes: - buf = open(newfile, "w") - buf.write(new_workspace) - buf.close() + with open(newfile, "w") as buf: + buf.write(new_workspace) print(f"New workspace file saved to <{newfile}>.") diff --git a/src/tmuxp/cli/debug_info.py b/src/tmuxp/cli/debug_info.py index a23fac1f88f..d72e3fc1ac6 100644 --- a/src/tmuxp/cli/debug_info.py +++ b/src/tmuxp/cli/debug_info.py @@ -7,7 +7,6 @@ import typing as t from colorama import Fore - from libtmux.__about__ import __version__ as libtmux_version from libtmux.common import get_version, tmux_cmd @@ -34,7 +33,7 @@ def prepend_tab(strings): """ Prepend tab to strings in list. """ - return list(map(lambda x: "\t%s" % x, strings)) + return ["\t%s" % x for x in strings] def output_break(): """ diff --git a/src/tmuxp/cli/freeze.py b/src/tmuxp/cli/freeze.py index 2383a435ba5..0f22257fc18 100644 --- a/src/tmuxp/cli/freeze.py +++ b/src/tmuxp/cli/freeze.py @@ -5,11 +5,12 @@ import typing as t from libtmux.server import Server + from tmuxp.config_reader import ConfigReader from tmuxp.exc import TmuxpException from tmuxp.workspace.finders import get_workspace_dir -from .. import util +from .. import exc, util from ..workspace import freezer from .utils import prompt, prompt_choices, prompt_yes_no @@ -105,7 +106,7 @@ def command_freeze( session = util.get_session(server) if not session: - raise TmuxpException("Session not found.") + raise exc.SessionNotFound() except TmuxpException as e: print(e) return @@ -194,9 +195,8 @@ def extract_workspace_format( destdir = os.path.dirname(dest) if not os.path.isdir(destdir): os.makedirs(destdir) - buf = open(dest, "w") - buf.write(workspace) - buf.close() + with open(dest, "w") as buf: + buf.write(workspace) if not args.quiet: print("Saved to %s." % dest) diff --git a/src/tmuxp/cli/import_config.py b/src/tmuxp/cli/import_config.py index 895ee6f2141..301f0dd8cfb 100644 --- a/src/tmuxp/cli/import_config.py +++ b/src/tmuxp/cli/import_config.py @@ -11,7 +11,7 @@ from .utils import prompt, prompt_choices, prompt_yes_no, tmuxp_echo -def get_tmuxinator_dir() -> str: +def get_tmuxinator_dir() -> pathlib.Path: """ Return tmuxinator configuration directory. @@ -19,7 +19,7 @@ def get_tmuxinator_dir() -> str: Returns ------- - str : + pathlib.Path : absolute path to tmuxinator config directory See Also @@ -27,25 +27,25 @@ def get_tmuxinator_dir() -> str: :meth:`tmuxp.workspace.importers.tmuxinator.import_tmuxinator` """ if "TMUXINATOR_CONFIG" in os.environ: - return os.path.expanduser(os.environ["TMUXINATOR_CONFIG"]) + return pathlib.Path(os.environ["TMUXINATOR_CONFIG"]).expanduser() - return os.path.expanduser("~/.tmuxinator/") + return pathlib.Path("~/.tmuxinator/").expanduser() -def get_teamocil_dir() -> str: +def get_teamocil_dir() -> pathlib.Path: """ Return teamocil configuration directory. Returns ------- - str : + pathlib.Path : absolute path to teamocil config directory See Also -------- :meth:`tmuxp.workspace.importers.teamocil.import_teamocil` """ - return os.path.expanduser("~/.teamocil/") + return pathlib.Path("~/.teamocil/").expanduser() def _resolve_path_no_overwrite(workspace_file: str) -> str: @@ -153,9 +153,8 @@ def import_config( if prompt_yes_no("Save to %s?" % dest_path): dest = dest_path - buf = open(dest, "w") - buf.write(new_config) - buf.close() + with open(dest, "w") as buf: + buf.write(new_config) tmuxp_echo("Saved to %s." % dest) else: diff --git a/src/tmuxp/cli/load.py b/src/tmuxp/cli/load.py index 46862bb4238..de21db1aa6a 100644 --- a/src/tmuxp/cli/load.py +++ b/src/tmuxp/cli/load.py @@ -16,6 +16,7 @@ from libtmux.common import has_gte_version from libtmux.server import Server from libtmux.session import Session + from tmuxp.types import StrPath from .. import config_reader, exc, log, util @@ -90,11 +91,7 @@ def set_layout_hook(session: Session, hook_name: str) -> None: hook_cmd.append("selectw -p") # unset the hook immediately after executing - hook_cmd.append( - "set-hook -u -t {target_session} {hook_name}".format( - target_session=session.id, hook_name=hook_name - ) - ) + hook_cmd.append(f"set-hook -u -t {session.id} {hook_name}") hook_cmd.append(f"selectw -t {attached_window.id}") # join the hook's commands with semicolons @@ -118,12 +115,26 @@ def load_plugins(session_config: t.Dict[str, t.Any]) -> t.List[t.Any]: module_name = plugin.split(".") module_name = ".".join(module_name[:-1]) plugin_name = plugin.split(".")[-1] + except Exception as error: + tmuxp_echo( + style("[Plugin Error] ", fg="red") + + f"Couldn't load {plugin}\n" + + style(f"{error}", fg="yellow") + ) + sys.exit(1) + + try: plugin = getattr(importlib.import_module(module_name), plugin_name) plugins.append(plugin()) except exc.TmuxpPluginException as error: if not prompt_yes_no( - "%sSkip loading %s?" - % (style(str(error), fg="yellow"), plugin_name), + "{}Skip loading {}?".format( + style( + str(error), + fg="yellow", + ), + plugin_name, + ), default=True, ): tmuxp_echo( diff --git a/src/tmuxp/cli/shell.py b/src/tmuxp/cli/shell.py index 7ea117cbf20..d0774b2dffa 100644 --- a/src/tmuxp/cli/shell.py +++ b/src/tmuxp/cli/shell.py @@ -166,7 +166,7 @@ def command_shell( session=session, window_name=args.window_name, current_pane=current_pane ) - pane = util.get_pane(window=window, current_pane=current_pane) # NOQA: F841 + pane = util.get_pane(window=window, current_pane=current_pane) if args.command is not None: exec(args.command) diff --git a/src/tmuxp/cli/utils.py b/src/tmuxp/cli/utils.py index 9a2d41df9c4..91f2ef165c5 100644 --- a/src/tmuxp/cli/utils.py +++ b/src/tmuxp/cli/utils.py @@ -4,6 +4,12 @@ from .. import log +if t.TYPE_CHECKING: + from typing_extensions import TypeAlias + + CLIColour: TypeAlias = t.Union[int, t.Tuple[int, int, int], str] + + logger = logging.getLogger(__name__) @@ -126,7 +132,7 @@ def prompt_choices( if isinstance(choice, str): options.append(choice) elif isinstance(choice, tuple): - options.append("%s [%s]" % (choice, choice[0])) + options.append(f"{choice} [{choice[0]}]") choice = choice[0] _choices.append(choice) @@ -183,10 +189,15 @@ def _interpret_color( return str(_ansi_colors[color] + offset) +class UnknownStyleColor(Exception): + def __init__(self, color: "CLIColour", *args: object, **kwargs: object) -> None: + return super().__init__(f"Unknown color {color!r}", *args, **kwargs) + + def style( text: t.Any, - fg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, - bg: t.Optional[t.Union[int, t.Tuple[int, int, int], str]] = None, + fg: t.Optional["CLIColour"] = None, + bg: t.Optional["CLIColour"] = None, bold: t.Optional[bool] = None, dim: t.Optional[bool] = None, underline: t.Optional[bool] = None, @@ -207,13 +218,13 @@ def style( try: bits.append(f"\033[{_interpret_color(fg)}m") except KeyError: - raise TypeError(f"Unknown color {fg!r}") from None + raise UnknownStyleColor(color=fg) from None if bg: try: bits.append(f"\033[{_interpret_color(bg, 10)}m") except KeyError: - raise TypeError(f"Unknown color {bg!r}") from None + raise UnknownStyleColor(color=bg) from None if bold is not None: bits.append(f"\033[{1 if bold else 22}m") diff --git a/src/tmuxp/config_reader.py b/src/tmuxp/config_reader.py index 6f3683c6dbb..b3562da7d7a 100644 --- a/src/tmuxp/config_reader.py +++ b/src/tmuxp/config_reader.py @@ -99,7 +99,7 @@ def _from_file(cls, path: pathlib.Path) -> t.Dict[str, t.Any]: {'session_name': 'my session'} """ assert isinstance(path, pathlib.Path) - content = open(path).read() + content = path.open().read() if path.suffix in [".yaml", ".yml"]: format: "FormatLiteral" = "yaml" diff --git a/src/tmuxp/exc.py b/src/tmuxp/exc.py index e8801e19f0c..5fb0d9f722f 100644 --- a/src/tmuxp/exc.py +++ b/src/tmuxp/exc.py @@ -6,6 +6,8 @@ """ import typing as t +from libtmux._internal.query_list import ObjectDoesNotExist + from ._compat import implements_to_string @@ -19,10 +21,58 @@ class WorkspaceError(TmuxpException): """Error parsing tmuxp workspace data.""" +class SessionNotFound(TmuxpException): + def __init__( + self, session_target: t.Optional[str] = None, *args: object, **kwargs: object + ) -> None: + msg = "Session not found" + if session_target is not None: + msg += f": {session_target}" + return super().__init__(msg, *args, **kwargs) + + +class WindowNotFound(TmuxpException): + def __init__( + self, window_target: t.Optional[str] = None, *args: object, **kwargs: object + ) -> None: + msg = "Window not found" + if window_target is not None: + msg += f": {window_target}" + return super().__init__(msg, *args, **kwargs) + + +class PaneNotFound(TmuxpException): + def __init__( + self, pane_target: t.Optional[str] = None, *args: object, **kwargs: object + ) -> None: + msg = "Pane not found" + if pane_target is not None: + msg += f": {pane_target}" + return super().__init__(msg, *args, **kwargs) + + class EmptyWorkspaceException(WorkspaceError): """Workspace file is empty.""" + def __init__(self, *args: object, **kwargs: object) -> None: + return super().__init__("Session configuration is empty.", *args, **kwargs) + + +class SessionMissingWorkspaceException(WorkspaceError, ObjectDoesNotExist): + def __init__(self, *args: object, **kwargs: object) -> None: + return super().__init__( + "No session object exists for WorkspaceBuilder. " + "Tip: Add session_name in constructor or run WorkspaceBuilder.build()", + *args, + **kwargs, + ) + + +class ActiveSessionMissingWorkspaceException(WorkspaceError): + def __init__(self, *args: object, **kwargs: object) -> None: + return super().__init__("No session active.", *args, **kwargs) + class TmuxpPluginException(TmuxpException): @@ -50,11 +100,11 @@ def __init__( self.cmd = cmd self.output = output self.message = ( - "before_script failed with returncode {returncode}.\n" - "command: {cmd}\n" + f"before_script failed with returncode {self.returncode}.\n" + f"command: {self.cmd}\n" "Error output:\n" - "{output}" - ).format(returncode=self.returncode, cmd=self.cmd, output=self.output) + f"{self.output}" + ) def __str__(self): return self.message diff --git a/src/tmuxp/plugin.py b/src/tmuxp/plugin.py index 8417d28f6c1..7d56316630c 100644 --- a/src/tmuxp/plugin.py +++ b/src/tmuxp/plugin.py @@ -139,14 +139,14 @@ def _version_check(self) -> None: assert isinstance(constraints, dict) try: assert self._pass_version_check(**constraints) - except AssertionError: + except AssertionError as e: raise TmuxpPluginException( "Incompatible {dep} version: {version}\n{plugin_name} " "requirements:\nmin: {vmin} | max: {vmax} | " "incompatible: {incompatible}\n".format( dep=dep, plugin_name=self.plugin_name, **constraints ) - ) + ) from e def _pass_version_check( self, diff --git a/src/tmuxp/shell.py b/src/tmuxp/shell.py index 44aa6fe55e1..0f73721d6c3 100644 --- a/src/tmuxp/shell.py +++ b/src/tmuxp/shell.py @@ -6,6 +6,7 @@ """ import logging import os +import pathlib import typing as t logger = logging.getLogger(__name__) @@ -32,7 +33,7 @@ def has_ipython() -> bool: def has_ptpython() -> bool: try: - from ptpython.repl import embed, run_config # NOQA F841 + from ptpython.repl import embed, run_config # F841 except ImportError: try: from prompt_toolkit.contrib.repl import embed, run_config # NOQA F841 @@ -44,8 +45,8 @@ def has_ptpython() -> bool: def has_ptipython() -> bool: try: - from ptpython.ipython import embed # NOQA F841 - from ptpython.repl import run_config # NOQA F841 + from ptpython.ipython import embed # F841 + from ptpython.repl import run_config # F841 except ImportError: try: from prompt_toolkit.contrib.ipython import embed # NOQA F841 @@ -80,7 +81,7 @@ def get_bpython(options, extra_args=None): if extra_args is None: extra_args = {} - from bpython import embed # NOQA F841 + from bpython import embed # F841 def launch_bpython(): imported_objects = get_launch_args(**options) @@ -106,7 +107,7 @@ def launch_ipython(): ipython_arguments = extra_args or get_ipython_arguments() start_ipython(argv=ipython_arguments, user_ns=imported_objects) - return launch_ipython + return launch_ipython # NOQA: TRY300 except ImportError: # IPython < 0.11 # Explicitly pass an empty list as arguments, because otherwise @@ -130,7 +131,7 @@ def get_ptpython(options, vi_mode=False): def launch_ptpython(): imported_objects = get_launch_args(**options) - history_filename = os.path.expanduser("~/.ptpython_history") + history_filename = str(pathlib.Path("~/.ptpython_history").expanduser()) embed( globals=imported_objects, history_filename=history_filename, @@ -156,7 +157,7 @@ def get_ptipython(options, vi_mode=False): def launch_ptipython(): imported_objects = get_launch_args(**options) - history_filename = os.path.expanduser("~/.ptpython_history") + history_filename = str(pathlib.Path("~/.ptpython_history").expanduser()) embed( user_ns=imported_objects, history_filename=history_filename, @@ -169,13 +170,17 @@ def launch_ptipython(): def get_launch_args(**kwargs): import libtmux + from libtmux.pane import Pane + from libtmux.server import Server + from libtmux.session import Session + from libtmux.window import Window return { "libtmux": libtmux, - "Server": libtmux.Server, - "Session": libtmux.Session, - "Window": libtmux.Window, - "Pane": libtmux.Pane, + "Server": Server, + "Session": Session, + "Window": Window, + "Pane": Pane, "server": kwargs.get("server"), "session": kwargs.get("session"), "window": kwargs.get("window"), @@ -208,15 +213,16 @@ def get_code(use_pythonrc, imported_objects): # We want to honor both $PYTHONSTARTUP and .pythonrc.py, so follow system # conventions and get $PYTHONSTARTUP first then .pythonrc.py. if use_pythonrc: + PYTHONSTARTUP = os.environ.get("PYTHONSTARTUP") for pythonrc in { - os.environ.get("PYTHONSTARTUP"), - os.path.expanduser("~/.pythonrc.py"), + *([pathlib.Path(PYTHONSTARTUP)] if PYTHONSTARTUP is not None else []), + pathlib.Path("~/.pythonrc.py").expanduser(), }: if not pythonrc: continue - if not os.path.isfile(pythonrc): + if not pythonrc.is_file(): continue - with open(pythonrc) as handle: + with pythonrc.open() as handle: pythonrc_code = handle.read() # Match the behavior of the cpython shell where an error in # PYTHONSTARTUP prints an exception and continues. diff --git a/src/tmuxp/types.py b/src/tmuxp/types.py index 536225c9f1d..f1f6c138f88 100644 --- a/src/tmuxp/types.py +++ b/src/tmuxp/types.py @@ -6,7 +6,7 @@ :class:`StrPath` and :class:`StrOrBytesPath` is based on `typeshed's`_. .. _typeshed's: https://github.com/python/typeshed/blob/9687d5/stdlib/_typeshed/__init__.pyi#L98 -""" # NOQA E501 +""" # E501 from os import PathLike from typing import Union diff --git a/src/tmuxp/util.py b/src/tmuxp/util.py index 90acacf05a7..8a306fbedf7 100644 --- a/src/tmuxp/util.py +++ b/src/tmuxp/util.py @@ -50,15 +50,20 @@ def run_before_script( stderr_str = "\n".join(list(filter(None, stderr_strlist))) # filter empty raise exc.BeforeLoadScriptError( - proc.returncode, os.path.abspath(script_file), stderr_str + proc.returncode, + os.path.abspath(script_file), # NOQA: PTH100 + stderr_str, ) - - return proc.returncode + else: + return proc.returncode except OSError as e: if e.errno == 2: - raise exc.BeforeLoadScriptNotExists(e, os.path.abspath(script_file)) + raise exc.BeforeLoadScriptNotExists( + e, + os.path.abspath(script_file), # NOQA: PTH100 + ) from e else: - raise e + raise def oh_my_zsh_auto_title() -> None: @@ -66,29 +71,31 @@ def oh_my_zsh_auto_title() -> None: See: https://github.com/robbyrussell/oh-my-zsh/pull/257 """ - if "SHELL" in os.environ and "zsh" in os.environ.get("SHELL", ""): - if os.path.exists(os.path.expanduser("~/.oh-my-zsh")): - # oh-my-zsh exists - if ( - "DISABLE_AUTO_TITLE" not in os.environ - or os.environ.get("DISABLE_AUTO_TITLE") == "false" - ): - print( - "Please set:\n\n" - "\texport DISABLE_AUTO_TITLE='true'\n\n" - "in ~/.zshrc or where your zsh profile is stored.\n" - 'Remember the "export" at the beginning!\n\n' - "Then create a new shell or type:\n\n" - "\t$ source ~/.zshrc" - ) + if ( + "SHELL" in os.environ + and "zsh" in os.environ.get("SHELL", "") + and os.path.exists(os.path.expanduser("~/.oh-my-zsh")) # NOQA PTH110, PTH111 + and ( + "DISABLE_AUTO_TITLE" not in os.environ + or os.environ.get("DISABLE_AUTO_TITLE") == "false" + ) + ): + print( + "Please set:\n\n" + "\texport DISABLE_AUTO_TITLE='true'\n\n" + "in ~/.zshrc or where your zsh profile is stored.\n" + 'Remember the "export" at the beginning!\n\n' + "Then create a new shell or type:\n\n" + "\t$ source ~/.zshrc" + ) def get_current_pane(server: "Server") -> t.Optional["Pane"]: """Return Pane if one found in env""" if os.getenv("TMUX_PANE") is not None: try: - return [p for p in server.panes if p.pane_id == os.getenv("TMUX_PANE")][0] - except IndexError: + return next(p for p in server.panes if p.pane_id == os.getenv("TMUX_PANE")) + except StopIteration: pass return None @@ -109,15 +116,13 @@ def get_session( session = server.sessions.get(session_id=current_pane.session_id) else: session = server.sessions[0] - except Exception: - session = None - if session is None: + except Exception as e: if session_name: - raise exc.TmuxpException("Session not found: %s" % session_name) - else: - raise exc.TmuxpException("Session not found") + raise exc.SessionNotFound(session_name) from e + raise exc.SessionNotFound() from e + assert session is not None return session @@ -133,17 +138,14 @@ def get_window( window = session.windows.get(window_id=current_pane.window_id) else: window = session.windows[0] - except Exception: - window = None - - if window is None: + except Exception as e: if window_name: - raise exc.TmuxpException("Window not found: %s" % window_name) - if current_pane: - raise exc.TmuxpException("Window not found: %s" % current_pane) - else: - raise exc.TmuxpException("Window not found") + raise exc.WindowNotFound(window_target=window_name) from e + elif current_pane: + raise exc.WindowNotFound(window_target=str(current_pane)) from e + raise exc.WindowNotFound() from e + assert window is not None return window @@ -151,16 +153,15 @@ def get_pane(window: "Window", current_pane: t.Optional["Pane"] = None) -> "Pane pane = None try: if current_pane is not None: - pane = window.panes.get(pane_id=current_pane.pane_id) # NOQA: F841 + pane = window.panes.get(pane_id=current_pane.pane_id) else: - pane = window.attached_pane # NOQA: F841 + pane = window.attached_pane except exc.TmuxpException as e: print(e) if pane is None: if current_pane: - raise exc.TmuxpException("Pane not found: %s" % current_pane) - else: - raise exc.TmuxpException("Pane not found") + raise exc.PaneNotFound(str(current_pane)) + raise exc.PaneNotFound() return pane diff --git a/src/tmuxp/workspace/builder.py b/src/tmuxp/workspace/builder.py index efd2f2fc6aa..b73a250fecd 100644 --- a/src/tmuxp/workspace/builder.py +++ b/src/tmuxp/workspace/builder.py @@ -141,7 +141,7 @@ def __init__( self, session_config: t.Dict[str, t.Any], server: Server, - plugins: t.List[t.Any] = [], + plugins: t.Optional[t.List[t.Any]] = None, ) -> None: """Initialize workspace loading. @@ -162,8 +162,10 @@ def __init__( ``self.session``. """ + if plugins is None: + plugins = [] if not session_config: - raise exc.EmptyWorkspaceException("Session configuration is empty.") + raise exc.EmptyWorkspaceException() # validation.validate_schema(session_config) @@ -188,10 +190,7 @@ def __init__( @property def session(self): if self._session is None: - raise ObjectDoesNotExist( - "No session object exists for WorkspaceBuilder. " - "Tip: Add session_name in constructor or run WorkspaceBuilder.build()" - ) + raise exc.SessionMissingWorkspaceException() return self._session def session_exists(self, session_name: str) -> bool: @@ -256,7 +255,7 @@ def build(self, session: t.Optional[Session] = None, append: bool = False) -> No assert session.server is not None self.server: "Server" = session.server - self.server.sessions + assert self.server.sessions is not None assert self.server.has_session(session.name) assert session.id @@ -276,9 +275,9 @@ def build(self, session: t.Optional[Session] = None, append: bool = False) -> No if "start_directory" in self.session_config: cwd = self.session_config["start_directory"] run_before_script(self.session_config["before_script"], cwd=cwd) - except Exception as e: + except Exception: self.session.kill_session() - raise e + raise if "options" in self.session_config: for option, value in self.session_config["options"].items(): @@ -349,10 +348,7 @@ def iter_create_windows( for window_iterator, window_config in enumerate( self.session_config["windows"], start=1 ): - if "window_name" not in window_config: - window_name = None - else: - window_name = window_config["window_name"] + window_name = window_config.get("window_name", None) is_first_window_pass = self.first_window_pass( window_iterator, session, append @@ -363,20 +359,14 @@ def iter_create_windows( w1 = session.attached_window w1.move_window("99") - if "start_directory" in window_config: - start_directory = window_config["start_directory"] - else: - start_directory = None + start_directory = window_config.get("start_directory", None) # If the first pane specifies a start_directory, use that instead. panes = window_config["panes"] if panes and "start_directory" in panes[0]: start_directory = panes[0]["start_directory"] - if "window_shell" in window_config: - window_shell = window_config["window_shell"] - else: - window_shell = None + window_shell = window_config.get("window_shell", None) # If the first pane specifies a shell, use that instead. try: @@ -462,7 +452,9 @@ def iter_create_panes( pane = window.attached_pane else: - def get_pane_start_directory(): + def get_pane_start_directory( + pane_config: t.Dict[str, str], window_config: t.Dict[str, str] + ) -> t.Optional[str]: if "start_directory" in pane_config: return pane_config["start_directory"] elif "start_directory" in window_config: @@ -470,7 +462,9 @@ def get_pane_start_directory(): else: return None - def get_pane_shell(): + def get_pane_shell( + pane_config: t.Dict[str, str], window_config: t.Dict[str, str] + ) -> t.Optional[str]: if "shell" in pane_config: return pane_config["shell"] elif "window_shell" in window_config: @@ -496,8 +490,14 @@ def get_pane_shell(): pane = window.split_window( attach=True, - start_directory=get_pane_start_directory(), - shell=get_pane_shell(), + start_directory=get_pane_start_directory( + pane_config=pane_config, + window_config=window_config, + ), + shell=get_pane_shell( + pane_config=pane_config, + window_config=window_config, + ), target=pane.id, environment=environment, ) @@ -564,7 +564,7 @@ def find_current_attached_session(self) -> Session: current_active_pane = get_current_pane(self.server) if current_active_pane is None: - raise exc.TmuxpException("No session active.") + raise exc.ActiveSessionMissingWorkspaceException() return next( ( diff --git a/src/tmuxp/workspace/finders.py b/src/tmuxp/workspace/finders.py index 943de30bff6..16b805c498f 100644 --- a/src/tmuxp/workspace/finders.py +++ b/src/tmuxp/workspace/finders.py @@ -19,11 +19,7 @@ def is_workspace_file( filename: str, - extensions: t.Union["ValidExtensions", t.List["ValidExtensions"]] = [ - ".yml", - ".yaml", - ".json", - ], + extensions: t.Union["ValidExtensions", t.List["ValidExtensions"], None] = None, ) -> bool: """ Return True if file has a valid workspace file type. @@ -39,13 +35,15 @@ def is_workspace_file( ------- bool """ + if extensions is None: + extensions = [".yml", ".yaml", ".json"] extensions = [extensions] if isinstance(extensions, str) else extensions return any(filename.endswith(e) for e in extensions) def in_dir( - workspace_dir: t.Union[pathlib.Path, str] = os.path.expanduser("~/.tmuxp"), - extensions: t.List["ValidExtensions"] = [".yml", ".yaml", ".json"], + workspace_dir: t.Union[pathlib.Path, str, None] = None, + extensions: t.Optional[t.List["ValidExtensions"]] = None, ) -> t.List[str]: """ Return a list of workspace_files in ``workspace_dir``. @@ -61,11 +59,17 @@ def in_dir( ------- list """ - workspace_files = [] + if workspace_dir is None: + workspace_dir = os.path.expanduser("~/.tmuxp") - for filename in os.listdir(workspace_dir): - if is_workspace_file(filename, extensions) and not filename.startswith("."): - workspace_files.append(filename) + if extensions is None: + extensions = [".yml", ".yaml", ".json"] + + workspace_files = [ + filename + for filename in os.listdir(workspace_dir) + if is_workspace_file(filename, extensions) and not filename.startswith(".") + ] return workspace_files @@ -86,11 +90,11 @@ def in_cwd() -> t.List[str]: >>> sorted(in_cwd()) ['.tmuxp.json', '.tmuxp.yaml'] """ - workspace_files = [] - - for filename in os.listdir(os.getcwd()): - if filename.startswith(".tmuxp") and is_workspace_file(filename): - workspace_files.append(filename) + workspace_files = [ + filename + for filename in os.listdir(os.getcwd()) + if filename.startswith(".tmuxp") and is_workspace_file(filename) + ] return workspace_files diff --git a/src/tmuxp/workspace/freezer.py b/src/tmuxp/workspace/freezer.py index b9b4ab9ddb3..8f38e7be4bd 100644 --- a/src/tmuxp/workspace/freezer.py +++ b/src/tmuxp/workspace/freezer.py @@ -1,6 +1,10 @@ +import typing as t + from libtmux.pane import Pane from libtmux.session import Session -import typing as t + +if t.TYPE_CHECKING: + from libtmux.window import Window def inline(workspace_dict): @@ -23,7 +27,7 @@ def inline(workspace_dict): ): workspace_dict["shell_command"] = workspace_dict["shell_command"][0] - if len(workspace_dict.keys()) == int(1): + if len(workspace_dict.keys()) == 1: workspace_dict = workspace_dict["shell_command"] if ( "shell_command_before" in workspace_dict @@ -76,10 +80,10 @@ def freeze(session: Session) -> t.Dict[str, t.Any]: # If all panes have same path, set 'start_directory' instead # of using 'cd' shell commands. - def pane_has_same_path(pane: Pane) -> bool: + def pane_has_same_path(window: "Window", pane: Pane) -> bool: return window.panes[0].pane_current_path == pane.pane_current_path - if all(pane_has_same_path(pane=pane) for pane in window.panes): + if all(pane_has_same_path(window=window, pane=pane) for pane in window.panes): window_config["start_directory"] = window.panes[0].pane_current_path for pane in window.panes: @@ -94,7 +98,7 @@ def pane_has_same_path(pane: Pane) -> bool: current_cmd = pane.pane_current_command - def filter_interpretters_and_shells() -> bool: + def filter_interpretters_and_shells(current_cmd: t.Optional[str]) -> bool: return current_cmd is not None and ( current_cmd.startswith("-") or any( @@ -102,7 +106,7 @@ def filter_interpretters_and_shells() -> bool: ) ) - if filter_interpretters_and_shells(): + if filter_interpretters_and_shells(current_cmd=current_cmd): current_cmd = None if current_cmd: diff --git a/src/tmuxp/workspace/importers.py b/src/tmuxp/workspace/importers.py index b5eff6606ba..9f60208f28c 100644 --- a/src/tmuxp/workspace/importers.py +++ b/src/tmuxp/workspace/importers.py @@ -126,10 +126,7 @@ def import_teamocil(workspace_dict: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: if "session" in workspace_dict: workspace_dict = workspace_dict["session"] - if "name" in workspace_dict: - tmuxp_workspace["session_name"] = workspace_dict["name"] - else: - tmuxp_workspace["session_name"] = None + tmuxp_workspace["session_name"] = workspace_dict.get("name", None) if "root" in workspace_dict: tmuxp_workspace["start_directory"] = workspace_dict.pop("root") @@ -144,10 +141,10 @@ def import_teamocil(workspace_dict: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]: if "filters" in w: if "before" in w["filters"]: - for b in w["filters"]["before"]: + for _b in w["filters"]["before"]: window_dict["shell_command_before"] = w["filters"]["before"] if "after" in w["filters"]: - for b in w["filters"]["after"]: + for _b in w["filters"]["after"]: window_dict["shell_command_after"] = w["filters"]["after"] if "root" in w: diff --git a/src/tmuxp/workspace/loader.py b/src/tmuxp/workspace/loader.py index 9fc30330b3a..1668a554547 100644 --- a/src/tmuxp/workspace/loader.py +++ b/src/tmuxp/workspace/loader.py @@ -6,28 +6,28 @@ """ import logging import os +import pathlib from typing import Dict logger = logging.getLogger(__name__) -def expandshell(_path): - """ - Return expanded path based on user's ``$HOME`` and ``env``. +def expandshell(value: str) -> str: + """Returned wih variables expanded based on user's ``$HOME`` and ``env``. - :py:func:`os.path.expanduser` and :py:func:`os.path.expandvars`. + :py:func:`os.path.expanduser` and :py:fubasednc:`os.path.expandvars`. Parameters ---------- - path : str - path to expand + value : str + value to expand Returns ------- str - path with shell variables expanded + value with shell variables expanded """ - return os.path.expandvars(os.path.expanduser(_path)) + return os.path.expandvars(os.path.expanduser(value)) # NOQA: PTH111 def expand_cmd(p: Dict) -> Dict: @@ -48,9 +48,12 @@ def expand_cmd(p: Dict) -> Dict: if not cmds or any(a == cmds for a in [None, "blank", "pane"]): cmds = [] - if isinstance(cmds, list) and len(cmds) == int(1): - if any(a in cmds for a in [None, "blank", "pane"]): - cmds = [] + if ( + isinstance(cmds, list) + and len(cmds) == 1 + and any(a in cmds for a in [None, "blank", "pane"]) + ): + cmds = [] for cmd_idx, cmd in enumerate(cmds): if isinstance(cmd, str): @@ -98,8 +101,7 @@ def expand(workspace_dict, cwd=None, parent=None): # Note: cli.py will expand workspaces relative to project's workspace directory # for the first cwd argument. - if not cwd: - cwd = os.getcwd() + cwd = pathlib.Path().cwd() if not cwd else pathlib.Path(cwd) if "session_name" in workspace_dict: workspace_dict["session_name"] = expandshell(workspace_dict["session_name"]) @@ -110,7 +112,7 @@ def expand(workspace_dict, cwd=None, parent=None): val = workspace_dict["environment"][key] val = expandshell(val) if any(val.startswith(a) for a in [".", "./"]): - val = os.path.normpath(os.path.join(cwd, val)) + val = str(cwd / val) workspace_dict["environment"][key] = val if "global_options" in workspace_dict: for key in workspace_dict["global_options"]: @@ -118,7 +120,7 @@ def expand(workspace_dict, cwd=None, parent=None): if isinstance(val, str): val = expandshell(val) if any(val.startswith(a) for a in [".", "./"]): - val = os.path.normpath(os.path.join(cwd, val)) + val = str(cwd / val) workspace_dict["global_options"][key] = val if "options" in workspace_dict: for key in workspace_dict["options"]: @@ -126,7 +128,7 @@ def expand(workspace_dict, cwd=None, parent=None): if isinstance(val, str): val = expandshell(val) if any(val.startswith(a) for a in [".", "./"]): - val = os.path.normpath(os.path.join(cwd, val)) + val = str(cwd / val) workspace_dict["options"][key] = val # Any workspace section, session, window, pane that can contain the @@ -144,16 +146,16 @@ def expand(workspace_dict, cwd=None, parent=None): # This is for the case where you may be loading a workspace from # outside your shell current directory. if parent: - cwd = parent["start_directory"] - start_path = os.path.normpath(os.path.join(cwd, start_path)) + cwd = pathlib.Path(parent["start_directory"]) + + start_path = str((cwd / start_path).resolve(strict=False)) + workspace_dict["start_directory"] = start_path if "before_script" in workspace_dict: workspace_dict["before_script"] = expandshell(workspace_dict["before_script"]) if any(workspace_dict["before_script"].startswith(a) for a in [".", "./"]): - workspace_dict["before_script"] = os.path.normpath( - os.path.join(cwd, workspace_dict["before_script"]) - ) + workspace_dict["before_script"] = str(cwd / workspace_dict["before_script"]) if "shell_command" in workspace_dict and isinstance( workspace_dict["shell_command"], str @@ -206,34 +208,29 @@ def trickle(workspace_dict): # prepends a pane's ``shell_command`` list with the window and sessions' # ``shell_command_before``. - if "start_directory" in workspace_dict: - session_start_directory = workspace_dict["start_directory"] - else: - session_start_directory = None + session_start_directory = workspace_dict.get("start_directory", None) - if "suppress_history" in workspace_dict: - suppress_history = workspace_dict["suppress_history"] - else: - suppress_history = None + suppress_history = workspace_dict.get("suppress_history", None) for window_dict in workspace_dict["windows"]: # Prepend start_directory to relative window commands if session_start_directory: + session_start_directory = session_start_directory if "start_directory" not in window_dict: window_dict["start_directory"] = session_start_directory else: if not any( window_dict["start_directory"].startswith(a) for a in ["~", "/"] ): - window_start_path = os.path.join( - session_start_directory, window_dict["start_directory"] + window_start_path = ( + pathlib.Path(session_start_directory) + / window_dict["start_directory"] ) - window_dict["start_directory"] = window_start_path + window_dict["start_directory"] = str(window_start_path) # We only need to trickle to the window, workspace builder checks wconf - if suppress_history is not None: - if "suppress_history" not in window_dict: - window_dict["suppress_history"] = suppress_history + if suppress_history is not None and "suppress_history" not in window_dict: + window_dict["suppress_history"] = suppress_history # If panes were NOT specified for a window, assume that a single pane # with no shell commands is desired diff --git a/src/tmuxp/workspace/validation.py b/src/tmuxp/workspace/validation.py index bbe1ede7f9e..02578e1de80 100644 --- a/src/tmuxp/workspace/validation.py +++ b/src/tmuxp/workspace/validation.py @@ -3,6 +3,48 @@ from .. import exc +class SchemaValidationError(exc.WorkspaceError): + pass + + +class SessionNameMissingValidationError(SchemaValidationError): + def __init__(self, *args: object, **kwargs: object) -> None: + return super().__init__( + 'workspace requires "session_name"', + *args, + **kwargs, + ) + + +class WindowListMissingValidationError(SchemaValidationError): + def __init__(self, *args: object, **kwargs: object) -> None: + return super().__init__( + 'workspace requires list of "windows"', + *args, + **kwargs, + ) + + +class WindowNameMissingValidationError(SchemaValidationError): + def __init__(self, *args: object, **kwargs: object) -> None: + return super().__init__( + 'workspace window is missing "window_name"', + *args, + **kwargs, + ) + + +class InvalidPluginsValidationError(SchemaValidationError): + def __init__(self, plugins: t.Any, *args: object, **kwargs: object) -> None: + return super().__init__( + '"plugins" only supports list type. ' + + f" Received {type(plugins)}, " + + f"value: {plugins}", + *args, + **kwargs, + ) + + def validate_schema(workspace_dict: t.Any) -> bool: """ Return True if workspace schema is correct. @@ -18,17 +60,16 @@ def validate_schema(workspace_dict: t.Any) -> bool: """ # verify session_name if "session_name" not in workspace_dict: - raise exc.WorkspaceError('workspace requires "session_name"') + raise SessionNameMissingValidationError() if "windows" not in workspace_dict: - raise exc.WorkspaceError('workspace requires list of "windows"') + raise WindowListMissingValidationError() for window in workspace_dict["windows"]: if "window_name" not in window: - raise exc.WorkspaceError('workspace window is missing "window_name"') + raise WindowNameMissingValidationError() - if "plugins" in workspace_dict: - if not isinstance(workspace_dict["plugins"], list): - raise exc.WorkspaceError('"plugins" only supports list type') + if "plugins" in workspace_dict and not isinstance(workspace_dict["plugins"], list): + raise InvalidPluginsValidationError(plugins=workspace_dict.get("plugins")) return True diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index eacf8506973..ea49fb59fe7 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -1,12 +1,12 @@ import argparse -import os +import contextlib import pathlib import typing as t -import pytest - import libtmux +import pytest from libtmux.server import Server + from tmuxp import cli from tmuxp.cli.import_config import get_teamocil_dir, get_tmuxinator_dir from tmuxp.cli.load import _reattach, load_plugins @@ -26,7 +26,7 @@ def test_creates_config_dir_not_exists(tmp_path: pathlib.Path) -> None: """cli.startup() creates config dir if not exists.""" cli.startup(tmp_path) - assert os.path.exists(tmp_path) + assert tmp_path.exists() @pytest.mark.parametrize( @@ -42,10 +42,13 @@ def test_help( monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture, ) -> None: - try: + # In scrunched terminals, prevent width causing differantiation in result.out. + monkeypatch.setenv("COLUMNS", "100") + monkeypatch.setenv("LINES", "100") + + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass + result = capsys.readouterr() assert "usage: tmuxp [-h] [--version] [--log-level log-level]" in result.out @@ -56,26 +59,28 @@ def test_resolve_behavior( ) -> None: expect = tmp_path monkeypatch.chdir(tmp_path) - assert pathlib.Path("../").resolve() == pathlib.Path(os.path.dirname(expect)) - assert pathlib.Path(".").resolve() == expect + assert pathlib.Path("../").resolve() == expect.parent + assert pathlib.Path().resolve() == expect assert pathlib.Path("./").resolve() == expect assert pathlib.Path(expect).resolve() == expect def test_get_tmuxinator_dir(monkeypatch: pytest.MonkeyPatch) -> None: - assert get_tmuxinator_dir() == os.path.expanduser("~/.tmuxinator/") + assert get_tmuxinator_dir() == pathlib.Path("~/.tmuxinator").expanduser() monkeypatch.setenv("HOME", "/moo") - assert get_tmuxinator_dir() == "/moo/.tmuxinator/" - assert get_tmuxinator_dir() == os.path.expanduser("~/.tmuxinator/") + assert get_tmuxinator_dir() == pathlib.Path("/moo/.tmuxinator/") + assert str(get_tmuxinator_dir()) == "/moo/.tmuxinator" + assert get_tmuxinator_dir() == pathlib.Path("~/.tmuxinator/").expanduser() def test_get_teamocil_dir(monkeypatch: pytest.MonkeyPatch) -> None: - assert get_teamocil_dir() == os.path.expanduser("~/.teamocil/") + assert get_teamocil_dir() == pathlib.Path("~/.teamocil/").expanduser() monkeypatch.setenv("HOME", "/moo") - assert get_teamocil_dir() == "/moo/.teamocil/" - assert get_teamocil_dir() == os.path.expanduser("~/.teamocil/") + assert get_teamocil_dir() == pathlib.Path("/moo/.teamocil/") + assert str(get_teamocil_dir()) == "/moo/.teamocil" + assert get_teamocil_dir() == pathlib.Path("~/.teamocil/").expanduser() def test_pass_config_dir_ClickPath( @@ -129,10 +134,8 @@ def test_reattach_plugins( ) builder.build() - try: + with contextlib.suppress(libtmux.exc.LibTmuxException): _reattach(builder) - except libtmux.exc.LibTmuxException: - pass assert builder.session is not None proc = builder.session.cmd("display-message", "-p", "'#S'") diff --git a/tests/cli/test_convert.py b/tests/cli/test_convert.py index b923c4ea1d2..6a36e3e46d6 100644 --- a/tests/cli/test_convert.py +++ b/tests/cli/test_convert.py @@ -1,3 +1,4 @@ +import contextlib import io import json import pathlib @@ -41,10 +42,9 @@ def test_convert( input_args = "y\ny\n" if "-y" not in cli_args else "" monkeypatch.setattr("sys.stdin", io.StringIO(input_args)) - try: + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass + tmuxp_json = tmp_path / ".tmuxp.json" assert tmuxp_json.exists() assert tmuxp_json.open().read() == json.dumps({"session_name": "hello"}, indent=2) @@ -76,10 +76,8 @@ def test_convert_json( input_args = "y\ny\n" if "-y" not in cli_args else "" monkeypatch.setattr("sys.stdin", io.StringIO(input_args)) - try: + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass tmuxp_yaml = tmp_path / ".tmuxp.yaml" assert tmuxp_yaml.exists() diff --git a/tests/cli/test_freeze.py b/tests/cli/test_freeze.py index 87ddc58c143..9d5fac36e2a 100644 --- a/tests/cli/test_freeze.py +++ b/tests/cli/test_freeze.py @@ -1,10 +1,11 @@ +import contextlib import io import pathlib import typing as t import pytest - from libtmux.server import Server + from tmuxp import cli from tmuxp.config_reader import ConfigReader @@ -47,13 +48,11 @@ def test_freeze( monkeypatch.chdir(tmp_path) # Use tmux server (socket name) used in the test assert server.socket_name is not None - cli_args = cli_args + ["-L", server.socket_name] + cli_args = [*cli_args, "-L", server.socket_name] monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) - try: + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass yaml_config_path = tmp_path / "la.yaml" assert yaml_config_path.exists() @@ -93,13 +92,11 @@ def test_freeze_overwrite( monkeypatch.chdir(tmp_path) # Use tmux server (socket name) used in the test assert server.socket_name is not None - cli_args = cli_args + ["-L", server.socket_name] + cli_args = [*cli_args, "-L", server.socket_name] monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) - try: + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass yaml_config_path = tmp_path / "exists.yaml" assert yaml_config_path.exists() diff --git a/tests/cli/test_import.py b/tests/cli/test_import.py index 0d1e76a9220..ac8c295a4d9 100644 --- a/tests/cli/test_import.py +++ b/tests/cli/test_import.py @@ -1,3 +1,4 @@ +import contextlib import io import pathlib import typing as t @@ -61,10 +62,8 @@ def test_import_teamocil( monkeypatch.chdir(tmp_path) monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) - try: + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass new_config_yaml = tmp_path / "la.yaml" assert new_config_yaml.exists() @@ -109,10 +108,8 @@ def test_import_tmuxinator( monkeypatch.chdir(tmp_path) monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) - try: + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass new_config_yaml = tmp_path / "la.yaml" assert new_config_yaml.exists() diff --git a/tests/cli/test_load.py b/tests/cli/test_load.py index 6a42ce1ef75..aa341245ab3 100644 --- a/tests/cli/test_load.py +++ b/tests/cli/test_load.py @@ -1,15 +1,15 @@ +import contextlib import io import pathlib import typing as t -import pytest - -from pytest_mock import MockerFixture - import libtmux +import pytest from libtmux.common import has_lt_version from libtmux.server import Server from libtmux.session import Session +from pytest_mock import MockerFixture + from tmuxp import cli from tmuxp.cli.load import ( _load_append_windows_to_current_session, @@ -282,22 +282,18 @@ def test_load( config_path.format(tmp_path=tmp_path, TMUXP_CONFIGDIR=tmuxp_configdir) ) tmuxp_config.write_text( - """ + f""" session_name: {session_name} windows: - window_name: test panes: - - """.format( - session_name=session_name - ), + """, encoding="utf-8", ) - try: + with contextlib.suppress(SystemExit): cli.cli([*cli_args, "-d", "-L", server.socket_name, "-y"]) - except SystemExit: - pass result = capsys.readouterr() output = "".join(list(result.out)) @@ -363,7 +359,7 @@ def test_load_zsh_autotitle_warning( # Use tmux server (socket name) used in the test assert server.socket_name is not None - cli_args = cli_args + ["-L", server.socket_name] + cli_args = [*cli_args, "-L", server.socket_name] cli.cli(cli_args) result = capsys.readouterr() @@ -413,10 +409,9 @@ def test_load_log_file( monkeypatch.chdir(tmp_path) - try: + with contextlib.suppress(Exception): cli.cli(cli_args) - except Exception: - pass + result = capsys.readouterr() log_file_path = tmp_path / "log.txt" assert "Loading" in log_file_path.open().read() @@ -457,10 +452,9 @@ def test_load_plugins(monkeypatch_plugin_test_packages: None) -> None: def test_load_plugins_version_fail_skip( monkeypatch_plugin_test_packages, cli_args, inputs, capsys: pytest.CaptureFixture ) -> None: - try: + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass + result = capsys.readouterr() assert "[Loading]" in result.out @@ -484,10 +478,9 @@ def test_load_plugins_version_fail_no_skip( ) -> None: monkeypatch.setattr("sys.stdin", io.StringIO("".join(inputs))) - try: + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass + result = capsys.readouterr() assert "[Not Skipping]" in result.out @@ -502,10 +495,9 @@ def test_load_plugins_plugin_missing( cli_args: t.List[str], capsys: pytest.CaptureFixture, ) -> None: - try: + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass + result = capsys.readouterr() assert "[Plugin Error]" in result.out diff --git a/tests/cli/test_ls.py b/tests/cli/test_ls.py index 75d6de7db54..89579d70937 100644 --- a/tests/cli/test_ls.py +++ b/tests/cli/test_ls.py @@ -1,4 +1,4 @@ -import os +import contextlib import pathlib import pytest @@ -27,7 +27,7 @@ def test_ls_cli( # - directories should be ignored # - extensions not covered in VALID_WORKSPACE_DIR_FILE_EXTENSIONS ignored_filenames = [".git/", ".gitignore/", "session_4.txt"] - stems = [os.path.splitext(f)[0] for f in filenames if f not in ignored_filenames] + stems = [pathlib.Path(f).stem for f in filenames if f not in ignored_filenames] for filename in filenames: location = tmp_path / f".tmuxp/{filename}" @@ -36,10 +36,9 @@ def test_ls_cli( else: location.touch() - try: + with contextlib.suppress(SystemExit): cli.cli(["ls"]) - except SystemExit: - pass + cli_output = capsys.readouterr().out assert cli_output == "\n".join(stems) + "\n" diff --git a/tests/cli/test_shell.py b/tests/cli/test_shell.py index 8dafac33462..c95d918c919 100644 --- a/tests/cli/test_shell.py +++ b/tests/cli/test_shell.py @@ -1,12 +1,13 @@ +import contextlib import io import pathlib import subprocess import typing as t import pytest - from libtmux.server import Server from libtmux.session import Session + from tmuxp import cli, exc @@ -98,13 +99,13 @@ def test_shell( assert window.attached_pane is not None - template_ctx = dict( - SOCKET_NAME=server.socket_name, - SESSION_NAME=session.name, - WINDOW_NAME=window_name, - PANE_ID=window.attached_pane.id, - SERVER_SOCKET_NAME=server.socket_name, - ) + template_ctx = { + "SOCKET_NAME": server.socket_name, + "SESSION_NAME": session.name, + "WINDOW_NAME": window_name, + "PANE_ID": window.attached_pane.id, + "SERVER_SOCKET_NAME": server.socket_name, + } cli_args = cli_cmd + [cli_arg.format(**template_ctx) for cli_arg in cli_args] @@ -191,11 +192,11 @@ def test_shell_target_missing( assert session.name is not None template_ctx.update( - dict( - SOCKET_NAME=server.socket_name, - SESSION_NAME=session.name, - WINDOW_NAME=template_ctx.get("window_name", window_name), - ) + { + "SOCKET_NAME": server.socket_name, + "SESSION_NAME": session.name, + "WINDOW_NAME": template_ctx.get("window_name", window_name), + } ) cli_args = cli_cmd + [cli_arg.format(**template_ctx) for cli_arg in cli_args] @@ -265,13 +266,13 @@ def test_shell_interactive( assert window.attached_pane is not None - template_ctx = dict( - SOCKET_NAME=server.socket_name, - SESSION_NAME=session.name, - WINDOW_NAME=window_name, - PANE_ID=window.attached_pane.id, - SERVER_SOCKET_NAME=server.socket_name, - ) + template_ctx = { + "SOCKET_NAME": server.socket_name, + "SESSION_NAME": session.name, + "WINDOW_NAME": window_name, + "PANE_ID": window.attached_pane.id, + "SERVER_SOCKET_NAME": server.socket_name, + } cli_args = cli_cmd + [cli_arg.format(**template_ctx) for cli_arg in cli_args] @@ -280,9 +281,8 @@ def test_shell_interactive( monkeypatch.chdir(tmp_path) monkeypatch.setattr("sys.stdin", io.StringIO("exit()\r")) - try: + with contextlib.suppress(SystemExit): cli.cli(cli_args) - except SystemExit: - pass + result = capsys.readouterr() assert message.format(**template_ctx) in result.err diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py index a81b5f2914d..eb018c3ff09 100644 --- a/tests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py @@ -1 +1 @@ -from . import utils # noqa +from . import utils diff --git a/tests/fixtures/import_teamocil/__init__.py b/tests/fixtures/import_teamocil/__init__.py index 7fe671395a8..6d5ce91af9e 100644 --- a/tests/fixtures/import_teamocil/__init__.py +++ b/tests/fixtures/import_teamocil/__init__.py @@ -1 +1 @@ -from . import layouts, test1, test2, test3, test4 # noqa +from . import layouts, test1, test2, test3, test4 diff --git a/tests/fixtures/import_tmuxinator/__init__.py b/tests/fixtures/import_tmuxinator/__init__.py index bbdbf699ad0..91bc1d0f43e 100644 --- a/tests/fixtures/import_tmuxinator/__init__.py +++ b/tests/fixtures/import_tmuxinator/__init__.py @@ -1 +1 @@ -from . import test1, test2, test3 # noqa +from . import test1, test2, test3 diff --git a/tests/fixtures/utils.py b/tests/fixtures/utils.py index d6eb9cbbac4..858227c5b3a 100644 --- a/tests/fixtures/utils.py +++ b/tests/fixtures/utils.py @@ -10,13 +10,16 @@ def get_workspace_file( """Return fixture data, relative to __file__""" if isinstance(_file, str): _file = pathlib.Path(_file) + return FIXTURE_PATH / _file def read_workspace_file(_file: t.Union[pathlib.Path, str]) -> str: """Return fixture data, relative to __file__""" + if isinstance(_file, str): + _file = pathlib.Path(_file) - return open(get_workspace_file(_file)).read() + return get_workspace_file(_file).open().read() def write_config( diff --git a/tests/fixtures/workspace/__init__.py b/tests/fixtures/workspace/__init__.py index 11692230916..097409c1569 100644 --- a/tests/fixtures/workspace/__init__.py +++ b/tests/fixtures/workspace/__init__.py @@ -1,4 +1,4 @@ -from . import ( # noqa +from . import ( expand1, expand2, expand_blank, diff --git a/tests/fixtures/workspace/expand1.py b/tests/fixtures/workspace/expand1.py index 4cd6c401aad..fa898a06b14 100644 --- a/tests/fixtures/workspace/expand1.py +++ b/tests/fixtures/workspace/expand1.py @@ -1,4 +1,4 @@ -import os +import pathlib before_workspace = { "session_name": "sample workspace", @@ -33,7 +33,7 @@ def after_workspace(): return { "session_name": "sample workspace", - "start_directory": os.path.expanduser("~"), + "start_directory": str(pathlib.Path().home()), "windows": [ { "window_name": "editor", @@ -57,21 +57,15 @@ def after_workspace(): ], }, { - "start_directory": os.path.normpath( - os.path.join(os.path.expanduser("~"), "./") - ), + "start_directory": str(pathlib.Path().home()), "panes": [{"shell_command": [{"cmd": "pwd"}]}], }, { - "start_directory": os.path.normpath( - os.path.join(os.path.expanduser("~"), "./asdf") - ), + "start_directory": str(pathlib.Path().home() / "asdf"), "panes": [{"shell_command": [{"cmd": "pwd"}]}], }, { - "start_directory": os.path.normpath( - os.path.join(os.path.expanduser("~"), "../") - ), + "start_directory": str(pathlib.Path().home().parent.resolve()), "panes": [{"shell_command": [{"cmd": "pwd"}]}], }, {"panes": [{"shell_command": [{"cmd": "top"}]}]}, diff --git a/tests/fixtures/workspace/expand2.py b/tests/fixtures/workspace/expand2.py index 3aa5bd3aa67..21f30403748 100644 --- a/tests/fixtures/workspace/expand2.py +++ b/tests/fixtures/workspace/expand2.py @@ -1,4 +1,4 @@ -import os +import pathlib from .. import utils as test_utils @@ -9,5 +9,5 @@ def unexpanded_yaml(): def expanded_yaml(): return test_utils.read_workspace_file("workspace/expand2-expanded.yaml").format( - HOME=os.path.expanduser("~") + HOME=str(pathlib.Path().home()) ) diff --git a/tests/fixtures/workspace/shell_command_before.py b/tests/fixtures/workspace/shell_command_before.py index a952a3db543..69f5c2f2b4e 100644 --- a/tests/fixtures/workspace/shell_command_before.py +++ b/tests/fixtures/workspace/shell_command_before.py @@ -1,5 +1,5 @@ -import os -from typing import Any, Dict +import pathlib +import typing as t config_unexpanded = { # shell_command_before is string in some areas "session_name": "sample workspace", @@ -38,14 +38,14 @@ } -def config_expanded() -> Dict[str, Any]: +def config_expanded() -> t.Dict[str, t.Any]: return { # shell_command_before is string in some areas "session_name": "sample workspace", "start_directory": "/", "windows": [ { "window_name": "editor", - "start_directory": os.path.expanduser("~"), + "start_directory": str(pathlib.Path().home()), "shell_command_before": { "shell_command": [{"cmd": "source .venv/bin/activate"}] }, @@ -90,14 +90,14 @@ def config_expanded() -> Dict[str, Any]: } -def config_after() -> Dict[str, Any]: +def config_after() -> t.Dict[str, t.Any]: return { # shell_command_before is string in some areas "session_name": "sample workspace", "start_directory": "/", "windows": [ { "window_name": "editor", - "start_directory": os.path.expanduser("~"), + "start_directory": str(pathlib.Path().home()), "shell_command_before": { "shell_command": [{"cmd": "source .venv/bin/activate"}] }, diff --git a/tests/tests/test_helpers.py b/tests/tests/test_helpers.py index 0cb9ba21b83..5928f73937a 100644 --- a/tests/tests/test_helpers.py +++ b/tests/tests/test_helpers.py @@ -1,6 +1,5 @@ """Tests for .'s helper and utility functions.""" import pytest - from libtmux.server import Server from libtmux.test import get_test_session_name, temp_session diff --git a/tests/workspace/test_builder.py b/tests/workspace/test_builder.py index 30d0e85ade4..111ff8b440c 100644 --- a/tests/workspace/test_builder.py +++ b/tests/workspace/test_builder.py @@ -1,18 +1,21 @@ """Test for tmuxp workspacebuilder.""" +import functools import os import pathlib import textwrap import time import typing as t -import pytest - import libtmux +import pytest +from libtmux._internal.query_list import ObjectDoesNotExist from libtmux.common import has_gte_version, has_lt_version +from libtmux.exc import LibTmuxException from libtmux.pane import Pane from libtmux.session import Session from libtmux.test import retry_until, temp_session from libtmux.window import Window + from tmuxp import exc from tmuxp.cli.load import load_plugins from tmuxp.config_reader import ConfigReader @@ -24,6 +27,11 @@ if t.TYPE_CHECKING: from libtmux.server import Server + from typing_extensions import Protocol + + class AssertCallbackProtocol(Protocol): + def __call__(self, cmd: str, hist: str) -> bool: + ... def test_split_windows(session): @@ -87,15 +95,10 @@ def test_focus_pane_index(session): assert isinstance(_pane_base_index, int) pane_base_index = int(_pane_base_index) - if not pane_base_index: - pane_base_index = 0 - else: - pane_base_index = int(pane_base_index) + pane_base_index = 0 if not pane_base_index else int(pane_base_index) # get the pane index for each pane - pane_base_indexes = [] - for pane in session.attached_window.panes: - pane_base_indexes.append(int(pane.index)) + pane_base_indexes = [int(pane.index) for pane in session.attached_window.panes] pane_indexes_should_be = [pane_base_index + x for x in range(0, 3)] assert pane_indexes_should_be == pane_base_indexes @@ -182,7 +185,7 @@ def assertIsMissing(cmd, hist): buffer_name = "test" sent_cmd = None - def f(): + def f(p: Pane, buffer_name: str, assertCase: AssertCallbackProtocol) -> bool: # from v0.7.4 libtmux session.cmd adds target -t self.id by default # show-buffer doesn't accept -t, use global cmd. @@ -198,7 +201,9 @@ def f(): return assertCase(sent_cmd, history_cmd) - assert retry_until(f), f"Unknown sent command: [{sent_cmd}] in {assertCase}" + _f = functools.partial(f, p=p, buffer_name=buffer_name, assertCase=assertCase) + + assert retry_until(_f), f"Unknown sent command: [{sent_cmd}] in {assertCase}" def test_session_options(session): @@ -231,7 +236,7 @@ def test_global_options(session): _status_position = session.show_option("status-position", _global=True) assert isinstance(_status_position, str) assert "top" in _status_position - assert 493 == session.show_option("repeat-time", _global=True) + assert session.show_option("repeat-time", _global=True) == 493 def test_global_session_env_options(session, monkeypatch): @@ -338,10 +343,12 @@ def test_window_shell(session): if "window_shell" in wconf: assert wconf["window_shell"] == "top" - def f(): + def f(w: Window) -> bool: return w.window_name != "top" - retry_until(f) + _f = functools.partial(f, w=w) + + retry_until(_f) assert w.name != "top" @@ -530,12 +537,16 @@ def test_start_directory(session, tmp_path: pathlib.Path): for path, window in zip(dirs, session.windows): for p in window.panes: - def f(): + def f(path: str, p: Pane) -> bool: pane_path = p.pane_current_path - return path in pane_path or pane_path == path + return ( + pane_path is not None and path in pane_path + ) or pane_path == path + + _f = functools.partial(f, path=path, p=p) # handle case with OS X adding /private/ to /tmp/ paths - assert retry_until(f) + assert retry_until(_f) def test_start_directory_relative(session, tmp_path: pathlib.Path): @@ -567,8 +578,8 @@ def test_start_directory_relative(session, tmp_path: pathlib.Path): workspace = loader.trickle(workspace) - assert os.path.exists(config_dir) - assert os.path.exists(test_dir) + assert config_dir.exists() + assert test_dir.exists() builder = WorkspaceBuilder(session_config=workspace, server=session.server) builder.build(session=session) @@ -579,12 +590,16 @@ def test_start_directory_relative(session, tmp_path: pathlib.Path): for path, window in zip(dirs, session.windows): for p in window.panes: - def f(): - # Handle case where directories resolve to /private/ in OSX + def f(path: str, p: Pane) -> bool: pane_path = p.pane_current_path - return path in pane_path or pane_path == path + return ( + pane_path is not None and path in pane_path + ) or pane_path == path - assert retry_until(f) + _f = functools.partial(f, path=path, p=p) + + # handle case with OS X adding /private/ to /tmp/ paths + assert retry_until(_f) @pytest.mark.skipif( @@ -603,7 +618,7 @@ def test_start_directory_sets_session_path(server): builder.build() session = builder.session - expected = "{0}|/usr".format(session.id) + expected = f"{session.id}|/usr" cmd = server.cmd("list-sessions", "-F", "#{session_id}|#{session_path}") assert expected in cmd.stdout @@ -616,14 +631,14 @@ def test_pane_order(session): """ yaml_workspace = test_utils.read_workspace_file( "workspace/builder/pane_ordering.yaml" - ).format(HOME=os.path.realpath(os.path.expanduser("~"))) + ).format(HOME=str(pathlib.Path().home().resolve())) # test order of `panes` (and pane_index) above against pane_dirs pane_paths = [ "/usr/bin", "/usr", "/etc", - os.path.realpath(os.path.expanduser("~")), + str(pathlib.Path().home().resolve()), ] workspace = ConfigReader._load(format="yaml", content=yaml_workspace) @@ -636,7 +651,7 @@ def test_pane_order(session): assert len(session.windows) == window_count for w, wconf in builder.iter_create_windows(session): - for p in builder.iter_create_panes(w, wconf): + for _ in builder.iter_create_panes(w, wconf): w.select_layout("tiled") # fix glitch with pane size assert len(session.windows) == window_count @@ -654,11 +669,13 @@ def test_pane_order(session): # at 0 since python list. pane_path = pane_paths[p_index - pane_base_index] - def f(): + def f(pane_path: str, p: Pane): p.refresh() return p.pane_current_path == pane_path - assert retry_until(f) + _f = functools.partial(f, pane_path=pane_path, p=p) + + assert retry_until(_f) def test_window_index(session): @@ -1268,12 +1285,14 @@ def test_first_pane_start_directory(session, tmp_path: pathlib.Path): window = session.windows[0] for path, p in zip(dirs, window.panes): - def f(): + def f(path: str, p: Pane) -> bool: pane_path = p.pane_current_path - return path in pane_path or pane_path == path + return (pane_path is not None and path in pane_path) or pane_path == path + + _f = functools.partial(f, path=path, p=p) # handle case with OS X adding /private/ to /tmp/ paths - assert retry_until(f) + assert retry_until(_f) @pytest.mark.skipif( @@ -1384,7 +1403,14 @@ def test_issue_800_default_size_many_windows( builder = WorkspaceBuilder(session_config=workspace, server=server) if raises: - with pytest.raises(Exception): + with pytest.raises( + ( + LibTmuxException, + exc.TmuxpException, + exc.EmptyWorkspaceException, + ObjectDoesNotExist, + ) + ): builder.build() assert builder is not None diff --git a/tests/workspace/test_config.py b/tests/workspace/test_config.py index 4bc142b9ecb..436175d4d79 100644 --- a/tests/workspace/test_config.py +++ b/tests/workspace/test_config.py @@ -1,5 +1,4 @@ """Test for tmuxp configuration import, inlining, expanding and export.""" -import os import pathlib import typing as t @@ -44,14 +43,15 @@ def test_export_json(tmp_path: pathlib.Path, config_fixture: "WorkspaceTestData" # There's no tests for this # def test_find_workspace_file(tmp_path: pathlib.Path): - configs = [] + configs = [str(tmp_path / x) for x in tmp_path.rglob("*.[json][ini][yaml]")] garbage_file = tmp_path / "config.psd" garbage_file.write_text("wat", encoding="utf-8") - for r, d, f in os.walk(str(tmp_path)): - for filela in (x for x in f if x.endswith((".json", ".ini", "yaml"))): - configs.append(str(tmp_path / filela)) + # for _r, _d, f in os.walk(str(tmp_path)): + # configs.extend( + # [str(tmp_path / x) for x in f if x.endswith((".json", ".ini", "yaml"))] + # ) files = 0 config_json = tmp_path / "config.json" diff --git a/tests/workspace/test_finder.py b/tests/workspace/test_finder.py index 1d64505a4a5..cdb33e7323a 100644 --- a/tests/workspace/test_finder.py +++ b/tests/workspace/test_finder.py @@ -53,8 +53,8 @@ def test_get_configs_cwd( confdir.mkdir() monkeypatch.chdir(confdir) - config1 = open(".tmuxp.json", "w+b") - config1.close() + with pathlib.Path(".tmuxp.json").open("w+b") as config1: + config1.close() configs_found = in_cwd() assert len(configs_found) == 1 @@ -153,13 +153,13 @@ def test_resolve_dot( assert find_workspace_file("myconfig") == str(user_config) assert find_workspace_file("~/.tmuxp/myconfig.yaml") == str(user_config) - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file(".tmuxp.json") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file(".tmuxp.ini") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("../") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("mooooooo") monkeypatch.chdir(homedir) @@ -176,15 +176,15 @@ def test_resolve_dot( assert find_workspace_file("myconfig") == str(user_config) assert find_workspace_file("~/.tmuxp/myconfig.yaml") == str(user_config) - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file(".") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file(".tmuxp.yaml") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("../") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("mooooooo") monkeypatch.chdir(configdir) @@ -198,15 +198,15 @@ def test_resolve_dot( assert find_workspace_file("myconfig") == str(user_config) assert find_workspace_file("~/.tmuxp/myconfig.yaml") == str(user_config) - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file(".") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file(".tmuxp.yaml") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("../") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("mooooooo") monkeypatch.chdir(tmp_path) @@ -223,15 +223,15 @@ def test_resolve_dot( assert find_workspace_file("myconfig") == str(user_config) assert find_workspace_file("~/.tmuxp/myconfig.yaml") == str(user_config) - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file(".") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file(".tmuxp.yaml") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("../") - with pytest.raises(Exception): + with pytest.raises(FileNotFoundError): find_workspace_file("mooooooo")