1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
from __future__ import annotations
import abc
import sys
from typing import Dict, Optional, Type
from .config import Config
from .terminal import Terminal
from .trigger import Trigger
class Manager:
_registry: Dict[str, Command] = {}
@classmethod
def list_commands(cls):
return cls._registry.values()
@classmethod
def get_command(cls, character: str) -> Optional[Command]:
return cls._registry.get(character)
@classmethod
def register(cls, command: Type[Command]):
if command.character in cls._registry:
raise ValueError(f"Duplicate character {repr(command.character)}")
cls._registry[command.character] = command()
@classmethod
def run_command(
cls, character: str, trigger: Trigger, term: Terminal, config: Config
) -> None:
command = cls.get_command(character)
if command:
command.run(trigger, term, config)
class Command(abc.ABC):
character: str
caption: str
description: str
show_in_menu: bool = True
def __init_subclass__(cls, **kwargs) -> None:
for field in ("character", "caption", "description"):
if not hasattr(cls, field):
raise NotImplementedError(f"{cls.__name__}: {field} not specified")
super().__init_subclass__(**kwargs)
Manager.register(cls)
@abc.abstractmethod
def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
"""
Modify runner_args in-place if needed and return a bool indicating whether
tests should be triggered instantly
"""
class OpenMenuCommand(Command):
character = "w"
caption = "w"
description = "show menu"
show_in_menu = False
def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
term.clear()
term.print_menu(config.runner_args)
class InvokeCommand(Command):
character = "\n"
caption = "Enter"
description = "Invoke test runner"
def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
trigger.emit_now()
class ResetRunnerArgsCommand(Command):
character = "r"
caption = "r"
description = "reset all runner args"
def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
config.runner_args.clear()
trigger.emit_now()
class ChangeRunnerArgsCommand(Command):
character = "c"
caption = "c"
description = "change runner args"
def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
term.reset()
raw = input("\nEnter new runner args: ")
new_args = raw.strip().split()
config.runner_args.clear()
config.runner_args.extend(new_args)
trigger.emit_now()
class OnlyFailedCommand(Command):
character = "f"
caption = "f"
description = "run only failed tests (--lf)"
def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
if "--lf" not in config.runner_args:
config.runner_args.append("--lf")
trigger.emit_now()
class PDBCommand(Command):
character = "p"
caption = "p"
description = "drop to pdb on fail (--pdb)"
def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
if "--pdb" not in config.runner_args:
config.runner_args.append("--pdb")
trigger.emit_now()
class VerboseCommand(Command):
character = "v"
caption = "v"
description = "increase verbosity (-v)"
def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
if "-v" not in config.runner_args:
config.runner_args.append("-v")
trigger.emit_now()
class EraseScreenCommand(Command):
character = "e"
caption = "e"
description = "Erase terminal screen"
def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
term.clear()
term.print_short_menu(config.runner_args)
class QuitCommand(Command):
character = "q"
caption = "q"
description = "quit pytest-watcher"
def run(self, trigger: Trigger, term: Terminal, config: Config) -> None:
sys.exit(0)
|