Improved --help output.

This commit is contained in:
Jakub Roztocil 2013-08-10 11:56:19 +02:00
parent 8378ad3624
commit 67496162fa
2 changed files with 263 additions and 179 deletions

View File

@ -3,7 +3,10 @@
NOTE: the CLI interface may change before reaching v1.0. NOTE: the CLI interface may change before reaching v1.0.
""" """
from argparse import FileType, OPTIONAL, ZERO_OR_MORE, SUPPRESS from textwrap import dedent, wrap
#noinspection PyCompatibility
from argparse import (RawDescriptionHelpFormatter, FileType,
OPTIONAL, ZERO_OR_MORE, SUPPRESS)
from . import __doc__ from . import __doc__
from . import __version__ from . import __version__
@ -12,77 +15,120 @@ from .output import AVAILABLE_STYLES, DEFAULT_STYLE
from .input import (Parser, AuthCredentialsArgType, KeyValueArgType, from .input import (Parser, AuthCredentialsArgType, KeyValueArgType,
SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ITEMS, SEP_PROXY, SEP_CREDENTIALS, SEP_GROUP_ITEMS,
OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD, OUT_REQ_HEAD, OUT_REQ_BODY, OUT_RESP_HEAD,
OUT_RESP_BODY, OUTPUT_OPTIONS, OUT_RESP_BODY, OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT,
PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY, SessionNameValidator) PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY, SessionNameValidator)
def _(text): class HTTPieHelpFormatter(RawDescriptionHelpFormatter):
"""Normalize whitespace.""" """A nicer help formatter.
return ' '.join(text.strip().split())
Help for arguments can be indented and contain new lines.
It will be de-dented and arguments in the help
will be separated by a blank line for better readability.
"""
def __init__(self, max_help_position=6, *args, **kwargs):
# A smaller indent for args help.
kwargs['max_help_position'] = max_help_position
super(HTTPieHelpFormatter, self).__init__(*args, **kwargs)
def _split_lines(self, text, width):
text = dedent(text).strip() + '\n\n'
return text.splitlines()
parser = Parser( parser = Parser(
formatter_class=HTTPieHelpFormatter,
description='%s <http://httpie.org>' % __doc__.strip(), description='%s <http://httpie.org>' % __doc__.strip(),
epilog='For every --option there is a --no-option' epilog=dedent("""
' that reverts the option to its default value.\n\n' For every --OPTION there is also a --no-OPTION that reverts OPTION
'Suggestions and bug reports are greatly appreciated:\n' to its default value.
'https://github.com/jkbr/httpie/issues'
Suggestions and bug reports are greatly appreciated:
https://github.com/jkbr/httpie/issues
""")
) )
############################################################################### #######################################################################
# Positional arguments. # Positional arguments.
############################################################################### #######################################################################
positional = parser.add_argument_group( positional = parser.add_argument_group(
title='Positional arguments', title='Positional arguments',
description=_(''' description=dedent("""
These arguments come after any flags and in the These arguments come after any flags and in the order they are listed here.
order they are listed here. Only URL is required. Only URL is required.
''')
""")
) )
positional.add_argument( positional.add_argument(
'method', 'method',
metavar='METHOD', metavar='METHOD',
nargs=OPTIONAL, nargs=OPTIONAL,
default=None, default=None,
help=_(''' help="""
The HTTP method to be used for the request The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).
(GET, POST, PUT, DELETE, PATCH, ...).
If this argument is omitted, then HTTPie This argument can be omitted in which case HTTPie will use POST if there
will guess the HTTP method. If there is some is some data to be sent, otherwise GET:
data to be sent, then it will be POST, otherwise GET.
''') $ http example.org # => GET
$ http example.org hello=world # => POST
"""
) )
positional.add_argument( positional.add_argument(
'url', 'url',
metavar='URL', metavar='URL',
help=_(''' help="""
The protocol defaults to http:// if the The scheme defaults to 'http://' if the URL does not include one.
URL does not include one.
''') """
) )
positional.add_argument( positional.add_argument(
'items', 'items',
metavar='REQUEST ITEM', metavar='REQUEST ITEM',
nargs=ZERO_OR_MORE, nargs=ZERO_OR_MORE,
type=KeyValueArgType(*SEP_GROUP_ITEMS), type=KeyValueArgType(*SEP_GROUP_ITEMS),
help=_(''' help=r"""
A key-value pair whose type is defined by the Optional key-value pairs to be included in the request. The separator used
separator used. It can be an HTTP header (header:value), determines the type:
a data field to be used in the request body (field_name=value),
a raw JSON data field (field_name:=value), ':' HTTP headers:
a query parameter (name==value),
or a file field (field_name@/path/to/file). Referer:http://httpie.org Cookie:foo=bar User-Agent:bacon/1.0
You can use a backslash to escape a colliding
separator in the field name. '==' URL parameters to be appended to the request URI:
''')
search==httpie
'=' Data fields to be serialized into a JSON object (with --json, -j)
or form data (with --form, -f):
name=HTTPie language=Python description='CLI HTTP client'
'@' Form file fields (only with --form, -f):
cs@~/Documents/CV.pdf
':=' Non-string JSON data fields (only with --json, -j):
awesome:=true amount:=42 colors:='["red", "green", "blue"]'
You can use a backslash to escape a colliding separator in the field name:
field-name-with\:colon=value
"""
) )
############################################################################### #######################################################################
# Content type. # Content type.
############################################################################### #######################################################################
content_type = parser.add_argument_group( content_type = parser.add_argument_group(
title='Predefined content types', title='Predefined content types',
@ -92,29 +138,30 @@ content_type = parser.add_argument_group(
content_type.add_argument( content_type.add_argument(
'--json', '-j', '--json', '-j',
action='store_true', action='store_true',
help=_(''' help="""
(default) Data items from the command (default) Data items from the command line are serialized as a JSON object.
line are serialized as a JSON object. The Content-Type and Accept headers are set to application/json
The Content-Type and Accept headers (if not specified).
are set to application/json (if not specified).
''') """
) )
content_type.add_argument( content_type.add_argument(
'--form', '-f', '--form', '-f',
action='store_true', action='store_true',
help=_(''' help="""
Data items from the command line are serialized as form fields. Data items from the command line are serialized as form fields.
The Content-Type is set to application/x-www-form-urlencoded
(if not specified). The Content-Type is set to application/x-www-form-urlencoded (if not
The presence of any file fields results specified). The presence of any file fields results in a
in a multipart/form-data request. multipart/form-data request.
''')
"""
) )
############################################################################### #######################################################################
# Output processing # Output processing
############################################################################### #######################################################################
output_processing = parser.add_argument_group(title='Output processing') output_processing = parser.add_argument_group(title='Output processing')
@ -123,12 +170,12 @@ output_processing.add_argument(
dest='prettify', dest='prettify',
default=PRETTY_STDOUT_TTY_ONLY, default=PRETTY_STDOUT_TTY_ONLY,
choices=sorted(PRETTY_MAP.keys()), choices=sorted(PRETTY_MAP.keys()),
help=_(''' help="""
Controls output processing. The value can be "none" to not prettify Controls output processing. The value can be "none" to not prettify
the output (default for redirected output), "all" to apply both colors the output (default for redirected output), "all" to apply both colors
and formatting and formatting (default for terminal output), "colors", or "format".
(default for terminal output), "colors", or "format".
''') """
) )
output_processing.add_argument( output_processing.add_argument(
'--style', '-s', '--style', '-s',
@ -136,75 +183,97 @@ output_processing.add_argument(
metavar='STYLE', metavar='STYLE',
default=DEFAULT_STYLE, default=DEFAULT_STYLE,
choices=AVAILABLE_STYLES, choices=AVAILABLE_STYLES,
help=_(''' help="""
Output coloring style. One of %s. Defaults to "%s". Output coloring style (default is "{default}"). On of:
For this option to work properly, please make sure that the
$TERM environment variable is set to "xterm-256color" or similar {available}
(e.g., via `export TERM=xterm-256color' in your ~/.bashrc).
''') % (', '.join(sorted(AVAILABLE_STYLES)), DEFAULT_STYLE) For this option to work properly, please make sure that the $TERM
environment variable is set to "xterm-256color" or similar
(e.g., via `export TERM=xterm-256color' in your ~/.bashrc).
"""
.format(
default=DEFAULT_STYLE,
available='\n'.join(
'{0: >20}'.format(line.strip())
for line in
wrap(' '.join(sorted(AVAILABLE_STYLES)), 60)
),
)
) )
############################################################################### #######################################################################
# Output options # Output options
############################################################################### #######################################################################
output_options = parser.add_argument_group(title='Output options') output_options = parser.add_argument_group(title='Output options')
output_options.add_argument( output_options.add_argument(
'--print', '-p', '--print', '-p',
dest='output_options', dest='output_options',
metavar='WHAT', metavar='WHAT',
help=_(''' help="""
String specifying what the output should contain: String specifying what the output should contain:
"{request_headers}" stands for the request headers, and
"{request_body}" for the request body. '{req_head}' request headers
"{response_headers}" stands for the response headers and '{req_body}' request body
"{response_body}" for response the body. '{res_head}' response headers
The default behaviour is "hb" (i.e., the response '{res_body}' response body
headers and body is printed), if standard output is not redirected.
If the output is piped to another program or to a file, The default behaviour is '{default}' (i.e., the response headers and body
then only the body is printed by default. is printed), if standard output is not redirected. If the output is piped
'''.format(request_headers=OUT_REQ_HEAD, to another program or to a file, then only the response body is printed
request_body=OUT_REQ_BODY, by default.
response_headers=OUT_RESP_HEAD,
response_body=OUT_RESP_BODY,)) """
.format(
req_head=OUT_REQ_HEAD,
req_body=OUT_REQ_BODY,
res_head=OUT_RESP_HEAD,
res_body=OUT_RESP_BODY,
default=OUTPUT_OPTIONS_DEFAULT,
)
) )
output_options.add_argument( output_options.add_argument(
'--verbose', '-v', '--verbose', '-v',
dest='output_options', dest='output_options',
action='store_const', action='store_const',
const=''.join(OUTPUT_OPTIONS), const=''.join(OUTPUT_OPTIONS),
help=_(''' help="""
Print the whole request as well as the response. Print the whole request as well as the response. Shortcut for --print={0}.
Shortcut for --print={0}.
'''.format(''.join(OUTPUT_OPTIONS))) """
.format(''.join(OUTPUT_OPTIONS))
) )
output_options.add_argument( output_options.add_argument(
'--headers', '-h', '--headers', '-h',
dest='output_options', dest='output_options',
action='store_const', action='store_const',
const=OUT_RESP_HEAD, const=OUT_RESP_HEAD,
help=_(''' help="""
Print only the response headers. Print only the response headers. Shortcut for --print={0}.
Shortcut for --print={0}.
'''.format(OUT_RESP_HEAD)) """
.format(OUT_RESP_HEAD)
) )
output_options.add_argument( output_options.add_argument(
'--body', '-b', '--body', '-b',
dest='output_options', dest='output_options',
action='store_const', action='store_const',
const=OUT_RESP_BODY, const=OUT_RESP_BODY,
help=_(''' help="""
Print only the response body. Print only the response body. Shortcut for --print={0}.
Shortcut for --print={0}.
'''.format(OUT_RESP_BODY)) """
.format(OUT_RESP_BODY)
) )
output_options.add_argument( output_options.add_argument(
'--stream', '-S', '--stream', '-S',
action='store_true', action='store_true',
default=False, default=False,
help=_(''' help="""
Always stream the output by line, i.e., behave like `tail -f'. Always stream the output by line, i.e., behave like `tail -f'.
Without --stream and with --pretty (either set or implied), Without --stream and with --pretty (either set or implied),
@ -216,33 +285,31 @@ output_options.add_argument(
It is useful also without --pretty: It ensures that the output is flushed It is useful also without --pretty: It ensures that the output is flushed
more often and in smaller chunks. more often and in smaller chunks.
''') """
) )
output_processing.add_argument( output_options.add_argument(
'--output', '-o', '--output', '-o',
type=FileType('a+b'), type=FileType('a+b'),
dest='output_file', dest='output_file',
metavar='FILE', metavar='FILE',
help=_( help="""
''' Save output to FILE. If --download is set, then only the response body is
Save output to FILE. If --download is set, then only the response saved to the file. Other parts of the HTTP exchange are printed to stderr.
body is saved to the file. Other parts of the HTTP exchange are
printed to stderr. """
'''
)
) )
output_options.add_argument( output_options.add_argument(
'--download', '-d', '--download', '-d',
action='store_true', action='store_true',
default=False, default=False,
help=_(''' help="""
Do not print the response body to stdout. Rather, download it and store it Do not print the response body to stdout. Rather, download it and store it
in a file. The filename is guessed unless specified with --output in a file. The filename is guessed unless specified with --output
[filename]. This action is similar to the default behaviour of wget. [filename]. This action is similar to the default behaviour of wget.
''') """
) )
output_options.add_argument( output_options.add_argument(
@ -250,48 +317,56 @@ output_options.add_argument(
dest='download_resume', dest='download_resume',
action='store_true', action='store_true',
default=False, default=False,
help=_(''' help="""
Resume an interrupted download. Resume an interrupted download. Note that the --output option needs to be
The --output option needs to be specified as well. specified as well.
''')
"""
) )
############################################################################### #######################################################################
# Sessions # Sessions
############################################################################### #######################################################################
sessions = parser.add_argument_group(title='Sessions')\ sessions = parser.add_argument_group(title='Sessions')\
.add_mutually_exclusive_group(required=False) .add_mutually_exclusive_group(required=False)
session_name_validator = SessionNameValidator( session_name_validator = SessionNameValidator(
'Session name contains invalid characters.') 'Session name contains invalid characters.'
)
sessions.add_argument( sessions.add_argument(
'--session', '--session',
metavar='SESSION_NAME_OR_PATH', metavar='SESSION_NAME_OR_PATH',
type=session_name_validator, type=session_name_validator,
help=_(''' help="""
Create, or reuse and update a session. Create, or reuse and update a session. Within a session, custom headers,
Within a session, custom headers, auth credential, as well as any auth credential, as well as any cookies sent by the server persist between
cookies sent by the server persist between requests. requests.
Session files are stored in %s/<HOST>/<SESSION_NAME>.json.
''' % DEFAULT_SESSIONS_DIR) Session files are stored in:
{session_dir}/<HOST>/<SESSION_NAME>.json.
"""
.format(session_dir=DEFAULT_SESSIONS_DIR)
) )
sessions.add_argument( sessions.add_argument(
'--session-read-only', '--session-read-only',
metavar='SESSION_NAME_OR_PATH', metavar='SESSION_NAME_OR_PATH',
type=session_name_validator, type=session_name_validator,
help=_(''' help="""
Create or read a session without updating it form the Create or read a session without updating it form the request/response
request/response exchange. exchange.
''')
"""
) )
############################################################################### #######################################################################
# Authentication # Authentication
############################################################################### #######################################################################
# ``requests.request`` keyword arguments. # ``requests.request`` keyword arguments.
auth = parser.add_argument_group(title='Authentication') auth = parser.add_argument_group(title='Authentication')
@ -299,26 +374,27 @@ auth.add_argument(
'--auth', '-a', '--auth', '-a',
metavar='USER[:PASS]', metavar='USER[:PASS]',
type=AuthCredentialsArgType(SEP_CREDENTIALS), type=AuthCredentialsArgType(SEP_CREDENTIALS),
help=_(''' help="""
If only the username is provided (-a username), If only the username is provided (-a username), HTTPie will prompt
HTTPie will prompt for the password. for the password.
'''),
""",
) )
auth.add_argument( auth.add_argument(
'--auth-type', '--auth-type',
choices=['basic', 'digest'], choices=['basic', 'digest'],
default='basic', default='basic',
help=_(''' help="""
The authentication mechanism to be used. The authentication mechanism to be used. Defaults to "basic".
Defaults to "basic".
''') """
) )
############################################################################### #######################################################################
# Network # Network
############################################################################### #######################################################################
network = parser.add_argument_group(title='Network') network = parser.add_argument_group(title='Network')
@ -328,31 +404,31 @@ network.add_argument(
action='append', action='append',
metavar='PROTOCOL:HOST', metavar='PROTOCOL:HOST',
type=KeyValueArgType(SEP_PROXY), type=KeyValueArgType(SEP_PROXY),
help=_(''' help="""
String mapping protocol to the URL of the proxy String mapping protocol to the URL of the proxy (e.g. http:foo.bar:3128).
(e.g. http:foo.bar:3128). You can specify multiple You can specify multiple proxies with different protocols.
proxies with different protocols.
''') """
) )
network.add_argument( network.add_argument(
'--follow', '--follow',
default=False, default=False,
action='store_true', action='store_true',
help=_(''' help="""
Set this flag if full redirects are allowed Set this flag if full redirects are allowed (e.g. re-POST-ing of data at
(e.g. re-POST-ing of data at new ``Location``) new Location).
''')
"""
) )
network.add_argument( network.add_argument(
'--verify', '--verify',
default='yes', default='yes',
help=_(''' help="""
Set to "no" to skip checking the host\'s SSL certificate. Set to "no" to skip checking the host's SSL certificate. You can also pass
You can also pass the path to a CA_BUNDLE the path to a CA_BUNDLE file for private certs. You can also set the
file for private certs. You can also set REQUESTS_CA_BUNDLE environment variable. Defaults to "yes".
the REQUESTS_CA_BUNDLE environment variable.
Defaults to "yes". """
''')
) )
network.add_argument( network.add_argument(
@ -360,36 +436,33 @@ network.add_argument(
type=float, type=float,
default=30, default=30,
metavar='SECONDS', metavar='SECONDS',
help=_(''' help="""
The connection timeout of the request in seconds. The connection timeout of the request in seconds. The default value is
The default value is 30 seconds. 30 seconds.
''')
"""
) )
network.add_argument( network.add_argument(
'--check-status', '--check-status',
default=False, default=False,
action='store_true', action='store_true',
help=_(''' help="""
By default, HTTPie exits with 0 when no network or other fatal By default, HTTPie exits with 0 when no network or other fatal errors
errors occur. occur. This flag instructs HTTPie to also check the HTTP status code and
exit with an error if the status indicates one.
This flag instructs HTTPie to also check the HTTP status code and When the server replies with a 4xx (Client Error) or 5xx (Server Error)
exit with an error if the status indicates one. status code, HTTPie exits with 4 or 5 respectively. If the response is a
3xx (Redirect) and --follow hasn't been set, then the exit status is 3.
Also an error message is written to stderr if stdout is redirected.
When the server replies with a 4xx (Client Error) or 5xx """
(Server Error) status code, HTTPie exits with 4 or 5 respectively.
If the response is a 3xx (Redirect) and --follow
hasn't been set, then the exit status is 3.
Also an error message is written to stderr if stdout is redirected.
''')
) )
############################################################################### #######################################################################
# Troubleshooting # Troubleshooting
############################################################################### #######################################################################
troubleshooting = parser.add_argument_group(title='Troubleshooting') troubleshooting = parser.add_argument_group(title='Troubleshooting')
@ -397,26 +470,36 @@ troubleshooting.add_argument(
'--help', '--help',
action='help', action='help',
default=SUPPRESS, default=SUPPRESS,
help='Show this help message and exit' help="""
Show this help message and exit.
"""
) )
troubleshooting.add_argument( troubleshooting.add_argument(
'--version', '--version',
action='version', action='version',
version=__version__ version=__version__,
help="""
Show version and exit.
"""
) )
troubleshooting.add_argument( troubleshooting.add_argument(
'--traceback', '--traceback',
action='store_true', action='store_true',
default=False, default=False,
help='Prints exception traceback should one occur.' help="""
Prints exception traceback should one occur.
"""
) )
troubleshooting.add_argument( troubleshooting.add_argument(
'--debug', '--debug',
action='store_true', action='store_true',
default=False, default=False,
help=_(''' help="""
Prints exception traceback should one occur, and also other Prints exception traceback should one occur, and also other information
information that is useful for debugging HTTPie itself and that is useful for debugging HTTPie itself and for reporting bugs.
for bug reports.
''') """
) )

View File

@ -8,6 +8,7 @@ import json
import mimetypes import mimetypes
import getpass import getpass
from io import BytesIO from io import BytesIO
#noinspection PyCompatibility
from argparse import ArgumentParser, ArgumentTypeError, ArgumentError from argparse import ArgumentParser, ArgumentTypeError, ArgumentError
try: try:
@ -417,8 +418,8 @@ class KeyValueArgType(object):
"""Tokenize `s`. There are only two token types - strings """Tokenize `s`. There are only two token types - strings
and escaped characters: and escaped characters:
>>> tokenize(r'foo\=bar\\baz') tokenize(r'foo\=bar\\baz')
['foo', Escaped('='), 'bar', Escaped('\\'), 'baz'] => ['foo', Escaped('='), 'bar', Escaped('\\'), 'baz']
""" """
tokens = [''] tokens = ['']