diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 54b8c623..3c9b9125 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,7 @@ This project adheres to `Semantic Versioning `_. * Fixed built-in plugins-related circular imports (`#925`_). * Added ``--format-options`` to allow disabling sorting, etc. (`#128`_) +* Added ``--unsorted`` shortcut to set all sorting-related ``--format-options`` to ``false``. (`#128`_) * Added ``--ciphers`` to allow configuring OpenSSL ciphers (`#870`_). * Added support for ``$XDG_CONFIG_HOME`` (`#920`_). * Added support for custom content types for uploaded files (`#668`_). diff --git a/httpie/cli/argparser.py b/httpie/cli/argparser.py index 37bb170e..b99d38be 100644 --- a/httpie/cli/argparser.py +++ b/httpie/cli/argparser.py @@ -9,9 +9,14 @@ from urllib.parse import urlsplit from requests.utils import get_netrc_auth -from httpie.cli.argtypes import AuthCredentials, KeyValueArgType, parse_auth +from httpie.cli.argtypes import ( + AuthCredentials, KeyValueArgType, PARSED_DEFAULT_FORMAT_OPTIONS, + parse_auth, + parse_format_options, +) from httpie.cli.constants import ( - HTTP_GET, HTTP_POST, OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT, + DEFAULT_FORMAT_OPTIONS, HTTP_GET, HTTP_POST, OUTPUT_OPTIONS, + OUTPUT_OPTIONS_DEFAULT, OUTPUT_OPTIONS_DEFAULT_STDOUT_REDIRECTED, OUT_RESP_BODY, PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY, SEPARATOR_CREDENTIALS, SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_GROUP_DATA_ITEMS, URL_SCHEME_RE, @@ -44,6 +49,8 @@ class HTTPieHelpFormatter(RawDescriptionHelpFormatter): return text.splitlines() +# TODO: refactor and design type-annotated data structures +# for raw args + parsed args and keep things immutable. class HTTPieArgumentParser(argparse.ArgumentParser): """Adds additional logic to `argparse.ArgumentParser`. @@ -84,6 +91,7 @@ class HTTPieArgumentParser(argparse.ArgumentParser): self._setup_standard_streams() self._process_output_options() self._process_pretty_options() + self._process_format_options() self._guess_method() self._parse_items() @@ -405,3 +413,9 @@ class HTTPieArgumentParser(argparse.ArgumentParser): if self.args.download_resume and not ( self.args.download and self.args.output_file): self.error('--continue requires --output to be specified') + + def _process_format_options(self): + parsed_options = PARSED_DEFAULT_FORMAT_OPTIONS + for options_group in self.args.format_options: + parsed_options = parse_format_options(options_group, defaults=parsed_options) + self.args.format_options = parsed_options diff --git a/httpie/cli/argtypes.py b/httpie/cli/argtypes.py index 0d4f9eb2..4f16e7c4 100644 --- a/httpie/cli/argtypes.py +++ b/httpie/cli/argtypes.py @@ -242,3 +242,9 @@ PARSED_DEFAULT_FORMAT_OPTIONS = parse_format_options( s=','.join(DEFAULT_FORMAT_OPTIONS), defaults=None, ) + + +class UnsortedAction(argparse.Action): + + def __call__(self, *args, **kwargs): + return 1 diff --git a/httpie/cli/constants.py b/httpie/cli/constants.py index ea8036fe..6305588c 100644 --- a/httpie/cli/constants.py +++ b/httpie/cli/constants.py @@ -91,7 +91,10 @@ DEFAULT_FORMAT_OPTIONS = [ 'json.indent:4', 'json.sort_keys:true', ] - +UNSORTED_FORMAT_OPTIONS = [ + 'headers.sort:false', + 'json.sort_keys:false', +] # Defaults OUTPUT_OPTIONS_DEFAULT = OUT_RESP_HEAD + OUT_RESP_BODY diff --git a/httpie/cli/definition.py b/httpie/cli/definition.py index b3b5f388..f8215220 100644 --- a/httpie/cli/definition.py +++ b/httpie/cli/definition.py @@ -9,14 +9,13 @@ from httpie import __doc__, __version__ from httpie.cli.argparser import HTTPieArgumentParser from httpie.cli.argtypes import ( KeyValueArgType, PARSED_DEFAULT_FORMAT_OPTIONS, SessionNameValidator, - parse_format_options, readable_file_arg, ) from httpie.cli.constants import ( DEFAULT_FORMAT_OPTIONS, OUTPUT_OPTIONS, OUTPUT_OPTIONS_DEFAULT, OUT_REQ_BODY, OUT_REQ_HEAD, OUT_RESP_BODY, OUT_RESP_HEAD, PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY, - SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_PROXY, + SEPARATOR_GROUP_ALL_ITEMS, SEPARATOR_PROXY, UNSORTED_FORMAT_OPTIONS, ) from httpie.output.formatters.colors import ( AUTO_STYLE, AVAILABLE_STYLES, DEFAULT_STYLE, @@ -229,14 +228,22 @@ output_processing.add_argument( auto_style=AUTO_STYLE, ) ) +output_processing.add_argument( + '--unsorted', + action='append_const', + const=','.join(UNSORTED_FORMAT_OPTIONS), + dest='format_options', + help=""" + Disables all sorting while formatting output. It is a shortcut for: + --format-options=json.sort_keys:false,headers.sort:false + + """ +) output_processing.add_argument( '--format-options', - type=lambda s: parse_format_options( - s=s, - defaults=PARSED_DEFAULT_FORMAT_OPTIONS - ), - default=PARSED_DEFAULT_FORMAT_OPTIONS, + default=[], + action='append', help=""" Controls output formatting. Only relevant when formatting is enabled through (explicit or implied) --pretty=all or --pretty=format. @@ -244,10 +251,11 @@ output_processing.add_argument( {option_list} - You can specify multiple comma-separated options. For example, this modifies - the settings to disable the sorting of JSON keys and headers: + You may use this option multiple times, as well as specify multiple + comma-separated options at the same time. For example, this modifies the + settings to disable the sorting of JSON keys, and sets the indent size to 2: - --format-options json.sort_keys:false,headers.sort:false + --format-options json.sort_keys:false,json.indent:2 This is something you will typically put into your config file. diff --git a/tests/test_output.py b/tests/test_output.py index e0438c10..afa499ce 100644 --- a/tests/test_output.py +++ b/tests/test_output.py @@ -6,7 +6,12 @@ from urllib.request import urlopen import pytest -from httpie.cli.argtypes import parse_format_options +from httpie.cli.constants import DEFAULT_FORMAT_OPTIONS +from httpie.cli.definition import parser +from httpie.cli.argtypes import ( + PARSED_DEFAULT_FORMAT_OPTIONS, + parse_format_options, +) from httpie.output.formatters.colors import get_lexer from httpie.status import ExitStatus from utils import COLOR, CRLF, HTTP_OK, MockEnvironment, http @@ -249,3 +254,55 @@ class TestFormatOptions: } with pytest.raises(argparse.ArgumentTypeError, match=expected_error): parse_format_options(s=options_string, defaults=defaults) + + @pytest.mark.parametrize( + argnames=['args', 'expected_format_options'], + argvalues=[ + ( + [ + '--format-options', + 'headers.sort:false,json.sort_keys:false', + '--format-options=json.indent:10' + ], + { + 'headers': {'sort': False}, + 'json': {'sort_keys': False, 'indent': 10, 'format': True}, + } + ), + ( + [ + '--unsorted' + ], + { + 'headers': {'sort': False}, + 'json': {'sort_keys': False, 'indent': 4, 'format': True}, + } + ), + ( + [ + '--format-options=headers.sort:true', + '--unsorted', + '--format-options=headers.sort:true', + ], + { + 'headers': {'sort': True}, + 'json': {'sort_keys': False, 'indent': 4, 'format': True}, + } + ), + ( + [ + '--no-format-options', # --no-