Implement support for bash & completion flow generation

This commit is contained in:
Batuhan Taskaya 2022-05-20 10:42:38 +03:00
parent b8e0be241c
commit ee5fc59c51
9 changed files with 218 additions and 54 deletions

View File

@ -0,0 +1,53 @@
METHODS=("GET" "POST" "PUT" "DELETE" "HEAD" "OPTIONS" "PATCH" "TRACE" "CONNECT" )
NORMARG=1 # TO-DO: dynamically calculate this?
_http_complete() {
local cur_word=${COMP_WORDS[COMP_CWORD]}
local prev_word=${COMP_WORDS[COMP_CWORD - 1]}
if [[ "$cur_word" == -* ]]; then
_http_complete_options "$cur_word"
else
if (( COMP_CWORD == NORMARG + 0 )); then
_http_complete_methods "$cur_word"
fi
if (( COMP_CWORD == NORMARG + 0 )); then
_http_complete_url "$cur_word"
fi
if (( COMP_CWORD == NORMARG + 1 )) && [[ " ${METHODS[*]} " =~ " ${prev_word} " ]]; then
_http_complete_url "$cur_word"
fi
if (( COMP_CWORD >= NORMARG + 2 )); then
_httpie_complete_request_item "$cur_word"
fi
if (( COMP_CWORD >= NORMARG + 1 )) && ! [[ " ${METHODS[*]} " =~ " ${prev_word} " ]]; then
_httpie_complete_request_item "$cur_word"
fi
fi
}
complete -o default -F _http_complete http httpie.http httpie.https https
_http_complete_methods() {
local cur_word=$1
local options="GET POST PUT DELETE HEAD OPTIONS PATCH TRACE CONNECT"
COMPREPLY+=( $( compgen -W "$options" -- "$cur_word" ) )
}
_http_complete_url() {
local cur_word=$1
local options="http:// https://"
COMPREPLY+=( $( compgen -W "$options" -- "$cur_word" ) )
}
_httpie_complete_request_item() {
local cur_word=$1
COMPREPLY+=("==" "=" ":=" ":=@")
}
_http_complete_options() {
local cur_word=$1
local options="--json -j --form -f --multipart --boundary --raw --compress -x --pretty --style -s --unsorted --sorted --response-charset --response-mime --format-options --print -p --headers -h --meta -m --body -b --verbose -v --all --stream -S --output -o --download -d --continue -c --quiet -q --session --session-read-only --auth -a --auth-type -A --ignore-netrc --offline --proxy --follow -F --max-redirects --max-headers --timeout --check-status --path-as-is --chunked --verify --ssl --ciphers --cert --cert-key --cert-key-pass --ignore-stdin -I --help --manual --version --traceback --default-scheme --debug "
COMPREPLY=( $( compgen -W "$options" -- "$cur_word" ) )
}

View File

@ -13,21 +13,22 @@ _httpie_params () {
if ! [[ $current == -* ]]; then if ! [[ $current == -* ]]; then
if (( CURRENT == NORMARG + 0 )); then if (( CURRENT == NORMARG + 0 )); then
_httpie_method && ret=0 _httpie_method && ret=0
fi
if (( CURRENT == NORMARG + 0 )); then
_httpie_url && ret=0
fi
if (( CURRENT == NORMARG + 1 )) && [[ ${METHODS[(ie)$predecessor]} -le ${#METHODS} ]]; then
_httpie_url && ret=0
fi
if (( CURRENT >= NORMARG + 2 )); then
_httpie_request_item && ret=0
fi
if (( CURRENT >= NORMARG + 1 )) && ! [[ ${METHODS[(ie)$predecessor]} -le ${#METHODS} ]]; then
_httpie_request_item && ret=0
fi
fi fi
if (( CURRENT == NORMARG + 0 )); then
_httpie_url && ret=0
fi
if (( CURRENT == NORMARG + 1 )) && [[ ${METHODS[(ie)$predecessor]} -le ${#METHODS} ]]; then
_httpie_url && ret=0
fi
if (( CURRENT >= NORMARG + 2 )); then
_httpie_request_item && ret=0
fi
if (( CURRENT >= NORMARG + 1 )) && ! [[ ${METHODS[(ie)$predecessor]} -le ${#METHODS} ]]; then
_httpie_request_item && ret=0
fi
fi
return $ret return $ret

View File

@ -0,0 +1,40 @@
METHODS=({% for method in methods -%} "{{ method }}" {% endfor -%})
NORMARG=1 # TO-DO: dynamically calculate this?
_http_complete() {
local cur_word=${COMP_WORDS[COMP_CWORD]}
local prev_word=${COMP_WORDS[COMP_CWORD - 1]}
if [[ "$cur_word" == -* ]]; then
_http_complete_options "$cur_word"
else
{% for flow_item in generate_flow() -%}
{{ compile_bash(flow_item) | indent(width=8) }}
{% endfor %}
fi
}
complete -o default -F _http_complete http httpie.http httpie.https https
_http_complete_methods() {
local cur_word=$1
local options="{{' '.join(methods)}}"
COMPREPLY+=( $( compgen -W "$options" -- "$cur_word" ) )
}
_http_complete_url() {
local cur_word=$1
local options="http:// https://"
COMPREPLY+=( $( compgen -W "$options" -- "$cur_word" ) )
}
_httpie_complete_request_item() {
local cur_word=$1
COMPREPLY+=("==" "=" ":=" ":=@")
}
_http_complete_options() {
local cur_word=$1
local options="{% for argument in arguments -%} {{ ' '.join(argument.aliases) }} {% endfor -%}"
COMPREPLY=( $( compgen -W "$options" -- "$cur_word" ) )
}

View File

@ -13,8 +13,8 @@ _httpie_params () {
if ! [[ $current == -* ]]; then if ! [[ $current == -* ]]; then
{% for flow_item in generate_flow() -%} {% for flow_item in generate_flow() -%}
{{ compile_zsh(flow_item) }} {{ compile_zsh(flow_item) | indent(width=8) }}
{% endfor -%} {% endfor %}
fi fi
return $ret return $ret

View File

@ -1,20 +0,0 @@
_http_complete() {
local cur_word=${COMP_WORDS[COMP_CWORD]}
local prev_word=${COMP_WORDS[COMP_CWORD - 1]}
if [[ "$cur_word" == -* ]]; then
_http_complete_options "$cur_word"
fi
}
complete -o default -F _http_complete http httpie.http httpie.https https
_http_complete_options() {
local cur_word=$1
local options="-j --json -f --form --pretty -s --style -p --print
-v --verbose -h --headers -b --body -S --stream -o --output -d --download
-c --continue --session --session-read-only -a --auth --auth-type --proxy
--follow --verify --cert --cert-key --timeout --check-status --ignore-stdin
--help --version --traceback --debug --raw"
COMPREPLY=( $( compgen -W "$options" -- "$cur_word" ) )
}

View File

@ -0,0 +1,83 @@
from enum import Enum
from functools import singledispatch
from completion_flow import (
And,
Check,
Condition,
If,
Node,
Not,
Suggest,
Suggestion,
Variable,
generate_flow,
)
class BashVariable(str, Enum):
CURRENT = 'COMP_CWORD'
NORMARG = 'NORMARG'
CURRENT_WORD = 'cur_word'
PREDECESSOR = 'prev_word'
METHODS = 'METHODS'
SUGGESTION_TO_FUNCTION = {
Suggestion.METHOD: '_http_complete_methods',
Suggestion.URL: '_http_complete_url',
Suggestion.REQUEST_ITEM: '_httpie_complete_request_item',
}
@singledispatch
def compile_bash(node: Node) -> ...:
raise NotImplementedError(f'{type(node)} is not supported')
@compile_bash.register(If)
def compile_if(node: If) -> str:
check = compile_bash(node.check)
action = compile_bash(node.action)
return f'if {check}; then\n {action}\nfi'
@compile_bash.register(Check)
def compile_check(node: Check) -> str:
args = [
BashVariable(arg.name) if isinstance(arg, Variable) else arg
for arg in node.args
]
if node.condition is Condition.POSITION_EQ:
return f'(( {BashVariable.CURRENT} == {BashVariable.NORMARG} + {args[0]} ))'
elif node.condition is Condition.POSITION_GE:
return f'(( {BashVariable.CURRENT} >= {BashVariable.NORMARG} + {args[0]} ))'
elif node.condition is Condition.CONTAINS_PREDECESSOR:
parts = [
'[[ ',
'" ${',
BashVariable.METHODS,
'[*]} " =~ " ${',
BashVariable.PREDECESSOR,
'} " ]]',
]
return ''.join(parts)
@compile_bash.register(And)
def compile_and(node: And) -> str:
return ' && '.join(compile_bash(check) for check in node.checks)
@compile_bash.register(Not)
def compile_not(node: Not) -> str:
return f'! {compile_bash(node.check)}'
@compile_bash.register(Suggest)
def compile_suggest(node: Suggest) -> str:
return (
SUGGESTION_TO_FUNCTION[node.suggestion]
+ f' "${BashVariable.CURRENT_WORD}"'
)

View File

@ -1,6 +1,6 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum, auto from enum import Enum, auto
from typing import List, Iterator from typing import Iterator, List
class Condition(Enum): class Condition(Enum):

View File

@ -1,18 +1,17 @@
from atexit import register
import functools import functools
import string import string
import textwrap import textwrap
from atexit import register
from jinja2 import Template
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Callable, TypeVar from typing import Any, Callable, Dict, TypeVar
from completion_flow import generate_flow
from jinja2 import Template
from httpie.cli.constants import SEPARATOR_FILE_UPLOAD from httpie.cli.constants import SEPARATOR_FILE_UPLOAD
from httpie.cli.definition import options from httpie.cli.definition import options
from httpie.cli.options import Argument, ParserSpec from httpie.cli.options import Argument, ParserSpec
from completion_flow import generate_flow
T = TypeVar('T') T = TypeVar('T')
EXTRAS_DIR = Path(__file__).parent.parent.parent EXTRAS_DIR = Path(__file__).parent.parent.parent
@ -202,7 +201,7 @@ def zsh_completer(spec: ParserSpec) -> Dict[str, Any]:
return { return {
'escape_zsh': escape_zsh, 'escape_zsh': escape_zsh,
'serialize_argument_to_zsh': serialize_argument_to_zsh, 'serialize_argument_to_zsh': serialize_argument_to_zsh,
'compile_zsh': compile_zsh 'compile_zsh': compile_zsh,
} }
@ -213,6 +212,15 @@ def fish_completer(spec: ParserSpec) -> Dict[str, Any]:
} }
@use_template('bash')
def fish_completer(spec: ParserSpec) -> Dict[str, Any]:
from bash import compile_bash
return {
'compile_bash': compile_bash,
}
def main(): def main():
for shell_type, completer in COMPLETERS.items(): for shell_type, completer in COMPLETERS.items():
print(f'Generating {shell_type} completer.') print(f'Generating {shell_type} completer.')

View File

@ -1,17 +1,16 @@
from functools import singledispatch
from enum import Enum from enum import Enum
from lib2to3.pgen2.pgen import generate_grammar from functools import singledispatch
from completion_flow import ( from completion_flow import (
Node,
Check,
Suggest,
Variable,
Condition,
Suggestion,
If,
And, And,
Check,
Condition,
If,
Node,
Not, Not,
generate_flow, Suggest,
Suggestion,
Variable,
) )