ExecContext: Make mode:xxx an enum

This commit introduces two new types, Input and InputMode. They
replace the more error-prone special strings cmd_input could be used
with. InputMode is an Enum, and Input can be either IntputMode, a
string or None.

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2026-03-23 12:39:57 +01:00
commit 0b05eb4c37

View file

@ -3,7 +3,8 @@
from __future__ import annotations from __future__ import annotations
import abc, re, sys import abc, re, sys
from typing import NamedTuple, TYPE_CHECKING from enum import Enum, auto
from typing import NamedTuple, TypeAlias, TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Self, Type from typing import Self, Type
@ -12,6 +13,14 @@ if TYPE_CHECKING:
from .log import * from .log import *
from .util import pretty_cmd from .util import pretty_cmd
class InputMode(Enum):
Interactive = auto()
NonInteractive = auto()
OptInteractive = auto()
Auto = auto()
Input: TypeAlias = InputMode | None | str
class Result(NamedTuple): class Result(NamedTuple):
stdout: str|None stdout: str|None
@ -34,7 +43,7 @@ class ExecContext(abc.ABC):
parent: ExecContext, parent: ExecContext,
title: str, title: str,
cmd: list[str], cmd: list[str],
cmd_input: str|None, cmd_input: Input,
wd: str|None, wd: str|None,
log_prefix: str, log_prefix: str,
throw: bool, throw: bool,
@ -53,13 +62,13 @@ class ExecContext(abc.ABC):
# or False # or False
interactive: bool|None = None interactive: bool|None = None
match cmd_input: match cmd_input:
case 'mode:interactive': case InputMode.Interactive:
interactive = True interactive = True
case 'mode:non-interactive': case InputMode.NonInteractive:
interactive = False interactive = False
case 'mode:opt-interactive': case InputMode.OptInteractive:
interactive = parent.interactive interactive = parent.interactive
case 'mode:auto': case InputMode.Auto:
interactive = sys.stdin.isatty() interactive = sys.stdin.isatty()
if interactive is None: if interactive is None:
interactive = parent.interactive interactive = parent.interactive
@ -68,7 +77,7 @@ class ExecContext(abc.ABC):
assert interactive in [ True, False ] assert interactive in [ True, False ]
self.__interactive = interactive self.__interactive = interactive
self.__cmd_input = cmd_input if cmd_input[:5] != 'mode:' else None self.__cmd_input = cmd_input if not isinstance(cmd_input, InputMode) else None
self.__throw = throw self.__throw = throw
self.__verbose = verbose if verbose is not None else parent.verbose_default self.__verbose = verbose if verbose is not None else parent.verbose_default
self.__pretty_cmd: str|None = None self.__pretty_cmd: str|None = None
@ -97,6 +106,10 @@ class ExecContext(abc.ABC):
def verbose(self) -> bool: def verbose(self) -> bool:
return self.__verbose return self.__verbose
@property
def cmd_input(self) -> bool:
return self.__cmd_input
@property @property
def pretty_cmd(self) -> str: def pretty_cmd(self) -> str:
if self.__pretty_cmd is None: if self.__pretty_cmd is None:
@ -159,7 +172,7 @@ class ExecContext(abc.ABC):
wd: str|None = None, wd: str|None = None,
throw: bool = True, throw: bool = True,
verbose: bool|None = None, verbose: bool|None = None,
cmd_input: str|None = 'mode:opt-interactive', cmd_input: Input = InputMode.OptInteractive,
env: dict[str, str]|None = None, env: dict[str, str]|None = None,
title: str=None title: str=None
) -> Result: ) -> Result:
@ -172,11 +185,11 @@ class ExecContext(abc.ABC):
throw: Raise an exception on non-zero exit status if True throw: Raise an exception on non-zero exit status if True
verbose: Emit log output while the command runs verbose: Emit log output while the command runs
cmd_input: cmd_input:
- "mode:opt-interactive" -> Let --interactive govern how to handle interactivity (default) - "InputMode.OptInteractive" -> Let --interactive govern how to handle interactivity (default)
- "mode:interactive" -> Inherit terminal stdin - "InputMode.Interactive" -> Inherit terminal stdin
- "mode:auto" -> Inherit terminal stdin if it is a TTY - "InputMode.Auto" -> Inherit terminal stdin if it is a TTY
- "mode:non-interactive" -> stdin from /dev/null - "InputMode.NonInteractive" -> stdin from /dev/null
- None -> Alias for mode:non-interactive - None -> Alias for InputMode.NonInteractive
- otherwise -> Feed cmd_input to stdin - otherwise -> Feed cmd_input to stdin
env: The environment the command should be run in env: The environment the command should be run in
@ -193,7 +206,7 @@ class ExecContext(abc.ABC):
cmd=cmd, cmd=cmd,
wd=wd, wd=wd,
verbose=cc.verbose, verbose=cc.verbose,
cmd_input=cmd_input, cmd_input=cc.cmd_input,
env=env, env=env,
interactive=cc.interactive, interactive=cc.interactive,
log_prefix = cc.log_prefix log_prefix = cc.log_prefix
@ -230,7 +243,7 @@ class ExecContext(abc.ABC):
opts=opts, opts=opts,
wd=wd, wd=wd,
verbose=cc.verbose, verbose=cc.verbose,
cmd_input=cmd_input, cmd_input=cc.cmd_input,
env=env, env=env,
interactive=cc.interactive, interactive=cc.interactive,
log_prefix = cc.log_prefix, log_prefix = cc.log_prefix,