diff --git a/docs/changelog.rst b/docs/changelog.rst index 9027c45a5..e56dcbb06 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -40,6 +40,9 @@ To update |kitty|, :doc:`follow the instructions `. - hints kitten: Fix a regression that caused using the default open program to trigger open actions instead of running the program (:iss:`3968`) +- Allow deleting environment variables in :opt:`env` by specifying + just the variable name, without a value + 0.23.1 [2021-08-17] ---------------------- diff --git a/kitty/child.py b/kitty/child.py index 28d483326..835658b89 100644 --- a/kitty/child.py +++ b/kitty/child.py @@ -13,7 +13,7 @@ from typing import ( import kitty.fast_data_types as fast_data_types -from .constants import is_macos, shell_path, terminfo_dir +from .constants import is_macos, shell_path, terminfo_dir, delete_env_var from .types import run_once try: @@ -232,6 +232,7 @@ class Child: from .shell_integration import get_supported_shell_name if get_supported_shell_name(self.argv[0]): env['KITTY_SHELL_INTEGRATION'] = opts.shell_integration + env = {k: v for k, v in env.items() if v is not delete_env_var} return env def fork(self) -> Optional[int]: diff --git a/kitty/constants.py b/kitty/constants.py index 157bd88a7..1c12144a9 100644 --- a/kitty/constants.py +++ b/kitty/constants.py @@ -21,10 +21,18 @@ class Version(NamedTuple): patch: int +class SentinelString(str): + + def __new__(cls, val: str) -> 'SentinelString': + ans: SentinelString = str.__new__(cls, val) + return ans + + appname: str = 'kitty' kitty_face = '🐱' version: Version = Version(0, 23, 1) str_version: str = '.'.join(map(str, version)) +delete_env_var = SentinelString('_delete_this_env_var_') _plat = sys.platform.lower() is_macos: bool = 'darwin' in _plat if getattr(sys, 'frozen', False): diff --git a/kitty/launch.py b/kitty/launch.py index 0de391af9..e22c5e4f9 100644 --- a/kitty/launch.py +++ b/kitty/launch.py @@ -17,6 +17,7 @@ from .tabs import Tab from .types import run_once from .utils import find_exe, read_shell_environment, set_primary_selection from .window import Watchers, Window +from .options.utils import env as parse_env try: from typing import TypedDict @@ -69,8 +70,9 @@ The working directory for the newly launched child. Use the special value --env type=list Environment variables to set in the child process. Can be specified multiple -times to set different environment variables. -Syntax: :italic:`name=value`. +times to set different environment variables. Syntax: :code:`name=value`. +Using :code:`name=` will set to empty string and just :code:`name` will +remove the environment variable. --copy-colors @@ -193,9 +195,8 @@ def get_env(opts: LaunchCLIOptions, active_child: Child) -> Dict[str, str]: if opts.copy_env and active_child: env.update(active_child.foreground_environ) for x in opts.env: - parts = x.split('=', 1) - if len(parts) == 2: - env[parts[0]] = parts[1] + for k, v in parse_env(x, env): + env[k] = v return env diff --git a/kitty/options/definition.py b/kitty/options/definition.py index ac95f1983..47fe0801a 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -2518,7 +2518,9 @@ environment variables are expanded recursively, so if you use:: env MYVAR1=a env MYVAR2=${MYVAR1}/${HOME}/b -The value of MYVAR2 will be :code:`a//b`. +The value of MYVAR2 will be :code:`a//b`. Using +:code:`VAR=` will set it to the empty string and using just :code:`VAR` +will delete the variable from the child process' environment. ''' ) diff --git a/kitty/options/utils.py b/kitty/options/utils.py index 6e7f0b64d..381b3abca 100644 --- a/kitty/options/utils.py +++ b/kitty/options/utils.py @@ -16,7 +16,7 @@ from kitty.conf.utils import ( KeyAction, key_func, positive_float, positive_int, python_string, to_bool, to_cmdline, to_color, uniq, unit_float ) -from kitty.constants import config_dir, is_macos +from kitty.constants import config_dir, is_macos, delete_env_var from kitty.fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE from kitty.fonts import FontFeature from kitty.key_names import ( @@ -731,10 +731,17 @@ def font_features(val: str) -> Iterable[Tuple[str, Tuple[FontFeature, ...]]]: def env(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, str]]: - key, val = val.partition('=')[::2] - key, val = key.strip(), val.strip() - if key: - yield key, expandvars(val, current_val) + val = val.strip() + if val: + if '=' in val: + key, v = val.split('=', 1) + key, v = key.strip(), v.strip() + if key: + if v: + v = expandvars(v, current_val) + yield key, v + else: + yield val, delete_env_var def kitten_alias(val: str) -> Iterable[Tuple[str, List[str]]]: diff --git a/kitty_tests/options.py b/kitty_tests/options.py index 94cd7b95d..0e2909968 100644 --- a/kitty_tests/options.py +++ b/kitty_tests/options.py @@ -5,6 +5,7 @@ from . import BaseTest from kitty.utils import log_error +from kitty.constants import delete_env_var class TestConfParsing(BaseTest): @@ -48,8 +49,8 @@ class TestConfParsing(BaseTest): self.assertFalse(bad_lines) opts = p('pointer_shape_when_grabbed XXX', bad_line_num=1) self.ae(opts.pointer_shape_when_grabbed, defaults.pointer_shape_when_grabbed) - opts = p('env A=1', 'env B=x$A', 'clear_all_shortcuts y', 'kitten_alias a b --moo', 'map f1 kitten a') - self.ae(opts.env, {'A': '1', 'B': 'x1'}) + opts = p('env A=1', 'env B=x$A', 'env C=', 'env D', 'clear_all_shortcuts y', 'kitten_alias a b --moo', 'map f1 kitten a') + self.ae(opts.env, {'A': '1', 'B': 'x1', 'C': '', 'D': delete_env_var}) ka = tuple(opts.keymap.values())[0] self.ae(ka.args, ('b', '--moo')) opts = p('kitty_mod alt')