diff --git a/kitty/conf/utils.py b/kitty/conf/utils.py index 9bc0134e5..2996fa60f 100644 --- a/kitty/conf/utils.py +++ b/kitty/conf/utils.py @@ -11,7 +11,7 @@ from typing import ( ) from ..rgb import Color, to_color as as_color -from ..utils import log_error +from ..utils import log_error, expandvars key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$') T = TypeVar('T') @@ -53,13 +53,36 @@ def to_bool(x: str) -> bool: return x.lower() in ('y', 'yes', 'true') -def to_cmdline(x: str) -> List[str]: - return list( - map( - lambda y: os.path.expandvars(os.path.expanduser(y)), - shlex.split(x) +class ToCmdline: + + def __init__(self) -> None: + self.override_env: Optional[Dict[str, str]] = None + + def __enter__(self) -> 'ToCmdline': + return self + + def __exit__(self, *a: Any) -> None: + self.override_env = None + + def filter_env_vars(self, *a: str) -> 'ToCmdline': + remove = frozenset(a) + self.override_env = {k: v for k, v in os.environ.items() if k not in remove} + return self + + def __call__(self, x: str) -> List[str]: + return list( + map( + lambda y: expandvars( + os.path.expanduser(y), + os.environ if self.override_env is None else self.override_env, + fallback_to_os_env=False + ), + shlex.split(x) + ) ) - ) + + +to_cmdline = ToCmdline() def python_string(text: str) -> str: diff --git a/kitty/config_data.py b/kitty/config_data.py index 512bb2ac3..d06a9f428 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -13,7 +13,7 @@ from typing import ( from . import fast_data_types as defines from .conf.definition import Option, Shortcut, option_func from .conf.utils import ( - choices, positive_float, positive_int, to_bool, to_cmdline, to_color, + choices, positive_float, positive_int, to_bool, to_cmdline as tc, to_color, to_color_or_none, unit_float ) from .constants import FloatEdges, config_dir, is_macos @@ -29,6 +29,10 @@ mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '⌘': 'SUPER', '⌥': 'ALT', 'OPTION': 'ALT', 'KITTY_MOD': 'KITTY'} +def to_cmdline(x: str) -> List[str]: + return tc(x) + + def parse_mods(parts: Iterable[str], sc: str) -> Optional[int]: def map_mod(m: str) -> str: diff --git a/kitty/utils.py b/kitty/utils.py index 03cb70935..28d31117b 100644 --- a/kitty/utils.py +++ b/kitty/utils.py @@ -14,7 +14,7 @@ from contextlib import suppress from functools import lru_cache from time import monotonic from typing import ( - Any, Callable, Dict, Generator, Iterable, List, Match, NamedTuple, + Any, Callable, Dict, Generator, Iterable, List, Mapping, Match, NamedTuple, Optional, Tuple, Union, cast ) @@ -28,18 +28,21 @@ from .typing import AddressFamily, PopenType, Socket, StartupCtx BASE = os.path.dirname(os.path.abspath(__file__)) -def expandvars(val: str, env: Dict[str, str] = {}) -> str: +def expandvars(val: str, env: Mapping[str, str] = {}, fallback_to_os_env: bool = True) -> str: def sub(m: Match) -> str: - key = m.group(1) + key = m.group(1) or m.group(2) result = env.get(key) - if result is None: + if result is None and fallback_to_os_env: result = os.environ.get(key) if result is None: result = m.group() return result - return re.sub(r'\$\{(\S+?)\}', sub, val) + if '$' not in val: + return val + + return re.sub(r'\$(?:(\w+)|\{([^}]+)\})', sub, val) def platform_window_id(os_window_id: int) -> Optional[int]: @@ -561,7 +564,7 @@ def read_shell_environment(opts: Optional[Options] = None) -> Dict[str, str]: def parse_uri_list(text: str) -> Generator[str, None, None]: ' Get paths from file:// URLs ' - from urllib.parse import urlparse, unquote + from urllib.parse import unquote, urlparse for line in text.splitlines(): if not line or line.startswith('#'): continue