more typing work
This commit is contained in:
parent
8a3e71d3d0
commit
cda1e28b32
36
__main__.py
36
__main__.py
@ -4,54 +4,56 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
|
||||
def icat(args):
|
||||
from kittens.runner import run_kitten
|
||||
def icat(args: List[str]) -> None:
|
||||
from kittens.runner import run_kitten as rk
|
||||
sys.argv = args
|
||||
run_kitten('icat')
|
||||
rk('icat')
|
||||
|
||||
|
||||
def list_fonts(args):
|
||||
def list_fonts(args: List[str]) -> None:
|
||||
from kitty.fonts.list import main as list_main
|
||||
list_main(args)
|
||||
|
||||
|
||||
def remote_control(args):
|
||||
def remote_control(args: List[str]) -> None:
|
||||
from kitty.remote_control import main as rc_main
|
||||
rc_main(args)
|
||||
|
||||
|
||||
def runpy(args):
|
||||
def runpy(args: List[str]) -> None:
|
||||
sys.argv = ['kitty'] + args[2:]
|
||||
exec(args[1])
|
||||
|
||||
|
||||
def hold(args):
|
||||
def hold(args: List[str]) -> None:
|
||||
import subprocess
|
||||
ret = subprocess.Popen(args[1:]).wait()
|
||||
sys.stdin.read()
|
||||
raise SystemExit(ret)
|
||||
|
||||
|
||||
def complete(args):
|
||||
def complete(args: List[str]) -> None:
|
||||
from kitty.complete import main as complete_main
|
||||
complete_main(args[1:], entry_points, namespaced_entry_points)
|
||||
|
||||
|
||||
def launch(args):
|
||||
def launch(args: List[str]) -> None:
|
||||
import runpy
|
||||
sys.argv = args[1:]
|
||||
exe = args[1]
|
||||
if exe.startswith(':'):
|
||||
import shutil
|
||||
exe = shutil.which(exe[1:])
|
||||
if not exe:
|
||||
q = shutil.which(exe[1:])
|
||||
if not q:
|
||||
raise SystemExit('{} not found in PATH'.format(args[1][1:]))
|
||||
exe = q
|
||||
runpy.run_path(exe, run_name='__main__')
|
||||
|
||||
|
||||
def run_kitten(args):
|
||||
def run_kitten(args: List[str]) -> None:
|
||||
try:
|
||||
kitten = args[1]
|
||||
except IndexError:
|
||||
@ -59,11 +61,11 @@ def run_kitten(args):
|
||||
list_kittens()
|
||||
raise SystemExit(1)
|
||||
sys.argv = args[1:]
|
||||
from kittens.runner import run_kitten
|
||||
run_kitten(kitten)
|
||||
from kittens.runner import run_kitten as rk
|
||||
rk(kitten)
|
||||
|
||||
|
||||
def namespaced(args):
|
||||
def namespaced(args: List[str]) -> None:
|
||||
func = namespaced_entry_points[args[1]]
|
||||
func(args[1:])
|
||||
|
||||
@ -84,7 +86,7 @@ namespaced_entry_points['hold'] = hold
|
||||
namespaced_entry_points['complete'] = complete
|
||||
|
||||
|
||||
def setup_openssl_environment():
|
||||
def setup_openssl_environment() -> None:
|
||||
# Workaround for Linux distros that have still failed to get their heads
|
||||
# out of their asses and implement a common location for SSL certificates.
|
||||
# It's not that hard people, there exists a wonderful tool called the symlink
|
||||
@ -98,7 +100,7 @@ def setup_openssl_environment():
|
||||
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_DIR')
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
if getattr(sys, 'frozen', False) and 'darwin' not in sys.platform.lower():
|
||||
setup_openssl_environment()
|
||||
first_arg = '' if len(sys.argv) < 2 else sys.argv[1]
|
||||
|
||||
@ -6,7 +6,9 @@ import os
|
||||
import shlex
|
||||
import sys
|
||||
from functools import lru_cache
|
||||
from typing import Callable, Dict, Iterable, Optional, Tuple
|
||||
from typing import (
|
||||
Any, Callable, Dict, Iterable, List, Optional, Sequence, Set, Tuple
|
||||
)
|
||||
|
||||
from kittens.runner import all_kitten_names, get_kitten_cli_docs
|
||||
|
||||
@ -37,19 +39,20 @@ them into something your shell will understand.
|
||||
|
||||
parsers: Dict[str, Callable] = {}
|
||||
serializers: Dict[str, Callable] = {}
|
||||
MathGroup = Dict[str, str]
|
||||
|
||||
|
||||
def debug(*a, **kw):
|
||||
def debug(*a: Any, **kw: Any) -> None:
|
||||
kw['file'] = sys.stderr
|
||||
print(*a, **kw)
|
||||
|
||||
|
||||
class Completions:
|
||||
|
||||
def __init__(self):
|
||||
self.match_groups = {}
|
||||
self.no_space_groups = set()
|
||||
self.files_groups = set()
|
||||
def __init__(self) -> None:
|
||||
self.match_groups: Dict[str, MathGroup] = {}
|
||||
self.no_space_groups: Set[str] = set()
|
||||
self.files_groups: Set[str] = set()
|
||||
|
||||
|
||||
@lru_cache(maxsize=2)
|
||||
@ -97,40 +100,44 @@ complete -f -c kitty -a "(__kitty_completions)"
|
||||
''',
|
||||
}
|
||||
|
||||
ParseResult = Tuple[List[str], bool]
|
||||
ParserFunc = Callable[[str], ParseResult]
|
||||
SerializerFunc = Callable[[Completions], str]
|
||||
|
||||
def input_parser(func):
|
||||
|
||||
def input_parser(func: ParserFunc) -> ParserFunc:
|
||||
name = func.__name__.split('_')[0]
|
||||
parsers[name] = func
|
||||
return func
|
||||
|
||||
|
||||
def output_serializer(func):
|
||||
def output_serializer(func: SerializerFunc) -> SerializerFunc:
|
||||
name = func.__name__.split('_')[0]
|
||||
serializers[name] = func
|
||||
return func
|
||||
|
||||
|
||||
@input_parser
|
||||
def zsh_input_parser(data):
|
||||
def zsh_input_parser(data: str) -> ParseResult:
|
||||
new_word = data.endswith('\n\n')
|
||||
words = data.rstrip().splitlines()
|
||||
return words, new_word
|
||||
|
||||
|
||||
@input_parser
|
||||
def bash_input_parser(data):
|
||||
def bash_input_parser(data: str) -> ParseResult:
|
||||
new_word = data.endswith('\n\n')
|
||||
words = data.rstrip().splitlines()
|
||||
return words, new_word
|
||||
|
||||
|
||||
@input_parser
|
||||
def fish_input_parser(data):
|
||||
def fish_input_parser(data: str) -> ParseResult:
|
||||
return data.rstrip().splitlines(), True
|
||||
|
||||
|
||||
@output_serializer
|
||||
def zsh_output_serializer(ans):
|
||||
def zsh_output_serializer(ans: Completions) -> str:
|
||||
lines = []
|
||||
for description, matches in ans.match_groups.items():
|
||||
cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote(description)]
|
||||
@ -143,7 +150,7 @@ def zsh_output_serializer(ans):
|
||||
cmd.extend(('-p', shlex.quote(common_prefix)))
|
||||
matches = {k[len(common_prefix):]: v for k, v in matches.items()}
|
||||
cmd.append('--')
|
||||
for word, description in matches.items():
|
||||
for word in matches:
|
||||
cmd.append(shlex.quote(word))
|
||||
lines.append(' '.join(cmd) + ';')
|
||||
# debug('\n'.join(lines))
|
||||
@ -151,7 +158,7 @@ def zsh_output_serializer(ans):
|
||||
|
||||
|
||||
@output_serializer
|
||||
def bash_output_serializer(ans):
|
||||
def bash_output_serializer(ans: Completions) -> str:
|
||||
lines = []
|
||||
for description, matches in ans.match_groups.items():
|
||||
needs_space = description not in ans.no_space_groups
|
||||
@ -164,7 +171,7 @@ def bash_output_serializer(ans):
|
||||
|
||||
|
||||
@output_serializer
|
||||
def fish_output_serializer(ans):
|
||||
def fish_output_serializer(ans: Completions) -> str:
|
||||
lines = []
|
||||
for matches in ans.match_groups.values():
|
||||
for word in matches:
|
||||
@ -174,16 +181,16 @@ def fish_output_serializer(ans):
|
||||
# }}}
|
||||
|
||||
|
||||
def completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points):
|
||||
def completions_for_first_word(ans: Completions, prefix: str, entry_points: Iterable[str], namespaced_entry_points: Iterable[str]) -> None:
|
||||
cmds = ['@' + c for c in remote_control_command_names()]
|
||||
ans.match_groups['Entry points'] = {
|
||||
k: None for k in
|
||||
k: '' for k in
|
||||
list(entry_points) + cmds + ['+' + k for k in namespaced_entry_points]
|
||||
if not prefix or k.startswith(prefix)
|
||||
}
|
||||
|
||||
|
||||
def kitty_cli_opts(ans, prefix=None):
|
||||
def kitty_cli_opts(ans: Completions, prefix: Optional[str] = None) -> None:
|
||||
matches = {}
|
||||
for opt in options_for_completion():
|
||||
if isinstance(opt, str):
|
||||
@ -194,7 +201,7 @@ def kitty_cli_opts(ans, prefix=None):
|
||||
ans.match_groups['Options'] = matches
|
||||
|
||||
|
||||
def complete_kitty_cli_arg(ans, opt, prefix):
|
||||
def complete_kitty_cli_arg(ans: Completions, opt: Optional[OptionDict], prefix: str) -> None:
|
||||
prefix = prefix or ''
|
||||
if not opt:
|
||||
return
|
||||
@ -202,11 +209,11 @@ def complete_kitty_cli_arg(ans, opt, prefix):
|
||||
if dest == 'override':
|
||||
from kitty.config import option_names_for_completion
|
||||
k = 'Config directives'
|
||||
ans.match_groups[k] = {k+'=': None for k in option_names_for_completion() if k.startswith(prefix)}
|
||||
ans.match_groups[k] = {k+'=': '' for k in option_names_for_completion() if k.startswith(prefix)}
|
||||
ans.no_space_groups.add(k)
|
||||
elif dest == 'config':
|
||||
|
||||
def is_conf_file(x):
|
||||
def is_conf_file(x: str) -> bool:
|
||||
if os.path.isdir(x):
|
||||
return True
|
||||
return x.lower().endswith('.conf')
|
||||
@ -229,14 +236,23 @@ def complete_kitty_cli_arg(ans, opt, prefix):
|
||||
complete_files_and_dirs(ans, prefix[len('unix:'):], files_group_name='UNIX sockets', add_prefix='unix:')
|
||||
|
||||
|
||||
def complete_alias_map(ans, words, new_word, option_map, complete_args=None):
|
||||
CompleteArgsFunc = Callable[[Completions, Optional[OptionDict], str], None]
|
||||
|
||||
|
||||
def complete_alias_map(
|
||||
ans: Completions,
|
||||
words: Sequence[str],
|
||||
new_word: str,
|
||||
option_map: Dict[str, OptionDict],
|
||||
complete_args: Optional[CompleteArgsFunc] = None
|
||||
) -> None:
|
||||
expecting_arg = False
|
||||
opt: Optional[OptionDict] = None
|
||||
last_word = words[-1] if words else ''
|
||||
for w in words:
|
||||
if expecting_arg:
|
||||
if w is last_word and not new_word:
|
||||
if opt is not None:
|
||||
if opt is not None and complete_args is not None:
|
||||
complete_args(ans, opt, w)
|
||||
return
|
||||
expecting_arg = False
|
||||
@ -264,7 +280,13 @@ def complete_alias_map(ans, words, new_word, option_map, complete_args=None):
|
||||
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(prefix)}
|
||||
|
||||
|
||||
def complete_cli(ans, words, new_word, seq: OptionSpecSeq, complete_args=lambda *a: None):
|
||||
def complete_cli(
|
||||
ans: Completions,
|
||||
words: Sequence[str],
|
||||
new_word: str,
|
||||
seq: OptionSpecSeq,
|
||||
complete_args: Optional[CompleteArgsFunc] = None
|
||||
) -> None:
|
||||
option_map = {}
|
||||
for opt in seq:
|
||||
if not isinstance(opt, str):
|
||||
@ -273,18 +295,18 @@ def complete_cli(ans, words, new_word, seq: OptionSpecSeq, complete_args=lambda
|
||||
complete_alias_map(ans, words, new_word, option_map, complete_args)
|
||||
|
||||
|
||||
def complete_remote_command(ans, cmd_name, words, new_word):
|
||||
def complete_remote_command(ans: Completions, cmd_name: str, words: Sequence[str], new_word: str) -> None:
|
||||
aliases, alias_map = options_for_cmd(cmd_name)
|
||||
if not alias_map:
|
||||
return
|
||||
args_completer = None
|
||||
args_completer: Optional[CompleteArgsFunc] = None
|
||||
args_completion = command_for_name(cmd_name).args_completion
|
||||
if args_completion and 'files' in args_completion:
|
||||
args_completer = remote_files_completer(args_completion['files'])
|
||||
complete_alias_map(ans, words, new_word, alias_map, complete_args=args_completer)
|
||||
|
||||
|
||||
def path_completion(prefix=''):
|
||||
def path_completion(prefix: str = '') -> Tuple[List[str], List[str]]:
|
||||
prefix = prefix.replace(r'\ ', ' ')
|
||||
dirs, files = [], []
|
||||
base = '.'
|
||||
@ -314,61 +336,70 @@ def path_completion(prefix=''):
|
||||
return dirs, files
|
||||
|
||||
|
||||
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=None, add_prefix=None):
|
||||
dirs, files = path_completion(prefix or '')
|
||||
files = filter(predicate, files)
|
||||
def complete_files_and_dirs(
|
||||
ans: Completions,
|
||||
prefix: str,
|
||||
files_group_name: str = 'Files',
|
||||
predicate: Optional[Callable[[str], bool]] = None,
|
||||
add_prefix: Optional[str] = None
|
||||
) -> None:
|
||||
dirs, files_ = path_completion(prefix or '')
|
||||
files = filter(predicate, files_)
|
||||
if add_prefix:
|
||||
dirs = (add_prefix + x for x in dirs)
|
||||
dirs = list(add_prefix + x for x in dirs)
|
||||
files = (add_prefix + x for x in files)
|
||||
|
||||
if dirs:
|
||||
ans.match_groups['Directories'] = dict.fromkeys(dirs)
|
||||
ans.files_groups.add('Directories'), ans.no_space_groups.add('Directories')
|
||||
ans.files_groups.add('Directories')
|
||||
ans.no_space_groups.add('Directories')
|
||||
if files:
|
||||
ans.match_groups[files_group_name] = dict.fromkeys(files)
|
||||
ans.files_groups.add(files_group_name)
|
||||
|
||||
|
||||
def complete_icat_args(ans, opt, prefix):
|
||||
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str) -> None:
|
||||
from mimetypes import guess_type
|
||||
|
||||
def icat_file_predicate(filename):
|
||||
def icat_file_predicate(filename: str) -> bool:
|
||||
mt = guess_type(filename)[0]
|
||||
if mt and mt.startswith('image/'):
|
||||
return True
|
||||
return False
|
||||
|
||||
if opt is None:
|
||||
complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate)
|
||||
|
||||
|
||||
def remote_files_completer(spec):
|
||||
def remote_files_completer(spec: Tuple[str, Tuple[str, ...]]) -> CompleteArgsFunc:
|
||||
name, matchers = spec
|
||||
|
||||
def complete_files_map(ans, opt, prefix):
|
||||
def complete_files_map(ans: Completions, opt: Optional[OptionDict], prefix: str) -> None:
|
||||
|
||||
def predicate(filename):
|
||||
def predicate(filename: str) -> bool:
|
||||
for m in matchers:
|
||||
if isinstance(m, str):
|
||||
from fnmatch import fnmatch
|
||||
return fnmatch(filename, m)
|
||||
return False
|
||||
|
||||
if opt is None:
|
||||
complete_files_and_dirs(ans, prefix, name, predicate)
|
||||
return complete_files_map
|
||||
|
||||
|
||||
def config_file_predicate(filename):
|
||||
def config_file_predicate(filename: str) -> bool:
|
||||
return filename.endswith('.conf')
|
||||
|
||||
|
||||
def complete_diff_args(ans, opt, prefix):
|
||||
def complete_diff_args(ans: Completions, opt: Optional[OptionDict], prefix: str) -> None:
|
||||
if opt is None:
|
||||
complete_files_and_dirs(ans, prefix, 'Files')
|
||||
elif opt['dest'] == 'config':
|
||||
complete_files_and_dirs(ans, prefix, 'Config Files', config_file_predicate)
|
||||
|
||||
|
||||
def complete_kitten(ans, kitten, words, new_word):
|
||||
def complete_kitten(ans: Completions, kitten: str, words: Sequence[str], new_word: str) -> None:
|
||||
try:
|
||||
cd = get_kitten_cli_docs(kitten)
|
||||
except SystemExit:
|
||||
@ -388,7 +419,7 @@ def complete_kitten(ans, kitten, words, new_word):
|
||||
}.get(kitten))
|
||||
|
||||
|
||||
def find_completions(words, new_word, entry_points, namespaced_entry_points):
|
||||
def find_completions(words: Sequence[str], new_word: str, entry_points: Iterable[str], namespaced_entry_points: Iterable[str]) -> Completions:
|
||||
ans = Completions()
|
||||
if not words or words[0] != 'kitty':
|
||||
return ans
|
||||
@ -401,20 +432,20 @@ def find_completions(words, new_word, entry_points, namespaced_entry_points):
|
||||
if words[0] == '@':
|
||||
if len(words) == 1 or (len(words) == 2 and not new_word):
|
||||
prefix = words[1] if len(words) > 1 else ''
|
||||
ans.match_groups['Remote control commands'] = {c: None for c in remote_control_command_names() if c.startswith(prefix)}
|
||||
ans.match_groups['Remote control commands'] = {c: '' for c in remote_control_command_names() if c.startswith(prefix)}
|
||||
else:
|
||||
complete_remote_command(ans, words[1], words[2:], new_word)
|
||||
return ans
|
||||
if words[0].startswith('@'):
|
||||
if len(words) == 1 and not new_word:
|
||||
prefix = words[0]
|
||||
ans.match_groups['Remote control commands'] = {'@' + c: None for c in remote_control_command_names() if c.startswith(prefix)}
|
||||
ans.match_groups['Remote control commands'] = {'@' + c: '' for c in remote_control_command_names() if c.startswith(prefix)}
|
||||
else:
|
||||
complete_remote_command(ans, words[0][1:], words[1:], new_word)
|
||||
if words[0] == '+':
|
||||
if len(words) == 1 or (len(words) == 2 and not new_word):
|
||||
prefix = words[1] if len(words) > 1 else ''
|
||||
ans.match_groups['Entry points'] = {c: None for c in namespaced_entry_points if c.startswith(prefix)}
|
||||
ans.match_groups['Entry points'] = {c: '' for c in namespaced_entry_points if c.startswith(prefix)}
|
||||
else:
|
||||
if words[1] == 'kitten':
|
||||
if len(words) == 2 or (len(words) == 3 and not new_word):
|
||||
@ -429,7 +460,7 @@ def find_completions(words, new_word, entry_points, namespaced_entry_points):
|
||||
ans.match_groups['Kittens'] = dict.fromkeys(all_kitten_names())
|
||||
else:
|
||||
prefix = words[0]
|
||||
ans.match_groups['Entry points'] = {c: None for c in namespaced_entry_points if c.startswith(prefix)}
|
||||
ans.match_groups['Entry points'] = {c: '' for c in namespaced_entry_points if c.startswith(prefix)}
|
||||
else:
|
||||
if len(words) == 2 and not new_word:
|
||||
ans.match_groups['Kittens'] = dict.fromkeys(k for k in all_kitten_names() if k.startswith(words[1]))
|
||||
@ -442,11 +473,11 @@ def find_completions(words, new_word, entry_points, namespaced_entry_points):
|
||||
return ans
|
||||
|
||||
|
||||
def setup(cstyle):
|
||||
def setup(cstyle: str) -> None:
|
||||
print(completion_scripts[cstyle])
|
||||
|
||||
|
||||
def main(args, entry_points, namespaced_entry_points):
|
||||
def main(args: Sequence[str], entry_points: Iterable[str], namespaced_entry_points: Iterable[str]) -> None:
|
||||
if not args:
|
||||
raise SystemExit('Must specify completion style')
|
||||
cstyle = args[0]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user