Start work on config infrastructure for ssh kitten

This commit is contained in:
Kovid Goyal 2022-02-25 15:14:10 +05:30
parent 5c8651c7cd
commit 846021296f
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
10 changed files with 211 additions and 3 deletions

View File

@ -38,6 +38,8 @@ def main() -> None:
from kittens.diff.options.definition import definition as kd
write_output('kittens.diff', kd)
from kittens.ssh.options.definition import definition as sd
write_output('kittens.ssh', sd)
if __name__ == '__main__':

View File

@ -93,7 +93,7 @@ def create_result_dict() -> typing.Dict[str, typing.Any]:
}
actions = frozenset(('map',))
actions: typing.FrozenSet[str] = frozenset(('map',))
def merge_result_dicts(defaults: typing.Dict[str, typing.Any], vals: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:

44
kittens/ssh/config.py Normal file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
import os
from typing import Any, Dict, Iterable, Optional
from kitty.conf.utils import (
load_config as _load_config, parse_config_base, resolve_config
)
from kitty.constants import config_dir
from .options.types import Options as SSHOptions, defaults
SYSTEM_CONF = '/etc/xdg/kitty/ssh.conf'
defconf = os.path.join(config_dir, 'ssh.conf')
def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> Dict[str, SSHOptions]:
from .options.parse import (
create_result_dict, merge_result_dicts, parse_conf_item
)
from .options.utils import init_results_dict
def parse_config(lines: Iterable[str]) -> Dict[str, Any]:
ans: Dict[str, Any] = init_results_dict(create_result_dict())
parse_config_base(lines, parse_conf_item, ans)
return ans
overrides = tuple(overrides) if overrides is not None else ()
opts_dict, paths = _load_config(defaults, parse_config, merge_result_dicts, *paths, overrides=overrides)
ans: Dict[str, SSHOptions] = {}
for hostname, host_opts_dict in opts_dict['per_host_dicts'].items():
opts = SSHOptions(host_opts_dict)
opts.config_paths = paths
opts.config_overrides = overrides
ans[hostname] = opts
return ans
def init_config() -> Dict[str, SSHOptions]:
config = tuple(resolve_config(SYSTEM_CONF, defconf))
opts_dict = load_config(*config)
return opts_dict

View File

View File

@ -0,0 +1,29 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
# After editing this file run ./gen-config.py to apply the changes
from kitty.conf.types import Definition
definition = Definition(
'kittens.ssh',
)
agr = definition.add_group
egr = definition.end_group
opt = definition.add_option
agr('global', 'Global') # {{{
opt('hostname', '*', option_type='hostname',
long_text='''
The hostname the following options apply to. A glob pattern to match multiple
hosts can be used. When not specified options apply to all hosts, until the
first hostname specification is found. Note that the hostname this matches
against is the hostname used by the remote computer, not the name you pass
to SSH to connect to it.
'''
)
egr() # }}}

View File

@ -0,0 +1,42 @@
# generated by gen-config.py DO NOT edit
import typing
from kittens.ssh.options.utils import hostname
from kitty.conf.utils import merge_dicts
class Parser:
def hostname(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
hostname(val, ans)
def create_result_dict() -> typing.Dict[str, typing.Any]:
return {
}
actions: typing.FrozenSet[str] = frozenset(())
def merge_result_dicts(defaults: typing.Dict[str, typing.Any], vals: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
ans = {}
for k, v in defaults.items():
if isinstance(v, dict):
ans[k] = merge_dicts(v, vals.get(k, {}))
elif k in actions:
ans[k] = v + vals.get(k, [])
else:
ans[k] = vals.get(k, v)
return ans
parser = Parser()
def parse_conf_item(key: str, val: str, ans: typing.Dict[str, typing.Any]) -> bool:
func = getattr(parser, key, None)
if func is not None:
func(val, ans)
return True
return False

View File

@ -0,0 +1,61 @@
# generated by gen-config.py DO NOT edit
import typing
option_names = ( # {{{
'hostname',) # }}}
class Options:
hostname: str = '*'
config_paths: typing.Tuple[str, ...] = ()
config_overrides: typing.Tuple[str, ...] = ()
def __init__(self, options_dict: typing.Optional[typing.Dict[str, typing.Any]] = None) -> None:
if options_dict is not None:
null = object()
for key in option_names:
val = options_dict.get(key, null)
if val is not null:
setattr(self, key, val)
@property
def _fields(self) -> typing.Tuple[str, ...]:
return option_names
def __iter__(self) -> typing.Iterator[str]:
return iter(self._fields)
def __len__(self) -> int:
return len(self._fields)
def _copy_of_val(self, name: str) -> typing.Any:
ans = getattr(self, name)
if isinstance(ans, dict):
ans = ans.copy()
elif isinstance(ans, list):
ans = ans[:]
return ans
def _asdict(self) -> typing.Dict[str, typing.Any]:
return {k: self._copy_of_val(k) for k in self}
def _replace(self, **kw: typing.Any) -> "Options":
ans = Options()
for name in self:
setattr(ans, name, self._copy_of_val(name))
for name, val in kw.items():
setattr(ans, name, val)
return ans
def __getitem__(self, key: typing.Union[int, str]) -> typing.Any:
k = option_names[key] if isinstance(key, int) else key
try:
return getattr(self, k)
except AttributeError:
pass
raise KeyError(f"No option named: {k}")
defaults = Options()

View File

@ -0,0 +1,30 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
from typing import Any, Dict, Optional
def init_results_dict(ans: Dict[str, Any]) -> Dict[str, Any]:
ans['current_hostname'] = '*'
ans['current_host_dict'] = chd = {'hostname': '*'}
ans['per_host_dicts'] = {'*': chd}
return ans
ignored_dict_keys = tuple(init_results_dict({}))
def hostname(val: str, dict_with_parse_results: Optional[Dict[str, Any]] = None) -> str:
if dict_with_parse_results is not None:
ch = dict_with_parse_results['current_hostname']
if val != ch:
hd = dict_with_parse_results.copy()
for k in ignored_dict_keys:
del hd[k]
phd = dict_with_parse_results['per_host_dicts']
phd[ch] = hd
dict_with_parse_results.clear()
dict_with_parse_results['per_host_dicts'] = phd
dict_with_parse_results['current_hostname'] = val
dict_with_parse_results['current_host_dict'] = phd.setdefault(val, {'hostname': val})
return val

View File

@ -306,7 +306,7 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
t('')
t('')
t(f'actions = frozenset({tuple(defn.actions)!r})')
t(f'actions: typing.FrozenSet[str] = frozenset({tuple(defn.actions)!r})')
t('')
t('')
t('def merge_result_dicts(defaults: typing.Dict[str, typing.Any], vals: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:')

View File

@ -1346,7 +1346,7 @@ def create_result_dict() -> typing.Dict[str, typing.Any]:
}
actions = frozenset(('map', 'mouse_map'))
actions: typing.FrozenSet[str] = frozenset(('map', 'mouse_map'))
def merge_result_dicts(defaults: typing.Dict[str, typing.Any], vals: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]: