Start work on porting docs generation

This commit is contained in:
Kovid Goyal 2021-05-31 13:29:29 +05:30
parent 3e598a17cf
commit 46b3f71b8f
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 145 additions and 101 deletions

View File

@ -32,10 +32,9 @@ kitty_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if kitty_src not in sys.path: if kitty_src not in sys.path:
sys.path.insert(0, kitty_src) sys.path.insert(0, kitty_src)
from kitty.conf.definition import Option, Shortcut, OptionOrAction, MouseAction # noqa from kitty.conf.types import Definition # noqa
from kitty.constants import str_version # noqa from kitty.constants import str_version # noqa
# config {{{ # config {{{
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
@ -248,8 +247,8 @@ def add_html_context(app: Any, pagename: str, templatename: str, context: Any, *
# CLI docs {{{ # CLI docs {{{
def write_cli_docs(all_kitten_names: Iterable[str]) -> None: def write_cli_docs(all_kitten_names: Iterable[str]) -> None:
from kitty.launch import options_spec as launch_options_spec
from kitty.cli import option_spec_as_rst from kitty.cli import option_spec_as_rst
from kitty.launch import options_spec as launch_options_spec
with open('generated/launch.rst', 'w') as f: with open('generated/launch.rst', 'w') as f:
f.write(option_spec_as_rst( f.write(option_spec_as_rst(
appname='launch', ospec=launch_options_spec, heading_char='_', appname='launch', ospec=launch_options_spec, heading_char='_',
@ -264,7 +263,7 @@ if you specify a program-to-run you can use the special placeholder
'kitty --to', 'kitty @ --to')) 'kitty --to', 'kitty @ --to'))
as_rst = partial(option_spec_as_rst, heading_char='_') as_rst = partial(option_spec_as_rst, heading_char='_')
from kitty.rc.base import all_command_names, command_for_name from kitty.rc.base import all_command_names, command_for_name
from kitty.remote_control import global_options_spec, cli_msg from kitty.remote_control import cli_msg, global_options_spec
with open('generated/cli-kitty-at.rst', 'w') as f: with open('generated/cli-kitty-at.rst', 'w') as f:
p = partial(print, file=f) p = partial(print, file=f)
p('kitty @\n' + '-' * 80) p('kitty @\n' + '-' * 80)
@ -293,7 +292,9 @@ if you specify a program-to-run you can use the special placeholder
def write_remote_control_protocol_docs() -> None: # {{{ def write_remote_control_protocol_docs() -> None: # {{{
from kitty.rc.base import all_command_names, command_for_name, RemoteCommand from kitty.rc.base import (
RemoteCommand, all_command_names, command_for_name
)
field_pat = re.compile(r'\s*([a-zA-Z0-9_+]+)\s*:\s*(.+)') field_pat = re.compile(r'\s*([a-zA-Z0-9_+]+)\s*:\s*(.+)')
def format_cmd(p: Callable, name: str, cmd: RemoteCommand) -> None: def format_cmd(p: Callable, name: str, cmd: RemoteCommand) -> None:
@ -447,87 +448,6 @@ def parse_shortcut_node(env: Any, sig: str, signode: Any) -> str:
return sig return sig
def render_conf(conf_name: str, all_options: Iterable[OptionOrAction]) -> str:
from kitty.conf.definition import merged_opts, Group
ans = ['.. default-domain:: conf', '']
a = ans.append
current_group: Optional[Group] = None
all_options_ = list(all_options)
kitty_mod = 'kitty_mod'
def render_group(group: Group) -> None:
a('')
a(f'.. _conf-{conf_name}-{group.name}:')
a('')
a(group.short_text)
heading_level = '+' if '.' in group.name else '^'
a(heading_level * (len(group.short_text) + 20))
a('')
if group.start_text:
a(group.start_text)
a('')
def handle_group_end(group: Group) -> None:
if group.end_text:
assert current_group is not None
a(''), a(current_group.end_text)
def handle_group(new_group: Group, new_group_is_shortcut: bool = False) -> None:
nonlocal current_group
if new_group is not current_group:
if current_group:
handle_group_end(current_group)
current_group = new_group
render_group(current_group)
def handle_option(i: int, opt: Option) -> None:
nonlocal kitty_mod
if not opt.long_text or not opt.add_to_docs:
return
handle_group(opt.group)
if opt.name == 'kitty_mod':
kitty_mod = opt.defval_as_string
mopts = list(merged_opts(all_options_, opt, i))
a('.. opt:: ' + ', '.join(conf_name + '.' + mo.name for mo in mopts))
a('.. code-block:: conf')
a('')
sz = max(len(x.name) for x in mopts)
for mo in mopts:
a((' {:%ds} {}' % sz).format(mo.name, mo.defval_as_string))
a('')
if opt.long_text:
a(expand_opt_references(conf_name, opt.long_text))
a('')
def handle_shortcuts(shortcuts: Sequence[Union[Shortcut, MouseAction]]) -> None:
sc = shortcuts[0]
handle_group(sc.group, True)
sc_text = f'{conf_name}.{sc.short_text}'
a('.. shortcut:: ' + sc_text)
shortcuts = [s for s in shortcuts if s.add_to_default]
shortcut_slugs[f'{conf_name}.{sc.name}'] = (sc_text, sc.key.replace('kitty_mod', kitty_mod))
if shortcuts:
a('.. code-block:: conf')
a('')
for x in shortcuts:
if x.add_to_default:
a(' ' + x.line.replace('kitty_mod', kitty_mod))
a('')
if sc.long_text:
a(expand_opt_references(conf_name, sc.long_text))
a('')
for i, opt in enumerate(all_options_):
if isinstance(opt, Option):
handle_option(i, opt)
else:
handle_shortcuts(opt)
if current_group:
handle_group_end(current_group)
return '\n'.join(ans)
def process_opt_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]: def process_opt_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
conf_name, opt = target.partition('.')[::2] conf_name, opt = target.partition('.')[::2]
if not opt: if not opt:
@ -572,25 +492,24 @@ def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
sc_role.warn_dangling = True sc_role.warn_dangling = True
sc_role.process_link = process_shortcut_link sc_role.process_link = process_shortcut_link
def generate_default_config(all_options: Dict[str, OptionOrAction], name: str) -> None: def generate_default_config(definition: Definition, name: str) -> None:
from kitty.conf.definition import as_conf_file
with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f: with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f:
print('.. highlight:: conf\n', file=f) print('.. highlight:: conf\n', file=f)
f.write(render_conf(name, all_options.values())) f.write('\n'.join(definition.as_rst(name, shortcut_slugs)))
conf_name = re.sub(r'^kitten-', '', name) + '.conf' conf_name = re.sub(r'^kitten-', '', name) + '.conf'
with open(f'generated/conf/{conf_name}', 'w', encoding='utf-8') as f: with open(f'generated/conf/{conf_name}', 'w', encoding='utf-8') as f:
text = '\n'.join(as_conf_file(all_options.values())) text = '\n'.join(definition.as_conf())
print(text, file=f) print(text, file=f)
from kitty.config_data import all_options from kitty.options.definition import definition
generate_default_config(all_options, 'kitty') generate_default_config(definition, 'kitty')
from kittens.runner import get_kitten_conf_docs from kittens.runner import get_kitten_conf_docs
for kitten in all_kitten_names: for kitten in all_kitten_names:
all_options = get_kitten_conf_docs(kitten) definition = get_kitten_conf_docs(kitten)
if all_options: if definition:
generate_default_config(all_options, f'kitten-{kitten}') generate_default_config(definition, f'kitten-{kitten}')
# }}} # }}}

View File

@ -7,11 +7,15 @@ import importlib
import os import os
import sys import sys
from functools import partial from functools import partial
from typing import Any, Dict, FrozenSet, List from typing import Any, Dict, FrozenSet, List, TYPE_CHECKING, cast
from kitty.types import run_once from kitty.types import run_once
aliases = {'url_hints': 'hints'} aliases = {'url_hints': 'hints'}
if TYPE_CHECKING:
from kitty.conf.types import Definition
else:
Definition = object
def resolved_kitten(k: str) -> str: def resolved_kitten(k: str) -> str:
@ -151,12 +155,12 @@ def get_kitten_cli_docs(kitten: str) -> Any:
return ans return ans
def get_kitten_conf_docs(kitten: str) -> Any: def get_kitten_conf_docs(kitten: str) -> Definition:
setattr(sys, 'all_options', None) setattr(sys, 'options_definition', None)
run_kitten(kitten, run_name='__conf__') run_kitten(kitten, run_name='__conf__')
ans = getattr(sys, 'all_options') ans = getattr(sys, 'options_definition')
delattr(sys, 'all_options') delattr(sys, 'options_definition')
return ans return cast(Definition, ans)
def main() -> None: def main() -> None:

View File

@ -27,6 +27,19 @@ class Unset:
unset = Unset() unset = Unset()
def expand_opt_references(conf_name: str, text: str) -> str:
conf_name += '.'
def expand(m: Match) -> str:
ref = m.group(1)
if '<' not in ref and '.' not in ref:
full_ref = conf_name + ref
return ':opt:`{} <{}>`'.format(ref, full_ref)
return str(m.group())
return re.sub(r':opt:`(.+?)`', expand, text)
def remove_markup(text: str) -> str: def remove_markup(text: str) -> str:
def sub(m: Match) -> str: def sub(m: Match) -> str:
@ -122,6 +135,27 @@ class Option:
a('') a('')
return ans return ans
def as_rst(
self, conf_name: str, shortcut_slugs: Dict[str, Tuple[str, str]],
kitty_mod: str, level: int = 0, option_group: List['Option'] = []
) -> List[str]:
ans: List[str] = []
a = ans.append
if not self.documented:
return ans
mopts = [self] + option_group
a('.. opt:: ' + ', '.join(conf_name + '.' + mo.name for mo in mopts))
a('.. code-block:: conf')
a('')
sz = max(len(x.name) for x in mopts)
for mo in mopts:
a((' {:%ds} {}' % sz).format(mo.name, mo.defval_as_string))
a('')
if self.long_text:
a(expand_opt_references(conf_name, self.long_text))
a('')
return ans
class MultiVal: class MultiVal:
@ -160,17 +194,38 @@ class MultiOption:
a('') a('')
return ans return ans
def as_rst(self, conf_name: str, shortcut_slugs: Dict[str, Tuple[str, str]], kitty_mod: str, level: int = 0) -> List[str]:
ans: List[str] = []
a = ans.append
a(f'.. opt:: {conf_name}.{self.name}')
a('.. code-block:: conf')
a('')
for k in self.items:
if k.documented:
a(f' {self.name:s} {k.defval_as_str}')
a('')
if self.long_text:
a(expand_opt_references(conf_name, self.long_text))
a('')
return ans
class Mapping: class Mapping:
add_to_default: bool add_to_default: bool
short_text: str
long_text: str long_text: str
documented: bool documented: bool
setting_name: str setting_name: str
name: str
@property @property
def parseable_text(self) -> str: def parseable_text(self) -> str:
return '' return ''
@property
def key_text(self) -> str:
return ''
def as_conf(self, commented: bool = False, level: int = 0) -> List[str]: def as_conf(self, commented: bool = False, level: int = 0) -> List[str]:
ans: List[str] = [] ans: List[str] = []
if self.documented: if self.documented:
@ -181,6 +236,24 @@ class Mapping:
a(''), a(render_block(self.long_text.strip())), a('') a(''), a(render_block(self.long_text.strip())), a('')
return ans return ans
def as_rst(self, conf_name: str, shortcut_slugs: Dict[str, Tuple[str, str]], kitty_mod: str, level: int = 0) -> List[str]:
ans: List[str] = []
sc_text = f'{conf_name}.{self.short_text}'
shortcut_slugs[f'{conf_name}.{self.name}'] = (sc_text, self.key_text.replace('kitty_mod', kitty_mod))
if self.documented:
a = ans.append
a('.. shortcut:: ' + sc_text)
if self.add_to_default:
a('.. code-block:: conf')
a('')
a(' ' + self.setting_name + ' ' + self.parseable_text.replace('kitty_mod', kitty_mod))
a('')
if self.long_text:
a(expand_opt_references(conf_name, self.long_text))
a('')
return ans
class ShortcutMapping(Mapping): class ShortcutMapping(Mapping):
setting_name: str = 'map' setting_name: str = 'map'
@ -202,6 +275,10 @@ class ShortcutMapping(Mapping):
def parseable_text(self) -> str: def parseable_text(self) -> str:
return f'{self.key} {self.action_def}' return f'{self.key} {self.action_def}'
@property
def key_text(self) -> str:
return self.key
class MouseMapping(Mapping): class MouseMapping(Mapping):
setting_name: str = 'mouse_map' setting_name: str = 'mouse_map'
@ -226,6 +303,10 @@ class MouseMapping(Mapping):
def parseable_text(self) -> str: def parseable_text(self) -> str:
return f'{self.button} {self.event} {self.modes} {self.action_def}' return f'{self.button} {self.event} {self.modes} {self.action_def}'
@property
def key_text(self) -> str:
return self.button
NonGroups = Union[Option, MultiOption, ShortcutMapping, MouseMapping] NonGroups = Union[Option, MultiOption, ShortcutMapping, MouseMapping]
GroupItem = Union[NonGroups, 'Group'] GroupItem = Union[NonGroups, 'Group']
@ -251,6 +332,7 @@ class Group:
return len(self.items) return len(self.items)
def iter_all_with_coalesced_options(self) -> Iterator[Union[Tuple, GroupItem]]: def iter_all_with_coalesced_options(self) -> Iterator[Union[Tuple, GroupItem]]:
self.kitty_mod = 'kitty_mod'
option_groups = {} option_groups = {}
current_group: List[Option] = [] current_group: List[Option] = []
coalesced = set() coalesced = set()
@ -283,6 +365,42 @@ class Group:
else: else:
yield x yield x
def as_rst(self, conf_name: str, shortcut_slugs: Dict[str, Tuple[str, str]], kitty_mod: str = 'kitty_mod', level: int = 0) -> List[str]:
ans: List[str] = []
a = ans.append
if level:
a('')
a(f'.. _conf-{conf_name}-{self.name}:')
a('')
a(self.title)
heading_level = '+' if level > 1 else '^'
a(heading_level * (len(self.title) + 20))
a('')
if self.start_text:
a(self.start_text)
a('')
else:
ans.extend(('.. default-domain:: conf', ''))
if not level:
for opt in self.iter_all_non_groups():
if isinstance(opt, Option) and opt.name == 'kitty_mod':
kitty_mod = opt.defval_as_string
break
for item in self.iter_all_with_coalesced_options():
if isinstance(item, tuple):
option, option_group = item
lines = option.as_rst(conf_name, shortcut_slugs, kitty_mod, option_group=option_group)
else:
lines = item.as_rst(conf_name, shortcut_slugs, kitty_mod, level + 1)
ans.extend(lines)
if level:
if self.end_text:
a('')
a(self.end_text)
return ans
def as_conf(self, commented: bool = False, level: int = 0) -> List[str]: def as_conf(self, commented: bool = False, level: int = 0) -> List[str]:
ans: List[str] = [] ans: List[str] = []
a = ans.append a = ans.append
@ -470,3 +588,6 @@ class Definition:
def as_conf(self, commented: bool = False) -> List[str]: def as_conf(self, commented: bool = False) -> List[str]:
return self.root_group.as_conf(commented) return self.root_group.as_conf(commented)
def as_rst(self, conf_name: str, shortcut_slugs: Dict[str, Tuple[str, str]]) -> List[str]:
return self.root_group.as_rst(conf_name, shortcut_slugs)