Skip to content

Commit

Permalink
Make the effective configuration file for the kitty process available…
Browse files Browse the repository at this point in the history
… in the cache directory

This can be parsed by kittens to load effective settings, thereby making
things like --override and reloading of config also affect kittens that
read kitty config. Still to be implemented on the kitten side.
  • Loading branch information
kovidgoyal committed Jan 5, 2025
1 parent b0c9262 commit 76ebc59
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 29 deletions.
4 changes: 3 additions & 1 deletion kitty/boss.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
)
from .colors import ColorSchemes, theme_colors
from .conf.utils import BadLine, KeyAction, to_cmdline
from .config import common_opts_as_dict, prepare_config_file_for_editing
from .config import common_opts_as_dict, prepare_config_file_for_editing, store_effective_config
from .constants import (
RC_ENCRYPTION_PROTOCOL_VERSION,
appname,
Expand Down Expand Up @@ -398,6 +398,7 @@ def __init__(
set_boss(self)
self.mappings: Mappings = Mappings(global_shortcuts, self.refresh_active_tab_bar)
self.notification_manager: NotificationManager = NotificationManager(debug=self.args.debug_keyboard or self.args.debug_rendering)
self.atexit.unlink(store_effective_config())

def startup_first_child(self, os_window_id: Optional[int], startup_sessions: Iterable[Session] = ()) -> None:
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)
Expand Down Expand Up @@ -2730,6 +2731,7 @@ def load_config_file(self, *paths: str, apply_overrides: bool = True, overrides:
clear_caches()
from .guess_mime_type import clear_mime_cache
clear_mime_cache()
store_effective_config()

def safe_delete_temp_file(self, path: str) -> None:
if is_path_in_temp_dir(path):
Expand Down
13 changes: 9 additions & 4 deletions kitty/conf/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,15 +442,20 @@ def generate_c_conversion(loc: str, ctypes: List[Union[Option, MultiOption]]) ->

def write_output(loc: str, defn: Definition, extra_after_type_defn: str = '') -> None:
cls, tc = generate_class(defn, loc)
ctypes = []
has_secret = []
for opt in defn.root_group.iter_all_non_groups():
if isinstance(opt, (Option, MultiOption)) and opt.ctype:
ctypes.append(opt)
if getattr(opt, 'has_secret', False):
has_secret.append(opt.name)
with open(os.path.join(*loc.split('.'), 'options', 'types.py'), 'w') as f:
f.write(f'{cls}\n')
f.write(extra_after_type_defn)
if has_secret:
f.write('\n\nsecret_options = ' + repr(tuple(has_secret)))
with open(os.path.join(*loc.split('.'), 'options', 'parse.py'), 'w') as f:
f.write(f'{tc}\n')
ctypes = []
for opt in defn.root_group.iter_all_non_groups():
if isinstance(opt, (Option, MultiOption)) and opt.ctype:
ctypes.append(opt)
if ctypes:
c = generate_c_conversion(loc, ctypes)
with open(os.path.join(*loc.split('.'), 'options', 'to-c-generated.h'), 'w') as f:
Expand Down
13 changes: 8 additions & 5 deletions kitty/conf/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ class Option:

def __init__(
self, name: str, defval: str, macos_default: Union[Unset, str], parser_func: ParserFuncType,
long_text: str, documented: bool, group: 'Group', choices: Tuple[str, ...], ctype: str
long_text: str, documented: bool, group: 'Group', choices: Tuple[str, ...], ctype: str,
has_secret: bool = False,
):
self.name = name
self.ctype = ctype
Expand All @@ -231,6 +232,7 @@ def __init__(
self.group = group
self.parser_func = parser_func
self.choices = choices
self.has_secret = has_secret

@property
def needs_coalescing(self) -> bool:
Expand Down Expand Up @@ -292,12 +294,13 @@ def __init__(self, val_as_str: str, add_to_default: bool, documented: bool, only

class MultiOption:

def __init__(self, name: str, parser_func: ParserFuncType, long_text: str, group: 'Group', ctype: str):
def __init__(self, name: str, parser_func: ParserFuncType, long_text: str, group: 'Group', ctype: str, has_secret: bool = False):
self.name = name
self.ctype = ctype
self.parser_func = parser_func
self.long_text = long_text
self.group = group
self.has_secret = has_secret
self.items: List[MultiVal] = []

def add_value(self, val_as_str: str, add_to_default: bool, documented: bool, only: Only) -> None:
Expand Down Expand Up @@ -701,7 +704,7 @@ def add_option(
documented: bool = True, add_to_default: bool = False,
only: Only = '', macos_default: Union[Unset, str] = unset,
choices: Tuple[str, ...] = (),
ctype: str = '',
ctype: str = '', has_secret: bool = False,
) -> None:
if isinstance(defval, bool):
defval = 'yes' if defval else 'no'
Expand All @@ -715,13 +718,13 @@ def add_option(
raise TypeError(f'Cannot specify macos_default for is_multiple option: {name} use only instead')
is_new = name not in self.multi_option_map
if is_new:
self.multi_option_map[name] = MultiOption(name, self.parser_func(option_type), long_text, self.current_group, ctype)
self.multi_option_map[name] = MultiOption(name, self.parser_func(option_type), long_text, self.current_group, ctype, has_secret)
mopt = self.multi_option_map[name]
if is_new:
self.current_group.append(mopt)
mopt.add_value(defval, add_to_default, documented, only)
return
opt = Option(name, defval, macos_default, self.parser_func(option_type), long_text, documented, self.current_group, choices, ctype)
opt = Option(name, defval, macos_default, self.parser_func(option_type), long_text, documented, self.current_group, choices, ctype, has_secret)
self.current_group.append(opt)
self.option_map[name] = opt

Expand Down
27 changes: 15 additions & 12 deletions kitty/conf/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ def parse_line(
parse_conf_item: ItemParser,
ans: Dict[str, Any],
base_path_for_includes: str,
accumulate_bad_lines: Optional[List[BadLine]] = None
effective_config_lines: Callable[[str, str], None],
accumulate_bad_lines: Optional[List[BadLine]] = None,
) -> None:
line = line.strip()
if not line or line.startswith('#'):
Expand All @@ -225,9 +226,7 @@ def parse_line(
with currently_parsing.set_file(f'<env var: {x}>'):
_parse(
NamedLineIterator(os.path.join(base_path_for_includes, ''), iter(os.environ[x].splitlines())),
parse_conf_item,
ans,
accumulate_bad_lines
parse_conf_item, ans, accumulate_bad_lines, effective_config_lines,
)
return
else:
Expand All @@ -238,7 +237,7 @@ def parse_line(
try:
with open(val, encoding='utf-8', errors='replace') as include:
with currently_parsing.set_file(val):
_parse(include, parse_conf_item, ans, accumulate_bad_lines)
_parse(include, parse_conf_item, ans, accumulate_bad_lines, effective_config_lines)
except FileNotFoundError:
log_error(
'Could not find included config file: {}, ignoring'.
Expand All @@ -250,17 +249,22 @@ def parse_line(
format(val)
)
return
if not parse_conf_item(key, val, ans):
if parse_conf_item(key, val, ans):
effective_config_lines(key, line)
else:
log_error(f'Ignoring unknown config key: {key}')



def _parse(
lines: Iterable[str],
parse_conf_item: ItemParser,
ans: Dict[str, Any],
accumulate_bad_lines: Optional[List[BadLine]] = None
accumulate_bad_lines: Optional[List[BadLine]] = None,
effective_config_lines: Optional[Callable[[str, str], None]] = None,
) -> None:
name = getattr(lines, 'name', None)
effective_config_lines = effective_config_lines or (lambda a, b: None)
if name:
base_path_for_includes = os.path.abspath(name) if name.endswith(os.path.sep) else os.path.dirname(os.path.abspath(name))
else:
Expand Down Expand Up @@ -297,7 +301,7 @@ def _parse(
next_line = ''
try:
with currently_parsing.set_line(line, line_num):
parse_line(line, parse_conf_item, ans, base_path_for_includes, accumulate_bad_lines)
parse_line(line, parse_conf_item, ans, base_path_for_includes, effective_config_lines, accumulate_bad_lines)
except Exception as e:
if accumulate_bad_lines is None:
raise
Expand All @@ -310,11 +314,10 @@ def parse_config_base(
lines: Iterable[str],
parse_conf_item: ItemParser,
ans: Dict[str, Any],
accumulate_bad_lines: Optional[List[BadLine]] = None
accumulate_bad_lines: Optional[List[BadLine]] = None,
effective_config_lines: Optional[Callable[[str, str], None]] = None,
) -> None:
_parse(
lines, parse_conf_item, ans, accumulate_bad_lines
)
_parse(lines, parse_conf_item, ans, accumulate_bad_lines, effective_config_lines)


def merge_dicts(defaults: Dict[str, Any], newvals: Dict[str, Any]) -> Dict[str, Any]:
Expand Down
36 changes: 32 additions & 4 deletions kitty/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from collections.abc import Generator, Iterable
from contextlib import contextmanager, suppress
from functools import partial
from typing import Any, Optional
from typing import Any, Callable, Optional

from .conf.utils import BadLine, parse_config_base
from .conf.utils import load_config as _load_config
Expand Down Expand Up @@ -140,24 +140,37 @@ def finalize_mouse_mappings(opts: Options, accumulate_bad_lines: Optional[list[B
opts.mousemap = mousemap


def parse_config(lines: Iterable[str], accumulate_bad_lines: Optional[list[BadLine]] = None) -> dict[str, Any]:
def parse_config(
lines: Iterable[str], accumulate_bad_lines: Optional[list[BadLine]] = None, effective_config_lines: Optional[Callable[[str, str], None]] = None
) -> dict[str, Any]:
from .options.parse import create_result_dict, parse_conf_item
ans: dict[str, Any] = create_result_dict()
parse_config_base(
lines,
parse_conf_item,
ans,
accumulate_bad_lines=accumulate_bad_lines
accumulate_bad_lines=accumulate_bad_lines,
effective_config_lines=effective_config_lines,
)
return ans


effective_config_lines: list[str] = []


def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumulate_bad_lines: Optional[list[BadLine]] = None) -> Options:
from .options.parse import merge_result_dicts
from .options.types import secret_options
del effective_config_lines[:]

def add_effective_config_line(key: str, line: str) -> None:
if key not in secret_options:
effective_config_lines.append(line)

overrides = tuple(overrides) if overrides is not None else ()
opts_dict, found_paths = _load_config(
defaults, partial(parse_config, accumulate_bad_lines=accumulate_bad_lines), merge_result_dicts, *paths, overrides=overrides)
defaults, partial(parse_config, accumulate_bad_lines=accumulate_bad_lines, effective_config_lines=add_effective_config_line),
merge_result_dicts, *paths, overrides=overrides)
opts = Options(opts_dict)

opts.alias_map = build_action_aliases(opts.kitten_alias, 'kitten')
Expand All @@ -178,6 +191,21 @@ def load_config(*paths: str, overrides: Optional[Iterable[str]] = None, accumula
return opts


def store_effective_config() -> str:
import os
import stat
import tempfile
dest = os.path.join(cache_dir(), 'effective-config')
os.makedirs(dest, exist_ok=True)
raw = '\n'.join(effective_config_lines)
with suppress(FileNotFoundError), tempfile.NamedTemporaryFile('w', dir=dest) as tf:
os.chmod(tf.name, stat.S_IRUSR | stat.S_IWUSR)
print(raw, file=tf)
path = os.path.join(dest, f'{os.getpid()}')
os.replace(tf.name, path)
return path


class KittyCommonOpts(TypedDict):
select_by_word_characters: str
open_url_with: list[str]
Expand Down
5 changes: 2 additions & 3 deletions kitty/options/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -3002,7 +3002,7 @@

opt('+remote_control_password', '',
option_type='remote_control_password',
add_to_default=False,
add_to_default=False, has_secret=True,
long_text='''
Allow other programs to control kitty using passwords. This option can be
specified multiple times to add multiple passwords. If no passwords are present
Expand Down Expand Up @@ -3210,8 +3210,7 @@
'''
)

opt('file_transfer_confirmation_bypass', '',
long_text='''
opt('file_transfer_confirmation_bypass', '', has_secret=True, long_text='''
The password that can be supplied to the :doc:`file transfer kitten
</kittens/transfer>` to skip the transfer confirmation prompt. This should only
be used when initiating transfers from trusted computers, over trusted networks
Expand Down
3 changes: 3 additions & 0 deletions kitty/options/types.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 76ebc59

Please sign in to comment.