Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shell completion support via argcomplete #1491

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -2537,6 +2537,17 @@ The two modes, `--pretty=all` (default for terminal) and `--pretty=none` (defaul
In the future, the command line syntax and some of the `--OPTIONS` may change slightly, as HTTPie improves and new features are added.
All changes are recorded in the [change log](#change-log).

### Shell completion

Shell completion is provided using the argcomplete library. It is suggested
to load the completion without falling back to the shell defaults in order
to avoid default completions in contexts where they do not apply. For example
for bash:

```bash
$ eval "$(register-python-argcomplete --complete-arguments -- http https)"
```

### Community and Support

HTTPie has the following community channels:
@@ -2549,10 +2560,11 @@ HTTPie has the following community channels:

#### Dependencies

Under the hood, HTTPie uses these two amazing libraries:
Under the hood, HTTPie uses these three amazing libraries:

- [Requests](https://requests.readthedocs.io/en/latest/) — Python HTTP library for humans
- [Pygments](https://pygments.org/) — Python syntax highlighter
- [argcomplete](https://github.com/kislyuk/argcomplete) — Shell completion generator

#### HTTPie friends

1 change: 1 addition & 0 deletions httpie/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# PYTHON_ARGCOMPLETE_OK
"""The main entry point. Invoke as `http' or `python -m httpie'.

"""
34 changes: 30 additions & 4 deletions httpie/cli/definition.py
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@
import textwrap
from argparse import FileType

from argcomplete.completers import ChoicesCompleter, FilesCompleter

from httpie import __doc__, __version__
from httpie.cli.argtypes import (KeyValueArgType, SessionNameValidator,
SSLCredentials, readable_file_arg,
@@ -64,6 +66,7 @@
$ http example.org hello=world # => POST

""",
completer=ChoicesCompleter(('GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'))
)
positional_arguments.add_argument(
dest='url',
@@ -79,6 +82,7 @@
$ http :/foo # => http://localhost/foo

""",
completer=ChoicesCompleter(()),
)
positional_arguments.add_argument(
dest='request_items',
@@ -136,6 +140,7 @@
field-name-with\:colon=value

""",
completer=ChoicesCompleter(()),
)

#######################################################################
@@ -189,7 +194,8 @@
short_help=(
'Specify a custom boundary string for multipart/form-data requests. '
'Only has effect only together with --form.'
)
),
completer=ChoicesCompleter(()),
)
content_types.add_argument(
'--raw',
@@ -351,6 +357,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):
--response-charset=big5

""",
completer=ChoicesCompleter(()),
)
output_processing.add_argument(
'--response-mime',
@@ -364,6 +371,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):
--response-mime=text/xml

""",
completer=ChoicesCompleter(()),
)
output_processing.add_argument(
'--format-options',
@@ -389,6 +397,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):
f' {option}' for option in DEFAULT_FORMAT_OPTIONS
).strip()
),
completer=ChoicesCompleter(()),
)

#######################################################################
@@ -418,6 +427,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):
response body is printed by default.

""",
completer=ChoicesCompleter(()),
)
output_options.add_argument(
'--headers',
@@ -492,6 +502,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):
dest='output_options_history',
metavar='WHAT',
help=Qualifiers.SUPPRESS,
completer=ChoicesCompleter(()),
)
output_options.add_argument(
'--stream',
@@ -526,6 +537,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):
printed to stderr.

""",
completer=FilesCompleter(),
)

output_options.add_argument(
@@ -597,6 +609,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):

https://httpie.io/docs/cli/config-file-directory
""",
completer=FilesCompleter(('json',)),
)
sessions.add_argument(
'--session-read-only',
@@ -608,6 +621,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):
exchange.

""",
completer=FilesCompleter(('json',)),
)

#######################################################################
@@ -672,6 +686,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
(-a username), HTTPie will prompt for the password.

""",
completer=ChoicesCompleter(()),
)
authentication.add_argument(
'--auth-type',
@@ -717,6 +732,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
and $HTTPS_proxy are supported as well.

""",
completer=ChoicesCompleter(()),
)
network.add_argument(
'--follow',
@@ -735,6 +751,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
By default, requests have a limit of 30 redirects (works with --follow).

""",
completer=ChoicesCompleter(()),
)
network.add_argument(
'--max-headers',
@@ -743,7 +760,8 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
short_help=(
'The maximum number of response headers to be read before '
'giving up (default 0, i.e., no limit).'
)
),
completer=ChoicesCompleter(()),
)

network.add_argument(
@@ -761,6 +779,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
the underlying socket for timeout seconds).

""",
completer=ChoicesCompleter(()),
)
network.add_argument(
'--check-status',
@@ -811,6 +830,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment
variable instead.)
""",
completer=ChoicesCompleter(('yes', 'no')),
)
ssl.add_argument(
'--ssl',
@@ -825,6 +845,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
are shown here).

""",
completer=ChoicesCompleter(()),
)
ssl.add_argument(
'--ciphers',
@@ -837,6 +858,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
{DEFAULT_SSL_CIPHERS}

""",
completer=ChoicesCompleter(()),
)
ssl.add_argument(
'--cert',
@@ -849,6 +871,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
specify --cert-key separately.

""",
completer=FilesCompleter(('crt', 'cert', 'pem')),
)
ssl.add_argument(
'--cert-key',
@@ -860,6 +883,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
certificate file does not contain the private key.

""",
completer=FilesCompleter(('key', 'pem')),
)

ssl.add_argument(
@@ -871,7 +895,8 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
The passphrase to be used to with the given private key. Only needed if --cert-key
is given and the key file requires a passphrase.
If not provided, you’ll be prompted interactively.
"""
""",
completer=ChoicesCompleter(()),
)

#######################################################################
@@ -913,7 +938,8 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
troubleshooting.add_argument(
'--default-scheme',
default='http',
short_help='The default scheme to use if not specified in the URL.'
short_help='The default scheme to use if not specified in the URL.',
completer=ChoicesCompleter(('http', 'https')),
)
troubleshooting.add_argument(
'--debug',
6 changes: 4 additions & 2 deletions httpie/cli/options.py
Original file line number Diff line number Diff line change
@@ -187,7 +187,7 @@ def __getattr__(self, attribute_name):
Qualifiers.ZERO_OR_MORE: argparse.ZERO_OR_MORE,
Qualifiers.ONE_OR_MORE: argparse.ONE_OR_MORE
}
ARGPARSE_IGNORE_KEYS = ('short_help', 'nested_options')
ARGPARSE_IGNORE_KEYS = ('short_help', 'nested_options', 'completer')


def to_argparse(
@@ -211,12 +211,14 @@ def to_argparse(
concrete_group = concrete_group.add_mutually_exclusive_group(required=False)

for abstract_argument in abstract_group.arguments:
concrete_group.add_argument(
argument = concrete_group.add_argument(
*abstract_argument.aliases,
**drop_keys(map_qualifiers(
abstract_argument.configuration, ARGPARSE_QUALIFIER_MAP
), ARGPARSE_IGNORE_KEYS)
)
if 'completer' in abstract_argument.configuration:
argument.completer = abstract_argument.configuration['completer']

return concrete_parser

3 changes: 3 additions & 0 deletions httpie/core.py
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
import socket
from typing import List, Optional, Union, Callable

import argcomplete
import requests
from pygments import __version__ as pygments_version
from requests import __version__ as requests_version
@@ -73,6 +74,8 @@ def handle_generic_error(e, annotation=None):

exit_status = ExitStatus.SUCCESS

argcomplete.autocomplete(parser)

try:
parsed_args = parser.parse_args(
args=args,
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -40,7 +40,8 @@
'multidict>=4.7.0',
'setuptools',
'importlib-metadata>=1.4.0; python_version < "3.8"',
'rich>=9.10.0'
'rich>=9.10.0',
'argcomplete'
]
install_requires_win_only = [
'colorama>=0.2.4',