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
Show file tree
Hide file tree
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
Expand Up @@ -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:
Expand All @@ -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

Expand Down
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'.

"""
Expand Down
34 changes: 30 additions & 4 deletions httpie/cli/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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',
Expand All @@ -79,6 +82,7 @@
$ http :/foo # => http://localhost/foo

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

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

#######################################################################
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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(()),
)

#######################################################################
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -526,6 +537,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):
printed to stderr.

""",
completer=FilesCompleter(),
)

output_options.add_argument(
Expand Down Expand Up @@ -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',
Expand All @@ -608,6 +621,7 @@ def format_style_help(available_styles, *, isolation_mode: bool = False):
exchange.

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

#######################################################################
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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(
Expand All @@ -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',
Expand Down Expand Up @@ -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',
Expand All @@ -825,6 +845,7 @@ def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
are shown here).

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

""",
completer=ChoicesCompleter(()),
)
ssl.add_argument(
'--cert',
Expand All @@ -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',
Expand All @@ -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(
Expand All @@ -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(()),
)

#######################################################################
Expand Down Expand Up @@ -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',
Expand Down
6 changes: 4 additions & 2 deletions httpie/cli/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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

Expand Down
3 changes: 3 additions & 0 deletions httpie/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down