1
0
mirror of https://github.com/httpie/cli.git synced 2025-06-27 00:51:16 +02:00
Files
httpie-cli/extras/scripts/completion/generate_completion.py

232 lines
5.9 KiB
Python
Raw Normal View History

2022-04-14 22:35:09 +03:00
import functools
import string
import textwrap
from atexit import register
from pathlib import Path
from typing import Any, Callable, Dict, TypeVar
2022-04-14 22:35:09 +03:00
from completion_flow import generate_flow
2022-04-14 22:35:09 +03:00
from jinja2 import Template
from httpie.cli.constants import SEPARATOR_FILE_UPLOAD
from httpie.cli.definition import options
from httpie.cli.options import Argument, ParserSpec
2022-05-18 18:30:46 +03:00
T = TypeVar('T')
2022-04-14 22:35:09 +03:00
2022-05-18 18:30:46 +03:00
EXTRAS_DIR = Path(__file__).parent.parent.parent
COMPLETION_DIR = EXTRAS_DIR / 'completion'
TEMPLATES_DIR = COMPLETION_DIR / 'templates'
2022-04-14 22:35:09 +03:00
2022-05-18 18:30:46 +03:00
COMPLETION_TEMPLATE_BASE = TEMPLATES_DIR / 'completion'
COMPLETION_SCRIPT_BASE = COMPLETION_DIR / 'completion'
2022-04-14 22:35:09 +03:00
COMMON_HTTP_METHODS = [
2022-05-18 18:30:46 +03:00
'GET',
'POST',
'PUT',
'DELETE',
'HEAD',
'OPTIONS',
'PATCH',
'TRACE',
'CONNECT',
2022-04-14 22:35:09 +03:00
]
2022-05-16 18:09:59 +03:00
COMPLETERS = {}
2022-04-14 22:35:09 +03:00
2022-05-18 18:30:46 +03:00
2022-04-14 22:35:09 +03:00
def use_template(shell_type):
def decorator(func):
@functools.wraps(func)
def wrapper(spec):
template_file = COMPLETION_TEMPLATE_BASE.with_suffix(
2022-05-18 18:30:46 +03:00
f'.{shell_type}.j2'
2022-04-14 22:35:09 +03:00
)
compiletion_script_file = COMPLETION_SCRIPT_BASE.with_suffix(
2022-05-18 18:30:46 +03:00
f'.{shell_type}'
2022-04-14 22:35:09 +03:00
)
jinja_template = Template(template_file.read_text())
jinja_template.globals.update(prepare_objects(spec))
extra_variables = func(spec)
compiletion_script_file.write_text(
jinja_template.render(**extra_variables)
)
2022-05-16 18:09:59 +03:00
COMPLETERS[shell_type] = wrapper
2022-04-14 22:35:09 +03:00
return wrapper
return decorator
BASE_FUNCTIONS = {}
def prepare_objects(spec: ParserSpec) -> Dict[str, Any]:
global_objects = {
**BASE_FUNCTIONS,
}
2022-05-18 18:30:46 +03:00
global_objects['request_items'] = find_argument_by_target_name(
spec, 'REQUEST_ITEM'
2022-04-14 22:35:09 +03:00
)
2022-05-18 18:30:46 +03:00
global_objects['arguments'] = [
2022-04-14 22:35:09 +03:00
argument
for group in spec.groups
for argument in group.arguments
if not argument.is_hidden
if not argument.is_positional
]
2022-05-18 18:30:46 +03:00
global_objects['methods'] = COMMON_HTTP_METHODS
global_objects['generate_flow'] = generate_flow
2022-04-14 22:35:09 +03:00
return global_objects
def register_function(func: T) -> T:
BASE_FUNCTIONS[func.__name__] = func
return func
@register_function
def is_file_based_operator(operator: str) -> bool:
return operator in {SEPARATOR_FILE_UPLOAD}
def escape_zsh(text: str) -> str:
2022-05-18 18:30:46 +03:00
return text.replace(':', '\\:')
2022-04-14 22:35:09 +03:00
def serialize_argument_to_zsh(argument):
2022-05-16 18:09:59 +03:00
# The argument format is the following:
2022-04-14 22:35:09 +03:00
# $prefix'$alias$has_value[$short_desc]:$metavar$:($choice_1 $choice_2)'
2022-05-18 18:30:46 +03:00
prefix = ''
2022-04-14 22:35:09 +03:00
declaration = []
2022-05-18 18:30:46 +03:00
has_choices = 'choices' in argument.configuration
2022-04-14 22:35:09 +03:00
# The format for the argument declaration canges depending on the
# the number of aliases. For a single $alias, we'll embed it directly
# in the declaration string, but for multiple of them, we'll use a
# $prefix.
if len(argument.aliases) > 1:
2022-05-18 18:30:46 +03:00
prefix = '{' + ','.join(argument.aliases) + '}'
2022-04-14 22:35:09 +03:00
else:
declaration.append(argument.aliases[0])
if not argument.is_flag:
2022-05-18 18:30:46 +03:00
declaration.append('=')
2022-04-14 22:35:09 +03:00
2022-05-18 18:30:46 +03:00
declaration.append('[' + argument.short_help + ']')
2022-04-14 22:35:09 +03:00
2022-05-18 18:30:46 +03:00
if 'metavar' in argument.configuration:
2022-04-14 22:35:09 +03:00
metavar = argument.metavar
elif has_choices:
# Choices always require a metavar, so even if we don't have one
# we can generate it from the argument aliases.
metavar = (
max(argument.aliases, key=len)
2022-05-18 18:30:46 +03:00
.lstrip('-')
.replace('-', '_')
2022-04-14 22:35:09 +03:00
.upper()
)
else:
metavar = None
if metavar:
# Strip out any whitespace, and escape any characters that would
# conflict with the shell.
2022-05-18 18:30:46 +03:00
metavar = escape_zsh(metavar.strip(' '))
declaration.append(f':{metavar}:')
2022-04-14 22:35:09 +03:00
if has_choices:
2022-05-18 18:30:46 +03:00
declaration.append('(' + ' '.join(argument.choices) + ')')
2022-04-14 22:35:09 +03:00
return prefix + f"'{''.join(declaration)}'"
2022-05-18 18:30:46 +03:00
def serialize_argument_to_fish(argument):
2022-05-16 18:09:59 +03:00
# The argument format is defined here
# <https://fishshell.com/docs/current/completions.html>
declaration = [
'complete',
'-c',
2022-05-18 18:30:46 +03:00
'http',
2022-05-16 18:09:59 +03:00
]
2022-05-18 18:30:46 +03:00
try:
short_form, long_form = argument.aliases
except ValueError:
short_form = None
(long_form,) = argument.aliases
if short_form:
declaration.append('-s')
declaration.append(short_form)
declaration.append('-l')
declaration.append(long_form)
if 'choices' in argument.configuration:
declaration.append('-xa')
declaration.append('"' + ' '.join(argument.choices) + '"')
elif 'lazy_choices' in argument.configuration:
declaration.append('-x')
declaration.append('-d')
declaration.append("'" + argument.short_help.replace("'", r"\'") + "'")
return '\t'.join(declaration)
2022-04-14 22:35:09 +03:00
def find_argument_by_target_name(spec: ParserSpec, name: str) -> Argument:
for group in spec.groups:
for argument in group.arguments:
if argument.aliases:
targets = argument.aliases
else:
targets = [argument.metavar]
if name in targets:
return argument
2022-05-18 18:30:46 +03:00
raise ValueError(f'Could not find argument with name {name}')
2022-04-14 22:35:09 +03:00
2022-05-18 18:30:46 +03:00
@use_template('zsh')
2022-04-14 22:35:09 +03:00
def zsh_completer(spec: ParserSpec) -> Dict[str, Any]:
from zsh import compile_zsh
2022-05-16 18:09:59 +03:00
return {
2022-05-18 18:30:46 +03:00
'escape_zsh': escape_zsh,
'serialize_argument_to_zsh': serialize_argument_to_zsh,
'compile_zsh': compile_zsh,
2022-05-16 18:09:59 +03:00
}
2022-05-18 18:30:46 +03:00
@use_template('fish')
2022-05-16 18:09:59 +03:00
def fish_completer(spec: ParserSpec) -> Dict[str, Any]:
2022-04-14 22:35:09 +03:00
return {
2022-05-18 18:30:46 +03:00
'serialize_argument_to_fish': serialize_argument_to_fish,
2022-04-14 22:35:09 +03:00
}
@use_template('bash')
def fish_completer(spec: ParserSpec) -> Dict[str, Any]:
from bash import compile_bash
return {
'compile_bash': compile_bash,
}
2022-05-16 18:09:59 +03:00
def main():
for shell_type, completer in COMPLETERS.items():
2022-05-18 18:30:46 +03:00
print(f'Generating {shell_type} completer.')
2022-05-16 18:09:59 +03:00
completer(options)
2022-05-18 18:30:46 +03:00
if __name__ == '__main__':
2022-05-16 18:09:59 +03:00
main()