Add type checking for the various CLI options objects
This commit is contained in:
parent
0f4e7921ee
commit
f05890719d
@ -7,6 +7,7 @@ from contextlib import suppress
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.constants import cache_dir
|
||||
from kitty.cli_stub import AskCLIOptions
|
||||
|
||||
from ..tui.operations import alternate_screen, styled
|
||||
from ..tui.handler import result_handler
|
||||
@ -89,7 +90,7 @@ def main(args):
|
||||
from kitty.shell import init_readline
|
||||
msg = 'Ask the user for input'
|
||||
try:
|
||||
args, items = parse_args(args[1:], option_text, '', msg, 'kitty ask')
|
||||
args, items = parse_args(args[1:], option_text, '', msg, 'kitty ask', result_class=AskCLIOptions)
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0])
|
||||
|
||||
@ -6,6 +6,7 @@ import os
|
||||
import sys
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import ClipboardCLIOptions
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
@ -81,7 +82,7 @@ usage = ''
|
||||
|
||||
|
||||
def main(args):
|
||||
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten clipboard')
|
||||
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten clipboard', result_class=ClipboardCLIOptions)
|
||||
if items:
|
||||
raise SystemExit('Unrecognized extra command line arguments')
|
||||
data = None
|
||||
|
||||
@ -12,7 +12,7 @@ from kitty.conf.definition import config_lines
|
||||
from kitty.constants import config_dir
|
||||
from kitty.rgb import color_as_sgr
|
||||
|
||||
from .config_data import type_map, all_options
|
||||
from .config_data import type_convert, all_options
|
||||
|
||||
defaults = None
|
||||
|
||||
@ -89,7 +89,7 @@ def parse_config(lines, check_keys=True):
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
type_map,
|
||||
type_convert,
|
||||
special_handling,
|
||||
ans,
|
||||
check_keys=check_keys
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from functools import partial
|
||||
# Utils {{{
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
from typing import Dict, Union
|
||||
from typing import Any, Dict, Union
|
||||
|
||||
from kitty.conf.definition import Option, Shortcut, option_func
|
||||
from kitty.conf.utils import positive_int, python_string, to_color
|
||||
@ -120,4 +120,9 @@ k('prev_match', '<', 'scroll_to prev-match', _('Scroll to previous search match'
|
||||
k('search_forward_simple', 'f', 'start_search substring forward', _('Search forward (no regex)'))
|
||||
k('search_backward_simple', 'b', 'start_search substring backward', _('Search backward (no regex)'))
|
||||
|
||||
type_map = {o.name: o.option_type for o in all_options.values() if isinstance(o, Option)}
|
||||
|
||||
def type_convert(name: str, val: Any) -> Any:
|
||||
o = all_options.get(name)
|
||||
if isinstance(o, Option):
|
||||
val = o.option_type(val)
|
||||
return val
|
||||
|
||||
@ -15,6 +15,7 @@ from functools import partial
|
||||
from gettext import gettext as _
|
||||
|
||||
from kitty.cli import CONFIG_HELP, parse_args
|
||||
from kitty.cli_stub import DiffCLIOptions
|
||||
from kitty.constants import appname
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.key_encoding import RELEASE, enter_key, key_defs as K
|
||||
@ -542,7 +543,7 @@ def get_remote_file(path):
|
||||
|
||||
def main(args):
|
||||
warnings.showwarning = showwarning
|
||||
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff')
|
||||
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff', result_class=DiffCLIOptions)
|
||||
if len(items) != 2:
|
||||
raise SystemExit('You must specify exactly two files/directories to compare')
|
||||
left, right = items
|
||||
|
||||
@ -11,6 +11,7 @@ from gettext import gettext as _
|
||||
from itertools import repeat
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import HintsCLIOptions
|
||||
from kitty.fast_data_types import set_clipboard_string
|
||||
from kitty.key_encoding import key_defs as K, backspace_key, enter_key
|
||||
from kitty.utils import screen_size_function
|
||||
@ -508,7 +509,7 @@ usage = ''
|
||||
|
||||
|
||||
def parse_hints_args(args):
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten hints')
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten hints', result_class=HintsCLIOptions)
|
||||
|
||||
|
||||
def main(args):
|
||||
|
||||
@ -14,6 +14,7 @@ from math import ceil
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import IcatCLIOptions
|
||||
from kitty.constants import appname
|
||||
from kitty.utils import TTYIO, fit_image, screen_size_function
|
||||
|
||||
@ -343,7 +344,7 @@ def process_single_item(item, args, url_pat=None, maybe_dir=True):
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
args, items = parse_args(args[1:], options_spec, usage, help_text, '{} +kitten icat'.format(appname))
|
||||
args, items = parse_args(args[1:], options_spec, usage, help_text, '{} +kitten icat'.format(appname), result_class=IcatCLIOptions)
|
||||
|
||||
if args.print_window_size:
|
||||
screen_size_function.ans = None
|
||||
|
||||
@ -8,6 +8,7 @@ import subprocess
|
||||
import sys
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import PanelCLIOptions
|
||||
from kitty.constants import is_macos
|
||||
|
||||
OPTIONS = r'''
|
||||
@ -48,7 +49,7 @@ usage = 'program-to-run'
|
||||
|
||||
|
||||
def parse_panel_args(args):
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel')
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions)
|
||||
|
||||
|
||||
def call_xprop(*cmd, silent=False):
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
import sys
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import ResizeCLIOptions
|
||||
from kitty.cmds import cmap, parse_subcommand_cli
|
||||
from kitty.constants import version
|
||||
from kitty.key_encoding import CTRL, RELEASE, key_defs as K
|
||||
@ -119,7 +120,7 @@ The base vertical increment.
|
||||
def main(args):
|
||||
msg = 'Resize the current window'
|
||||
try:
|
||||
args, items = parse_args(args[1:], OPTIONS, '', msg, 'resize_window')
|
||||
args, items = parse_args(args[1:], OPTIONS, '', msg, 'resize_window', result_class=ResizeCLIOptions)
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0], file=sys.stderr)
|
||||
|
||||
@ -7,6 +7,7 @@ import sys
|
||||
from contextlib import suppress
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import ErrorCLIOptions
|
||||
|
||||
from ..tui.operations import styled
|
||||
|
||||
@ -19,7 +20,7 @@ The title for the error message.
|
||||
|
||||
def real_main(args):
|
||||
msg = 'Show an error message'
|
||||
args, items = parse_args(args[1:], OPTIONS, '', msg, 'hints')
|
||||
args, items = parse_args(args[1:], OPTIONS, '', msg, 'hints', result_class=ErrorCLIOptions)
|
||||
error_message = sys.stdin.buffer.read().decode('utf-8')
|
||||
sys.stdin = open(os.ctermid())
|
||||
print(styled(args.title, fg_intense=True, fg='red', bold=True))
|
||||
|
||||
@ -11,6 +11,7 @@ from functools import lru_cache
|
||||
from gettext import gettext as _
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import UnicodeCLIOptions
|
||||
from kitty.config import cached_values_for
|
||||
from kitty.constants import config_dir
|
||||
from kitty.fast_data_types import is_emoji_presentation_base, wcswidth
|
||||
@ -530,7 +531,7 @@ default form specified in the unicode standard for the symbol is used.
|
||||
|
||||
|
||||
def parse_unicode_input_args(args):
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten unicode_input')
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten unicode_input', result_class=UnicodeCLIOptions)
|
||||
|
||||
|
||||
def main(args):
|
||||
|
||||
@ -304,8 +304,9 @@ class Boss:
|
||||
else:
|
||||
msg = json.loads(msg)
|
||||
if isinstance(msg, dict) and msg.get('cmd') == 'new_instance':
|
||||
from .cli_stub import CLIOptions
|
||||
startup_id = msg.get('startup_id')
|
||||
args, rest = parse_args(msg['args'][1:])
|
||||
args, rest = parse_args(msg['args'][1:], result_class=CLIOptions)
|
||||
args.args = rest
|
||||
opts = create_opts(args)
|
||||
if not os.path.isabs(args.directory):
|
||||
|
||||
235
kitty/cli.py
235
kitty/cli.py
@ -6,9 +6,30 @@ import os
|
||||
import re
|
||||
import sys
|
||||
from collections import deque
|
||||
from typing import (
|
||||
Any, Callable, Dict, FrozenSet, Iterator, List, Optional, Tuple, Type,
|
||||
TypeVar, Union, cast
|
||||
)
|
||||
|
||||
from .conf.utils import resolve_config
|
||||
from .constants import appname, defconf, is_macos, is_wayland, str_version
|
||||
from .cli_stub import CLIOptions
|
||||
from .options_stub import Options as OptionsStub
|
||||
|
||||
try:
|
||||
from typing import TypedDict
|
||||
|
||||
class OptionDict(TypedDict):
|
||||
dest: str
|
||||
aliases: FrozenSet[str]
|
||||
help: str
|
||||
choices: FrozenSet[str]
|
||||
type: str
|
||||
default: Optional[str]
|
||||
condition: bool
|
||||
except ImportError:
|
||||
OptionDict = Dict[str, Any] # type: ignore
|
||||
|
||||
|
||||
CONFIG_HELP = '''\
|
||||
Specify a path to the configuration file(s) to use. All configuration files are
|
||||
@ -34,49 +55,49 @@ defaults for all users.
|
||||
)
|
||||
|
||||
|
||||
def surround(x, start, end):
|
||||
def surround(x: str, start: int, end: int) -> str:
|
||||
if sys.stdout.isatty():
|
||||
x = '\033[{}m{}\033[{}m'.format(start, x, end)
|
||||
return x
|
||||
|
||||
|
||||
def emph(x):
|
||||
def emph(x: str) -> str:
|
||||
return surround(x, 91, 39)
|
||||
|
||||
|
||||
def cyan(x):
|
||||
def cyan(x: str) -> str:
|
||||
return surround(x, 96, 39)
|
||||
|
||||
|
||||
def green(x):
|
||||
def green(x: str) -> str:
|
||||
return surround(x, 32, 39)
|
||||
|
||||
|
||||
def blue(x):
|
||||
def blue(x: str) -> str:
|
||||
return surround(x, 34, 39)
|
||||
|
||||
|
||||
def yellow(x):
|
||||
def yellow(x: str) -> str:
|
||||
return surround(x, 93, 39)
|
||||
|
||||
|
||||
def italic(x):
|
||||
def italic(x: str) -> str:
|
||||
return surround(x, 3, 23)
|
||||
|
||||
|
||||
def bold(x):
|
||||
def bold(x: str) -> str:
|
||||
return surround(x, 1, 22)
|
||||
|
||||
|
||||
def title(x):
|
||||
def title(x: str) -> str:
|
||||
return blue(bold(x))
|
||||
|
||||
|
||||
def opt(text):
|
||||
def opt(text: str) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def option(x):
|
||||
def option(x: str) -> str:
|
||||
idx = x.find('-')
|
||||
if idx > -1:
|
||||
x = x[idx:]
|
||||
@ -84,33 +105,39 @@ def option(x):
|
||||
return ' '.join(parts)
|
||||
|
||||
|
||||
def code(x):
|
||||
def code(x: str) -> str:
|
||||
return x
|
||||
|
||||
|
||||
def kbd(x):
|
||||
def kbd(x: str) -> str:
|
||||
return x
|
||||
|
||||
|
||||
def env(x):
|
||||
def env(x: str) -> str:
|
||||
return italic(x)
|
||||
|
||||
|
||||
def file(x):
|
||||
def file(x: str) -> str:
|
||||
return italic(x)
|
||||
|
||||
|
||||
def parse_option_spec(spec=None):
|
||||
OptionSpecSeq = List[Union[str, OptionDict]]
|
||||
|
||||
|
||||
def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, OptionSpecSeq]:
|
||||
if spec is None:
|
||||
spec = options_spec()
|
||||
NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP'
|
||||
state = NORMAL
|
||||
lines = spec.splitlines()
|
||||
prev_line = ''
|
||||
seq = []
|
||||
disabled = []
|
||||
seq: OptionSpecSeq = []
|
||||
disabled: OptionSpecSeq = []
|
||||
mpat = re.compile('([a-z]+)=(.+)')
|
||||
current_cmd = None
|
||||
current_cmd: OptionDict = {
|
||||
'dest': '', 'aliases': frozenset(), 'help': '', 'choices': frozenset(), 'type': '', 'condition': False, 'default': None
|
||||
}
|
||||
empty_cmd = current_cmd
|
||||
|
||||
for line in lines:
|
||||
line = line.rstrip()
|
||||
@ -122,7 +149,10 @@ def parse_option_spec(spec=None):
|
||||
continue
|
||||
if line.startswith('--'):
|
||||
parts = line.split(' ')
|
||||
current_cmd = {'dest': parts[0][2:].replace('-', '_'), 'aliases': frozenset(parts), 'help': ''}
|
||||
current_cmd = {
|
||||
'dest': parts[0][2:].replace('-', '_'), 'aliases': frozenset(parts), 'help': '', 'choices': frozenset(), 'type': '',
|
||||
'default': None, 'condition': True
|
||||
}
|
||||
state = METADATA
|
||||
continue
|
||||
raise ValueError('Invalid option spec, unexpected line: {}'.format(line))
|
||||
@ -133,11 +163,17 @@ def parse_option_spec(spec=None):
|
||||
current_cmd['help'] += line
|
||||
else:
|
||||
k, v = m.group(1), m.group(2)
|
||||
if k == 'condition':
|
||||
v = eval(v)
|
||||
current_cmd[k] = v
|
||||
if k == 'choices':
|
||||
current_cmd['choices'] = {x.strip() for x in current_cmd['choices'].split(',')}
|
||||
current_cmd['choices'] = frozenset(x.strip() for x in v.split(','))
|
||||
else:
|
||||
if k == 'default':
|
||||
current_cmd['default'] = v
|
||||
elif k == 'type':
|
||||
current_cmd['type'] = v
|
||||
elif k == 'dest':
|
||||
current_cmd['dest'] = v
|
||||
elif k == 'condition':
|
||||
current_cmd['condition'] = bool(eval(v))
|
||||
elif state is HELP:
|
||||
if line:
|
||||
spc = '' if current_cmd['help'].endswith('\n') else ' '
|
||||
@ -148,15 +184,15 @@ def parse_option_spec(spec=None):
|
||||
else:
|
||||
state = NORMAL
|
||||
(seq if current_cmd.get('condition', True) else disabled).append(current_cmd)
|
||||
current_cmd = None
|
||||
current_cmd = empty_cmd
|
||||
prev_line = line
|
||||
if current_cmd is not None:
|
||||
if current_cmd is not empty_cmd:
|
||||
(seq if current_cmd.get('condition', True) else disabled).append(current_cmd)
|
||||
|
||||
return seq, disabled
|
||||
|
||||
|
||||
def prettify(text):
|
||||
def prettify(text: str) -> str:
|
||||
role_map = globals()
|
||||
|
||||
def sub(m):
|
||||
@ -167,11 +203,11 @@ def prettify(text):
|
||||
return text
|
||||
|
||||
|
||||
def prettify_rst(text):
|
||||
def prettify_rst(text: str) -> str:
|
||||
return re.sub(r':([a-z]+):`([^`]+)`(=[^\s.]+)', r':\1:`\2`:code:`\3`', text)
|
||||
|
||||
|
||||
def version(add_rev=False):
|
||||
def version(add_rev: bool = False) -> str:
|
||||
rev = ''
|
||||
from . import fast_data_types
|
||||
if add_rev and hasattr(fast_data_types, 'KITTY_VCS_REV'):
|
||||
@ -179,7 +215,7 @@ def version(add_rev=False):
|
||||
return '{} {}{} created by {}'.format(italic(appname), green(str_version), rev, title('Kovid Goyal'))
|
||||
|
||||
|
||||
def wrap(text, limit=80):
|
||||
def wrap(text: str, limit: int = 80) -> Iterator[str]:
|
||||
NORMAL, IN_FORMAT = 'NORMAL', 'IN_FORMAT'
|
||||
state = NORMAL
|
||||
last_space_at = None
|
||||
@ -203,7 +239,7 @@ def wrap(text, limit=80):
|
||||
last_space_at = None
|
||||
chars_in_line = i - breaks[-1]
|
||||
|
||||
lines = []
|
||||
lines: List[str] = []
|
||||
for b in reversed(breaks):
|
||||
lines.append(text[b:].lstrip())
|
||||
text = text[:b]
|
||||
@ -212,8 +248,8 @@ def wrap(text, limit=80):
|
||||
return reversed(lines)
|
||||
|
||||
|
||||
def get_defaults_from_seq(seq):
|
||||
ans = {}
|
||||
def get_defaults_from_seq(seq: OptionSpecSeq) -> Dict[str, Any]:
|
||||
ans: Dict[str, Any] = {}
|
||||
for opt in seq:
|
||||
if not isinstance(opt, str):
|
||||
ans[opt['dest']] = defval_for_opt(opt)
|
||||
@ -232,21 +268,21 @@ class PrintHelpForSeq:
|
||||
|
||||
allow_pager = True
|
||||
|
||||
def __call__(self, seq, usage, message, appname):
|
||||
def __call__(self, seq: OptionSpecSeq, usage: Optional[str], message: Optional[str], appname: str) -> None:
|
||||
from kitty.utils import screen_size_function
|
||||
screen_size = screen_size_function()
|
||||
try:
|
||||
linesz = min(screen_size().cols, 76)
|
||||
except OSError:
|
||||
linesz = 76
|
||||
blocks = []
|
||||
blocks: List[str] = []
|
||||
a = blocks.append
|
||||
|
||||
def wa(text, indent=0, leading_indent=None):
|
||||
if leading_indent is None:
|
||||
leading_indent = indent
|
||||
j = '\n' + (' ' * indent)
|
||||
lines = []
|
||||
lines: List[str] = []
|
||||
for l in text.splitlines():
|
||||
if l:
|
||||
lines.extend(wrap(l, limit=linesz - indent))
|
||||
@ -279,7 +315,7 @@ class PrintHelpForSeq:
|
||||
wa(prettify(t.strip()), indent=4)
|
||||
if defval is not None:
|
||||
wa('Default: {}'.format(defval), indent=4)
|
||||
if 'choices' in opt:
|
||||
if opt.get('choices'):
|
||||
wa('Choices: {}'.format(', '.join(opt['choices'])), indent=4)
|
||||
a('')
|
||||
|
||||
@ -299,9 +335,9 @@ class PrintHelpForSeq:
|
||||
print_help_for_seq = PrintHelpForSeq()
|
||||
|
||||
|
||||
def seq_as_rst(seq, usage, message, appname, heading_char='-'):
|
||||
def seq_as_rst(seq: OptionSpecSeq, usage: Optional[str], message: Optional[str], appname: Optional[str], heading_char: str = '-') -> str:
|
||||
import textwrap
|
||||
blocks = []
|
||||
blocks: List[str] = []
|
||||
a = blocks.append
|
||||
|
||||
usage = '[program-to-run ...]' if usage is None else usage
|
||||
@ -338,7 +374,7 @@ def seq_as_rst(seq, usage, message, appname, heading_char='-'):
|
||||
a(textwrap.indent(prettify_rst(t), ' ' * 4))
|
||||
if defval is not None:
|
||||
a(textwrap.indent('Default: :code:`{}`'.format(defval), ' ' * 4))
|
||||
if 'choices' in opt:
|
||||
if opt.get('choices'):
|
||||
a(textwrap.indent('Choices: :code:`{}`'.format(', '.join(sorted(opt['choices']))), ' ' * 4))
|
||||
a('')
|
||||
|
||||
@ -346,8 +382,30 @@ def seq_as_rst(seq, usage, message, appname, heading_char='-'):
|
||||
return text
|
||||
|
||||
|
||||
def defval_for_opt(opt):
|
||||
dv = opt.get('default')
|
||||
def as_type_stub(seq: OptionSpecSeq, disabled: OptionSpecSeq, class_name: str) -> str:
|
||||
from itertools import chain
|
||||
ans: List[str] = ['class {}:'.format(class_name)]
|
||||
for opt in chain(seq, disabled):
|
||||
if isinstance(opt, str):
|
||||
continue
|
||||
name = opt['dest']
|
||||
otype = opt['type'] or 'str'
|
||||
if otype in ('str', 'int', 'float'):
|
||||
t = otype
|
||||
elif otype == 'list':
|
||||
t = 'typing.Sequence[str]'
|
||||
elif otype in ('choice', 'choices'):
|
||||
t = 'str'
|
||||
elif otype.startswith('bool-'):
|
||||
t = 'bool'
|
||||
else:
|
||||
raise ValueError('Unknown CLI option type: {}'.format(otype))
|
||||
ans.append(' {}: {}'.format(name, t))
|
||||
return '\n'.join(ans) + '\n\n\n'
|
||||
|
||||
|
||||
def defval_for_opt(opt: OptionDict) -> Any:
|
||||
dv: Any = opt.get('default')
|
||||
typ = opt.get('type', '')
|
||||
if typ.startswith('bool-'):
|
||||
if dv is None:
|
||||
@ -363,11 +421,11 @@ def defval_for_opt(opt):
|
||||
|
||||
class Options:
|
||||
|
||||
def __init__(self, seq, usage, message, appname):
|
||||
def __init__(self, seq: OptionSpecSeq, usage: Optional[str], message: Optional[str], appname: Optional[str]):
|
||||
self.alias_map = {}
|
||||
self.seq = seq
|
||||
self.names_map = {}
|
||||
self.values_map = {}
|
||||
self.names_map: Dict[str, OptionDict] = {}
|
||||
self.values_map: Dict[str, Any] = {}
|
||||
self.usage, self.message, self.appname = usage, message, appname
|
||||
for opt in seq:
|
||||
if isinstance(opt, str):
|
||||
@ -378,13 +436,13 @@ class Options:
|
||||
self.names_map[name] = opt
|
||||
self.values_map[name] = defval_for_opt(opt)
|
||||
|
||||
def opt_for_alias(self, alias):
|
||||
def opt_for_alias(self, alias: str) -> OptionDict:
|
||||
opt = self.alias_map.get(alias)
|
||||
if opt is None:
|
||||
raise SystemExit('Unknown option: {}'.format(emph(alias)))
|
||||
return opt
|
||||
|
||||
def needs_arg(self, alias):
|
||||
def needs_arg(self, alias: str) -> bool:
|
||||
if alias in ('-h', '--help'):
|
||||
print_help_for_seq(self.seq, self.usage, self.message, self.appname or appname)
|
||||
raise SystemExit(0)
|
||||
@ -395,7 +453,7 @@ class Options:
|
||||
typ = opt.get('type', '')
|
||||
return not typ.startswith('bool-')
|
||||
|
||||
def process_arg(self, alias, val=None):
|
||||
def process_arg(self, alias: str, val: Any = None) -> None:
|
||||
opt = self.opt_for_alias(alias)
|
||||
typ = opt.get('type', '')
|
||||
name = opt['dest']
|
||||
@ -424,23 +482,15 @@ class Options:
|
||||
self.values_map[name] = val
|
||||
|
||||
|
||||
class Namespace:
|
||||
|
||||
def __init__(self, kwargs):
|
||||
for name, val in kwargs.items():
|
||||
setattr(self, name, val)
|
||||
|
||||
|
||||
def parse_cmdline(oc, disabled, args=None):
|
||||
def parse_cmdline(oc: Options, disabled: OptionSpecSeq, ans: Any, args: Optional[List[str]] = None) -> List[str]:
|
||||
NORMAL, EXPECTING_ARG = 'NORMAL', 'EXPECTING_ARG'
|
||||
state = NORMAL
|
||||
if args is None:
|
||||
args = sys.argv[1:]
|
||||
args = deque(args)
|
||||
dargs = deque(sys.argv[1:] if args is None else args)
|
||||
leftover_args: List[str] = []
|
||||
current_option = None
|
||||
|
||||
while args:
|
||||
arg = args.popleft()
|
||||
while dargs:
|
||||
arg = dargs.popleft()
|
||||
if state is NORMAL:
|
||||
if arg.startswith('-'):
|
||||
if arg == '--':
|
||||
@ -458,21 +508,23 @@ def parse_cmdline(oc, disabled, args=None):
|
||||
continue
|
||||
oc.process_arg(parts[0], parts[1])
|
||||
else:
|
||||
args = [arg] + list(args)
|
||||
leftover_args = [arg] + list(dargs)
|
||||
break
|
||||
else:
|
||||
elif current_option is not None:
|
||||
oc.process_arg(current_option, arg)
|
||||
current_option, state = None, NORMAL
|
||||
if state is EXPECTING_ARG:
|
||||
raise SystemExit('An argument is required for the option: {}'.format(emph(arg)))
|
||||
|
||||
ans = Namespace(oc.values_map)
|
||||
for key, val in oc.values_map.items():
|
||||
setattr(ans, key, val)
|
||||
for opt in disabled:
|
||||
setattr(ans, opt['dest'], defval_for_opt(opt))
|
||||
return ans, list(args)
|
||||
if not isinstance(opt, str):
|
||||
setattr(ans, opt['dest'], defval_for_opt(opt))
|
||||
return leftover_args
|
||||
|
||||
|
||||
def options_spec():
|
||||
def options_spec() -> str:
|
||||
if not hasattr(options_spec, 'ans'):
|
||||
OPTIONS = '''
|
||||
--class
|
||||
@ -618,31 +670,49 @@ Print out information about the system and kitty configuration.
|
||||
type=bool-set
|
||||
!
|
||||
'''
|
||||
options_spec.ans = OPTIONS.format(
|
||||
setattr(options_spec, 'ans', OPTIONS.format(
|
||||
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname)
|
||||
|
||||
)
|
||||
return options_spec.ans
|
||||
))
|
||||
return getattr(options_spec, 'ans')
|
||||
|
||||
|
||||
def options_for_completion():
|
||||
def options_for_completion() -> OptionSpecSeq:
|
||||
raw = '--help -h\ntype=bool-set\nShow help for {appname} command line options\n\n{raw}'.format(
|
||||
appname=appname, raw=options_spec())
|
||||
return parse_option_spec(raw)[0]
|
||||
|
||||
|
||||
def option_spec_as_rst(ospec=options_spec, usage=None, message=None, appname=None, heading_char='-'):
|
||||
def option_spec_as_rst(
|
||||
ospec: Callable[[], str] = options_spec,
|
||||
usage: Optional[str] = None, message: Optional[str] = None, appname: Optional[str] = None,
|
||||
heading_char='-'
|
||||
) -> str:
|
||||
options = parse_option_spec(ospec())
|
||||
seq, disabled = options
|
||||
oc = Options(seq, usage, message, appname)
|
||||
return seq_as_rst(oc.seq, oc.usage, oc.message, oc.appname, heading_char=heading_char)
|
||||
|
||||
|
||||
def parse_args(args=None, ospec=options_spec, usage=None, message=None, appname=None):
|
||||
T = TypeVar('T')
|
||||
|
||||
|
||||
def parse_args(
|
||||
args: Optional[List[str]] = None,
|
||||
ospec: Callable[[], str] = options_spec,
|
||||
usage: Optional[str] = None,
|
||||
message: Optional[str] = None,
|
||||
appname: Optional[str] = None,
|
||||
result_class: Type[T] = None,
|
||||
) -> Tuple[T, List[str]]:
|
||||
options = parse_option_spec(ospec())
|
||||
seq, disabled = options
|
||||
oc = Options(seq, usage, message, appname)
|
||||
return parse_cmdline(oc, disabled, args=args)
|
||||
if result_class is not None:
|
||||
ans = result_class()
|
||||
else:
|
||||
ans = cast(T, CLIOptions())
|
||||
return ans, parse_cmdline(oc, disabled, ans, args=args)
|
||||
|
||||
|
||||
SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
|
||||
@ -655,8 +725,8 @@ def print_shortcut(key_sequence, action):
|
||||
mmap = {m[len('GLFW_MOD_'):].lower(): x for m, x in v.items() if m.startswith('GLFW_MOD_')}
|
||||
kmap = {k[len('GLFW_KEY_'):].lower(): x for k, x in v.items() if k.startswith('GLFW_KEY_')}
|
||||
krmap = {v: k for k, v in kmap.items()}
|
||||
print_shortcut.maps = mmap, krmap
|
||||
mmap, krmap = print_shortcut.maps
|
||||
setattr(print_shortcut, 'maps', (mmap, krmap))
|
||||
mmap, krmap = getattr(print_shortcut, 'maps')
|
||||
keys = []
|
||||
for key in key_sequence:
|
||||
names = []
|
||||
@ -667,7 +737,8 @@ def print_shortcut(key_sequence, action):
|
||||
if key:
|
||||
if is_native:
|
||||
from .fast_data_types import GLFW_KEY_UNKNOWN, glfw_get_key_name
|
||||
names.append(glfw_get_key_name(GLFW_KEY_UNKNOWN, key))
|
||||
kn = glfw_get_key_name(GLFW_KEY_UNKNOWN, key) or 'Unknown key'
|
||||
names.append(kn)
|
||||
else:
|
||||
names.append(krmap[key])
|
||||
keys.append('+'.join(names))
|
||||
@ -700,12 +771,12 @@ def flatten_sequence_map(m):
|
||||
return ans
|
||||
|
||||
|
||||
def compare_opts(opts):
|
||||
def compare_opts(opts: OptionsStub) -> None:
|
||||
from .config import defaults, load_config
|
||||
print('\nConfig options different from defaults:')
|
||||
default_opts = load_config()
|
||||
changed_opts = [
|
||||
f for f in sorted(defaults._fields)
|
||||
f for f in sorted(defaults._fields) # type: ignore
|
||||
if f not in ('key_definitions', 'keymap', 'sequence_map') and getattr(opts, f) != getattr(defaults, f)
|
||||
]
|
||||
field_len = max(map(len, changed_opts)) if changed_opts else 20
|
||||
@ -713,9 +784,9 @@ def compare_opts(opts):
|
||||
for f in changed_opts:
|
||||
print(title(fmt.format(f)), getattr(opts, f))
|
||||
|
||||
final, initial = opts.keymap, default_opts.keymap
|
||||
final = {(k,): v for k, v in final.items()}
|
||||
initial = {(k,): v for k, v in initial.items()}
|
||||
final_, initial_ = opts.keymap, default_opts.keymap
|
||||
final = {(k,): v for k, v in final_.items()}
|
||||
initial = {(k,): v for k, v in initial_.items()}
|
||||
final_s, initial_s = map(flatten_sequence_map, (opts.sequence_map, default_opts.sequence_map))
|
||||
final.update(final_s)
|
||||
initial.update(initial_s)
|
||||
|
||||
64
kitty/cli_stub.py
Normal file
64
kitty/cli_stub.py
Normal file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
class CLIOptions:
|
||||
pass
|
||||
|
||||
|
||||
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
||||
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
||||
ErrorCLIOptions = UnicodeCLIOptions = CLIOptions
|
||||
|
||||
|
||||
def generate_stub() -> None:
|
||||
from .cli import parse_option_spec, as_type_stub
|
||||
from .conf.definition import save_type_stub
|
||||
text = 'import typing\n\n\n'
|
||||
|
||||
def do(otext=None, cls: str = 'CLIOptions'):
|
||||
nonlocal text
|
||||
text += as_type_stub(*parse_option_spec(otext), class_name=cls)
|
||||
|
||||
do()
|
||||
|
||||
from .launch import options_spec
|
||||
do(options_spec(), 'LaunchCLIOptions')
|
||||
|
||||
from kittens.ask.main import option_text
|
||||
do(option_text(), 'AskCLIOptions')
|
||||
|
||||
from kittens.clipboard.main import OPTIONS
|
||||
do(OPTIONS(), 'ClipboardCLIOptions')
|
||||
|
||||
from kittens.diff.main import OPTIONS
|
||||
do(OPTIONS(), 'DiffCLIOptions')
|
||||
|
||||
from kittens.hints.main import OPTIONS
|
||||
do(OPTIONS(), 'HintsCLIOptions')
|
||||
|
||||
from kittens.icat.main import OPTIONS
|
||||
do(OPTIONS, 'IcatCLIOptions')
|
||||
|
||||
from kittens.panel.main import OPTIONS
|
||||
do(OPTIONS(), 'PanelCLIOptions')
|
||||
|
||||
from kittens.resize_window.main import OPTIONS
|
||||
do(OPTIONS(), 'ResizeCLIOptions')
|
||||
|
||||
from kittens.show_error.main import OPTIONS
|
||||
do(OPTIONS(), 'ErrorCLIOptions')
|
||||
|
||||
from kittens.unicode_input.main import OPTIONS
|
||||
do(OPTIONS(), 'UnicodeCLIOptions')
|
||||
|
||||
save_type_stub(text, __file__)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import subprocess
|
||||
subprocess.Popen([
|
||||
'kitty', '+runpy',
|
||||
'from kitty.cli_stub import generate_stub; generate_stub()'
|
||||
])
|
||||
118
kitty/cli_stub.pyi
Normal file
118
kitty/cli_stub.pyi
Normal file
@ -0,0 +1,118 @@
|
||||
# Update this file by running: python kitty/cli_stub.py
|
||||
import typing
|
||||
|
||||
|
||||
class CLIOptions:
|
||||
cls: str
|
||||
name: str
|
||||
title: str
|
||||
config: typing.Sequence[str]
|
||||
override: typing.Sequence[str]
|
||||
directory: str
|
||||
detach: bool
|
||||
session: str
|
||||
hold: bool
|
||||
single_instance: bool
|
||||
instance_group: str
|
||||
wait_for_single_instance_window_close: bool
|
||||
listen_on: str
|
||||
start_as: str
|
||||
version: bool
|
||||
dump_commands: bool
|
||||
replay_commands: str
|
||||
dump_bytes: str
|
||||
debug_gl: bool
|
||||
debug_keyboard: bool
|
||||
debug_font_fallback: bool
|
||||
debug_config: bool
|
||||
execute: bool
|
||||
|
||||
|
||||
class LaunchCLIOptions:
|
||||
window_title: str
|
||||
tab_title: str
|
||||
type: str
|
||||
keep_focus: bool
|
||||
cwd: str
|
||||
env: typing.Sequence[str]
|
||||
copy_colors: bool
|
||||
copy_cmdline: bool
|
||||
copy_env: bool
|
||||
location: str
|
||||
allow_remote_control: bool
|
||||
stdin_source: str
|
||||
stdin_add_formatting: bool
|
||||
stdin_add_line_wrap_markers: bool
|
||||
marker: str
|
||||
|
||||
|
||||
class AskCLIOptions:
|
||||
type: str
|
||||
message: str
|
||||
name: str
|
||||
|
||||
|
||||
class ClipboardCLIOptions:
|
||||
get_clipboard: bool
|
||||
use_primary: bool
|
||||
wait_for_completion: bool
|
||||
|
||||
|
||||
class DiffCLIOptions:
|
||||
context: int
|
||||
config: typing.Sequence[str]
|
||||
override: typing.Sequence[str]
|
||||
|
||||
|
||||
class HintsCLIOptions:
|
||||
program: typing.Sequence[str]
|
||||
type: str
|
||||
regex: str
|
||||
linenum_action: str
|
||||
url_prefixes: str
|
||||
word_characters: str
|
||||
minimum_match_length: int
|
||||
multiple: bool
|
||||
multiple_joiner: str
|
||||
add_trailing_space: str
|
||||
hints_offset: int
|
||||
alphabet: str
|
||||
ascending: bool
|
||||
customize_processing: str
|
||||
|
||||
|
||||
class IcatCLIOptions:
|
||||
align: str
|
||||
place: str
|
||||
scale_up: bool
|
||||
clear: bool
|
||||
transfer_mode: str
|
||||
detect_support: bool
|
||||
detection_timeout: float
|
||||
print_window_size: bool
|
||||
stdin: str
|
||||
silent: bool
|
||||
z_index: str
|
||||
|
||||
|
||||
class PanelCLIOptions:
|
||||
lines: int
|
||||
columns: int
|
||||
edge: str
|
||||
config: typing.Sequence[str]
|
||||
override: typing.Sequence[str]
|
||||
|
||||
|
||||
class ResizeCLIOptions:
|
||||
horizontal_increment: int
|
||||
vertical_increment: int
|
||||
|
||||
|
||||
class ErrorCLIOptions:
|
||||
title: str
|
||||
|
||||
|
||||
class UnicodeCLIOptions:
|
||||
emoji_variation: str
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ from contextlib import suppress
|
||||
from typing import Any, BinaryIO, Callable, Dict, List, Optional
|
||||
|
||||
from .cli import (
|
||||
Namespace, get_defaults_from_seq, parse_args, parse_option_spec
|
||||
get_defaults_from_seq, parse_args, parse_option_spec
|
||||
)
|
||||
from .config import parse_config, parse_send_text_bytes
|
||||
from .constants import appname
|
||||
@ -42,7 +42,7 @@ class UnknownLayout(ValueError):
|
||||
hide_traceback = True
|
||||
|
||||
|
||||
CommandFunction = Callable[[Namespace, Namespace, List[str]], Optional[Dict[str, Any]]]
|
||||
CommandFunction = Callable[[Any, Any, List[str]], Optional[Dict[str, Any]]]
|
||||
cmap: Dict[str, CommandFunction] = {}
|
||||
|
||||
|
||||
@ -906,13 +906,16 @@ def cmd_launch(global_opts, opts, args):
|
||||
return ans
|
||||
|
||||
|
||||
class GlobalOpts:
|
||||
pass
|
||||
|
||||
|
||||
def launch(boss, window, payload):
|
||||
pg = cmd_launch.payload_get
|
||||
default_opts = parse_launch_args()[0]
|
||||
opts = {}
|
||||
opts = GlobalOpts()
|
||||
for key, default_value in default_opts.__dict__.items():
|
||||
opts[key] = payload.get(key, default_value)
|
||||
opts = Namespace(opts)
|
||||
setattr(opts, key, payload.get(key, default_value))
|
||||
match = pg(payload, 'match')
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
@ -1444,8 +1447,12 @@ def cli_params_for(func):
|
||||
return (func.options_spec or '\n').format, func.argspec, func.desc, '{} @ {}'.format(appname, func.name)
|
||||
|
||||
|
||||
class SubCommandOptions:
|
||||
pass
|
||||
|
||||
|
||||
def parse_subcommand_cli(func, args):
|
||||
opts, items = parse_args(args[1:], *cli_params_for(func))
|
||||
opts, items = parse_args(args[1:], *cli_params_for(func), result_class=SubCommandOptions)
|
||||
if func.args_count is not None and func.args_count != len(items):
|
||||
if func.args_count == 0:
|
||||
raise SystemExit('Unknown extra argument(s) supplied to {}'.format(func.name))
|
||||
@ -1455,4 +1462,4 @@ def parse_subcommand_cli(func, args):
|
||||
|
||||
def display_subcommand_help(func):
|
||||
with suppress(SystemExit):
|
||||
parse_args(['--help'], (func.options_spec or '\n').format, func.argspec, func.desc, func.name)
|
||||
parse_args(['--help'], (func.options_spec or '\n').format, func.argspec, func.desc, func.name, result_class=SubCommandOptions)
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
|
||||
import re
|
||||
from functools import partial
|
||||
from typing import Any, Dict, List, Set, Tuple, Union, get_type_hints, Optional
|
||||
from typing import (
|
||||
Any, Dict, Iterator, List, Optional, Set, Tuple, Union, get_type_hints
|
||||
)
|
||||
|
||||
from .utils import to_bool
|
||||
|
||||
@ -26,7 +28,7 @@ class Option:
|
||||
|
||||
__slots__ = 'name', 'group', 'long_text', 'option_type', 'defval_as_string', 'add_to_default', 'add_to_docs', 'line'
|
||||
|
||||
def __init__(self, name, group, defval, option_type, long_text, add_to_default, add_to_docs):
|
||||
def __init__(self, name: str, group: str, defval: str, option_type: Any, long_text: str, add_to_default: bool, add_to_docs: bool):
|
||||
self.name, self.group = name, group
|
||||
self.long_text, self.option_type = long_text.strip(), option_type
|
||||
self.defval_as_string = defval
|
||||
@ -158,8 +160,8 @@ def remove_markup(text):
|
||||
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)
|
||||
|
||||
|
||||
def iter_blocks(lines):
|
||||
current_block = []
|
||||
def iter_blocks(lines: Iterator[str]):
|
||||
current_block: List[str] = []
|
||||
prev_indent = 0
|
||||
for line in lines:
|
||||
indent_size = len(line) - len(line.lstrip())
|
||||
@ -180,9 +182,10 @@ def wrapped_block(lines):
|
||||
wrapper = getattr(wrapped_block, 'wrapper', None)
|
||||
if wrapper is None:
|
||||
import textwrap
|
||||
wrapper = wrapped_block.wrapper = textwrap.TextWrapper(
|
||||
wrapper = textwrap.TextWrapper(
|
||||
initial_indent='#: ', subsequent_indent='#: ', width=70, break_long_words=False
|
||||
)
|
||||
setattr(wrapped_block, 'wrapper', wrapper)
|
||||
for block, indent_size in iter_blocks(lines):
|
||||
if indent_size > 0:
|
||||
for line in block:
|
||||
@ -278,9 +281,9 @@ def as_conf_file(all_options):
|
||||
else:
|
||||
count += 1
|
||||
else:
|
||||
if start is not None:
|
||||
if start is not None and count is not None:
|
||||
map_groups.append((start, count))
|
||||
start = None
|
||||
start = count = None
|
||||
for start, count in map_groups:
|
||||
r = range(start, start + count)
|
||||
sz = max(len(ans[i].split(' ', 3)[1]) for i in r)
|
||||
@ -304,8 +307,13 @@ def config_lines(all_options):
|
||||
yield sc.line
|
||||
|
||||
|
||||
def as_type_stub(all_options: Dict[str, Union[Option, List[Shortcut]]], special_types: Optional[Dict[str, str]] = None) -> str:
|
||||
ans = ['import typing\n', '', 'class Options:']
|
||||
def as_type_stub(
|
||||
all_options: Dict[str, Union[Option, List[Shortcut]]],
|
||||
special_types: Optional[Dict[str, str]] = None,
|
||||
preamble_lines: Union[Tuple[str, ...], List[str], Iterator[str]] = (),
|
||||
extra_fields: Union[Tuple[Tuple[str, str], ...], List[Tuple[str, str]], Iterator[Tuple[str, str]]] = ()
|
||||
) -> str:
|
||||
ans = ['import typing\n'] + list(preamble_lines) + ['', 'class Options:']
|
||||
imports: Set[Tuple[str, str]] = set()
|
||||
overrides = special_types or {}
|
||||
for name, val in all_options.items():
|
||||
@ -316,4 +324,16 @@ def as_type_stub(all_options: Dict[str, Union[Option, List[Shortcut]]], special_
|
||||
for mod, name in imports:
|
||||
ans.insert(0, 'from {} import {}'.format(mod, name))
|
||||
ans.insert(0, 'import {}'.format(mod))
|
||||
for field_name, type_def in extra_fields:
|
||||
ans.append(' {}: {}'.format(field_name, type_def))
|
||||
return '\n'.join(ans)
|
||||
|
||||
|
||||
def save_type_stub(text: str, fpath: str) -> None:
|
||||
import os
|
||||
with open(fpath + 'i', 'w') as f:
|
||||
print(
|
||||
'# Update this file by running: python {}'.format(os.path.relpath(os.path.abspath(fpath))),
|
||||
file=f
|
||||
)
|
||||
f.write(text)
|
||||
|
||||
@ -73,7 +73,9 @@ def choices(*choices) -> Callable[[str], str]:
|
||||
|
||||
|
||||
def parse_line(
|
||||
line: str, type_map: Dict[str, Any], special_handling: Callable,
|
||||
line: str,
|
||||
type_convert: Callable[[str, Any], Any],
|
||||
special_handling: Callable,
|
||||
ans: Dict[str, Any], all_keys: Optional[FrozenSet[str]],
|
||||
base_path_for_includes: str
|
||||
) -> None:
|
||||
@ -93,7 +95,7 @@ def parse_line(
|
||||
val = os.path.join(base_path_for_includes, val)
|
||||
try:
|
||||
with open(val, encoding='utf-8', errors='replace') as include:
|
||||
_parse(include, type_map, special_handling, ans, all_keys)
|
||||
_parse(include, type_convert, special_handling, ans, all_keys)
|
||||
except FileNotFoundError:
|
||||
log_error(
|
||||
'Could not find included config file: {}, ignoring'.
|
||||
@ -108,15 +110,12 @@ def parse_line(
|
||||
if all_keys is not None and key not in all_keys:
|
||||
log_error('Ignoring unknown config key: {}'.format(key))
|
||||
return
|
||||
tm = type_map.get(key)
|
||||
if tm is not None:
|
||||
val = tm(val)
|
||||
ans[key] = val
|
||||
ans[key] = type_convert(key, val)
|
||||
|
||||
|
||||
def _parse(
|
||||
lines: Iterator[str],
|
||||
type_map: Dict[str, Any],
|
||||
type_convert: Callable[[str, Any], Any],
|
||||
special_handling: Callable,
|
||||
ans: Dict[str, Any],
|
||||
all_keys: Optional[FrozenSet[str]],
|
||||
@ -131,7 +130,7 @@ def _parse(
|
||||
for i, line in enumerate(lines):
|
||||
try:
|
||||
parse_line(
|
||||
line, type_map, special_handling, ans, all_keys,
|
||||
line, type_convert, special_handling, ans, all_keys,
|
||||
base_path_for_includes
|
||||
)
|
||||
except Exception as e:
|
||||
@ -143,7 +142,7 @@ def _parse(
|
||||
def parse_config_base(
|
||||
lines: Iterator[str],
|
||||
defaults: Any,
|
||||
type_map: Dict[str, Any],
|
||||
type_convert: Callable[[str, Any], Any],
|
||||
special_handling: Callable,
|
||||
ans: Dict[str, Any],
|
||||
check_keys=True,
|
||||
@ -151,7 +150,7 @@ def parse_config_base(
|
||||
):
|
||||
all_keys: Optional[FrozenSet[str]] = defaults._asdict() if check_keys else None
|
||||
_parse(
|
||||
lines, type_map, special_handling, ans, all_keys, accumulate_bad_lines
|
||||
lines, type_convert, special_handling, ans, all_keys, accumulate_bad_lines
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -7,9 +7,9 @@ import os
|
||||
import re
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
from contextlib import suppress
|
||||
from contextlib import contextmanager, suppress
|
||||
from functools import partial
|
||||
from typing import Type
|
||||
|
||||
from . import fast_data_types as defines
|
||||
from .conf.definition import as_conf_file, config_lines
|
||||
@ -17,10 +17,11 @@ from .conf.utils import (
|
||||
init_config, key_func, load_config as _load_config, merge_dicts,
|
||||
parse_config_base, python_string, to_bool, to_cmdline
|
||||
)
|
||||
from .config_data import all_options, parse_mods, type_map
|
||||
from .config_data import all_options, parse_mods, type_convert
|
||||
from .constants import cache_dir, defconf, is_macos
|
||||
from .utils import log_error
|
||||
from .key_names import get_key_name_lookup, key_name_aliases
|
||||
from .options_stub import Options as OptionsStub
|
||||
from .utils import log_error
|
||||
|
||||
|
||||
def parse_shortcut(sc):
|
||||
@ -575,9 +576,6 @@ def special_handling(key, val, ans):
|
||||
return True
|
||||
|
||||
|
||||
defaults = None
|
||||
|
||||
|
||||
def option_names_for_completion():
|
||||
yield from defaults
|
||||
yield from special_handlers
|
||||
@ -585,10 +583,15 @@ def option_names_for_completion():
|
||||
|
||||
def parse_config(lines, check_keys=True, accumulate_bad_lines=None):
|
||||
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}, 'kitten_aliases': {}, 'font_features': {}}
|
||||
if check_keys:
|
||||
defs = defaults
|
||||
else:
|
||||
defs = None
|
||||
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
type_map,
|
||||
defs,
|
||||
type_convert,
|
||||
special_handling,
|
||||
ans,
|
||||
check_keys=check_keys,
|
||||
@ -602,7 +605,9 @@ def parse_defaults(lines, check_keys=False):
|
||||
return ans
|
||||
|
||||
|
||||
Options, defaults = init_config(config_lines(all_options), parse_defaults)
|
||||
xc = init_config(config_lines(all_options), parse_defaults)
|
||||
Options: Type[OptionsStub] = xc[0]
|
||||
defaults: OptionsStub = xc[1]
|
||||
actions = frozenset(all_key_actions) | frozenset(
|
||||
'run_simple_kitten combine send_text goto_tab goto_layout set_font_size new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
|
||||
split()
|
||||
@ -763,7 +768,7 @@ def finalize_keys(opts):
|
||||
opts.sequence_map = sequence_map
|
||||
|
||||
|
||||
def load_config(*paths, overrides=None, accumulate_bad_lines=None):
|
||||
def load_config(*paths, overrides=None, accumulate_bad_lines=None) -> OptionsStub:
|
||||
parser = parse_config
|
||||
if accumulate_bad_lines is not None:
|
||||
parser = partial(parse_config, accumulate_bad_lines=accumulate_bad_lines)
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import os
|
||||
from gettext import gettext as _
|
||||
from typing import (
|
||||
Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar, Union
|
||||
Any, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar, Union
|
||||
)
|
||||
|
||||
from . import fast_data_types as defines
|
||||
@ -1432,4 +1432,9 @@ the line (same as pressing the Home key)::
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
type_map = {o.name: o.option_type for o in all_options.values() if isinstance(o, Option)}
|
||||
|
||||
def type_convert(name: str, val: Any) -> Any:
|
||||
o = all_options.get(name)
|
||||
if isinstance(o, Option):
|
||||
val = o.option_type(val)
|
||||
return val
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
from typing import Any, Callable, Dict, List, NewType, Optional, Tuple, Union
|
||||
|
||||
from kitty.boss import Boss
|
||||
from kitty.cli import Namespace
|
||||
from kitty.options_stub import Options
|
||||
|
||||
# Constants {{{
|
||||
KITTY_VCS_REV: str
|
||||
ERROR_PREFIX: str
|
||||
GLSL_VERSION: int
|
||||
GLFW_IBEAM_CURSOR: int
|
||||
@ -339,6 +340,10 @@ def redirect_std_streams(devnull: str) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def glfw_get_key_name(key: int, native_key: int) -> Optional[str]:
|
||||
pass
|
||||
|
||||
|
||||
StartupCtx = NewType('StartupCtx', int)
|
||||
Display = NewType('Display', int)
|
||||
|
||||
@ -468,7 +473,7 @@ def update_window_visibility(os_window_id: int, tab_id: int, window_id: int, win
|
||||
|
||||
|
||||
def set_options(
|
||||
opts: Namespace,
|
||||
opts: Options,
|
||||
is_wayland: bool = False,
|
||||
debug_gl: bool = False,
|
||||
debug_font_fallback: bool = False
|
||||
|
||||
@ -4,10 +4,11 @@
|
||||
|
||||
|
||||
from functools import lru_cache
|
||||
from typing import Dict
|
||||
from typing import Dict, List, Optional, Sequence, Tuple
|
||||
|
||||
from .child import Child
|
||||
from .cli import parse_args
|
||||
from .cli_stub import LaunchCLIOptions
|
||||
from .fast_data_types import set_clipboard_string
|
||||
from .utils import set_primary_selection
|
||||
|
||||
@ -130,16 +131,16 @@ the same as for the :code:`toggle_marker` map action (see :doc:`/marks`).
|
||||
'''
|
||||
|
||||
|
||||
def parse_launch_args(args=None):
|
||||
def parse_launch_args(args: Optional[Sequence[str]] = None) -> Tuple[LaunchCLIOptions, List[str]]:
|
||||
args = list(args or ())
|
||||
try:
|
||||
opts, args = parse_args(args=args, ospec=options_spec)
|
||||
opts, args = parse_args(result_class=LaunchCLIOptions, args=args, ospec=options_spec)
|
||||
except SystemExit as e:
|
||||
raise ValueError from e
|
||||
return opts, args
|
||||
|
||||
|
||||
def get_env(opts, active_child: Child) -> Dict[str, str]:
|
||||
def get_env(opts: LaunchCLIOptions, active_child: Child) -> Dict[str, str]:
|
||||
env: Dict[str, str] = {}
|
||||
if opts.copy_env and active_child:
|
||||
env.update(active_child.foreground_environ)
|
||||
@ -150,7 +151,7 @@ def get_env(opts, active_child: Child) -> Dict[str, str]:
|
||||
return env
|
||||
|
||||
|
||||
def tab_for_window(boss, opts, target_tab=None):
|
||||
def tab_for_window(boss, opts: LaunchCLIOptions, target_tab=None):
|
||||
if opts.type == 'tab':
|
||||
tm = boss.active_tab_manager
|
||||
tab = tm.new_tab(empty_tab=True, location=opts.location)
|
||||
@ -168,7 +169,7 @@ def tab_for_window(boss, opts, target_tab=None):
|
||||
return tab
|
||||
|
||||
|
||||
def launch(boss, opts, args, target_tab=None):
|
||||
def launch(boss, opts: LaunchCLIOptions, args: List[str], target_tab=None):
|
||||
active = boss.active_window_for_cwd
|
||||
active_child = getattr(active, 'child', None)
|
||||
env = get_env(opts, active_child)
|
||||
|
||||
@ -12,6 +12,7 @@ from .borders import load_borders_program
|
||||
from .boss import Boss
|
||||
from .child import set_default_env
|
||||
from .cli import create_opts, parse_args
|
||||
from .cli_stub import CLIOptions
|
||||
from .config import cached_values_for, initial_window_size_func
|
||||
from .constants import (
|
||||
appname, beam_cursor_data_file, config_dir, glfw_path, is_macos,
|
||||
@ -280,7 +281,7 @@ def _main():
|
||||
cwd_ok = False
|
||||
if not cwd_ok:
|
||||
os.chdir(os.path.expanduser('~'))
|
||||
args, rest = parse_args(args=args)
|
||||
args, rest = parse_args(result_class=CLIOptions, args=args)
|
||||
args.args = rest
|
||||
if args.debug_config:
|
||||
create_opts(args, debug_config=True)
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
|
||||
|
||||
class Options:
|
||||
pass
|
||||
@ -11,19 +9,23 @@ class Options:
|
||||
|
||||
def generate_stub():
|
||||
from .config_data import all_options
|
||||
from .conf.definition import as_type_stub
|
||||
from .conf.definition import as_type_stub, save_type_stub
|
||||
text = as_type_stub(
|
||||
all_options,
|
||||
special_types={
|
||||
'symbol_map': 'typing.Dict[typing.Tuple[int, int], str]'
|
||||
}
|
||||
)
|
||||
with open(__file__ + 'i', 'w') as f:
|
||||
print(
|
||||
'# Update this file by running: python {}'.format(os.path.relpath(os.path.abspath(__file__))),
|
||||
file=f
|
||||
},
|
||||
preamble_lines=(
|
||||
'from kitty.config import KeyAction',
|
||||
'KeySpec = typing.Tuple[int, bool, int]',
|
||||
'KeyMap = typing.Dict[KeySpec, KeyAction]',
|
||||
),
|
||||
extra_fields=(
|
||||
('keymap', 'KeyMap'),
|
||||
('sequence_map', 'typing.Dict[KeySpec, KeyMap]'),
|
||||
)
|
||||
f.write(text)
|
||||
)
|
||||
save_type_stub(text, __file__)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -3,6 +3,9 @@ import kitty.rgb
|
||||
from kitty.rgb import Color
|
||||
import typing
|
||||
|
||||
from kitty.config import KeyAction
|
||||
KeySpec = typing.Tuple[int, bool, int]
|
||||
KeyMap = typing.Dict[KeySpec, KeyAction]
|
||||
|
||||
class Options:
|
||||
font_family: str
|
||||
@ -380,4 +383,6 @@ class Options:
|
||||
linux_display_server: str
|
||||
kitty_mod: int
|
||||
clear_all_shortcuts: bool
|
||||
kitten_alias: str
|
||||
kitten_alias: str
|
||||
keymap: KeyMap
|
||||
sequence_map: typing.Dict[KeySpec, KeyMap]
|
||||
@ -138,13 +138,17 @@ cli_msg = (
|
||||
).format(appname=appname)
|
||||
|
||||
|
||||
class RCOptions:
|
||||
pass
|
||||
|
||||
|
||||
def parse_rc_args(args):
|
||||
cmds = (' :green:`{}`\n {}'.format(cmap[c].name, cmap[c].short_desc) for c in all_commands)
|
||||
msg = cli_msg + (
|
||||
'\n\n:title:`Commands`:\n{cmds}\n\n'
|
||||
'You can get help for each individual command by using:\n'
|
||||
'{appname} @ :italic:`command` -h').format(appname=appname, cmds='\n'.join(cmds))
|
||||
return parse_args(args[1:], global_options_spec, 'command ...', msg, '{} @'.format(appname))
|
||||
return parse_args(args[1:], global_options_spec, 'command ...', msg, '{} @'.format(appname), result_class=RCOptions)
|
||||
|
||||
|
||||
def main(args):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user