Compare commits

..

5 Commits

69 changed files with 889 additions and 1245 deletions

View File

@ -12,7 +12,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v3
with: with:
python-version: 3.9 python-version: 3.9

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v3
with: with:
python-version: "3.9" python-version: "3.9"

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v3
with: with:
python-version: 3.9 python-version: 3.9
- run: make venv - run: make venv

View File

@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v3
with: with:
python-version: "3.10" python-version: "3.10"
- run: make install - run: make install

View File

@ -18,7 +18,7 @@ jobs:
with: with:
ref: ${{ github.event.inputs.branch }} ref: ${{ github.event.inputs.branch }}
- uses: mislav/bump-homebrew-formula-action@v2 - uses: mislav/bump-homebrew-formula-action@v1
with: with:
formula-name: httpie formula-name: httpie
tag-name: ${{ github.events.inputs.branch }} tag-name: ${{ github.events.inputs.branch }}

View File

@ -13,7 +13,7 @@ jobs:
name: Release the Chocolatey name: Release the Chocolatey
runs-on: windows-2019 runs-on: windows-2019
env: env:
package-dir: docs\packaging\windows-chocolatey package-dir: ./httpie/docs/packaging/windows-chocolatey
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -30,21 +30,9 @@ jobs:
run: choco info httpie -s . run: choco info httpie -s .
working-directory: ${{ env.package-dir }} working-directory: ${{ env.package-dir }}
- name: Local installation - name: Check the Installation
run: | run: |
choco install httpie -y -dv -s "'.;https://community.chocolatey.org/api/v2/'" choco install httpie -y -dv -s "'.;https://community.chocolatey.org/api/v2/'"
working-directory: ${{ env.package-dir }}
- name: Test the locally installed binaries
run: |
# Source: https://stackoverflow.com/a/46760714/15330941
# Make `refreshenv` available right away, by defining the $env:ChocolateyInstall
# variable and importing the Chocolatey profile module.
$env:ChocolateyInstall = Convert-Path "$((Get-Command choco).Path)\..\.."
Import-Module "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
refreshenv
http --version http --version
https --version https --version
httpie --version httpie --version
@ -58,4 +46,3 @@ jobs:
run: | run: |
choco apikey --key $CHOCO_API_KEY --source https://push.chocolatey.org/ choco apikey --key $CHOCO_API_KEY --source https://push.chocolatey.org/
choco push httpie*.nupkg --source https://push.chocolatey.org/ choco push httpie*.nupkg --source https://push.chocolatey.org/
working-directory: ${{ env.package-dir }}

View File

@ -7,9 +7,6 @@ on:
description: "The branch, tag or SHA to release from" description: "The branch, tag or SHA to release from"
required: true required: true
default: "master" default: "master"
tag_name:
description: "Which release to upload the artifacts to (e.g., 3.0)"
required: true
release: release:
types: [released, prereleased] types: [released, prereleased]
@ -24,7 +21,7 @@ jobs:
with: with:
ref: ${{ github.event.inputs.branch }} ref: ${{ github.event.inputs.branch }}
- uses: actions/setup-python@v4 - uses: actions/setup-python@v3
with: with:
python-version: 3.9 python-version: 3.9
@ -48,30 +45,24 @@ jobs:
name: httpie.rpm name: httpie.rpm
path: extras/packaging/linux/artifacts/dist/*.rpm path: extras/packaging/linux/artifacts/dist/*.rpm
- name: Determine the release upload upload_url
id: release_id
run: |
pip install httpie
export API_URL="api.github.com/repos/httpie/httpie/releases/tags/${{ github.event.inputs.tag_name }}"
export UPLOAD_URL=`https --ignore-stdin GET $API_URL | jq -r ".upload_url"`
echo "::set-output name=UPLOAD_URL::$UPLOAD_URL"
- name: Publish Debian Package - name: Publish Debian Package
if: github.event_name == 'release'
uses: actions/upload-release-asset@v1.0.2 uses: actions/upload-release-asset@v1.0.2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.release_id.outputs.UPLOAD_URL }} upload_url: ${{ github.event.release.upload_url }}
asset_path: extras/packaging/linux/artifacts/dist/httpie_${{ github.event.inputs.tag_name }}_amd64.deb asset_path: extras/packaging/linux/artifacts/dist/httpie-${{ github.event.release.tag_name }}.deb
asset_name: httpie-${{ github.event.inputs.tag_name }}.deb asset_name: httpie-${{ github.event.release.tag_name }}.deb
asset_content_type: binary/octet-stream asset_content_type: binary/octet-stream
- name: Publish Single Executable - name: Publish Single Executable
if: github.event_name == 'release'
uses: actions/upload-release-asset@v1.0.2 uses: actions/upload-release-asset@v1.0.2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.release_id.outputs.UPLOAD_URL }} upload_url: ${{ github.event.release.upload_url }}
asset_path: extras/packaging/linux/artifacts/dist/http asset_path: extras/packaging/linux/artifacts/dist/http
asset_name: http asset_name: http
asset_content_type: binary/octet-stream asset_content_type: binary/octet-stream

View File

@ -17,12 +17,12 @@ jobs:
with: with:
ref: ${{ github.event.inputs.branch }} ref: ${{ github.event.inputs.branch }}
- uses: actions/setup-python@v4 - uses: actions/setup-python@v3
with: with:
python-version: 3.9 python-version: 3.9
- name: Build a binary wheel and a source tarball - name: Build a binary wheel and a source tarball
run: make install && make build run: make build
- name: Release on PyPI - name: Release on PyPI
uses: pypa/gh-action-pypi-publish@master uses: pypa/gh-action-pypi-publish@master

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v8 - uses: actions/stale@v5
with: with:
close-pr-message: 'Thanks for the pull request, but since it was stale for more than a 30 days we are closing it. If you want to work back on it, feel free to re-open it or create a new one.' close-pr-message: 'Thanks for the pull request, but since it was stale for more than a 30 days we are closing it. If you want to work back on it, feel free to re-open it or create a new one.'
stale-pr-label: 'stale' stale-pr-label: 'stale'

View File

@ -30,7 +30,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-python@v4 - uses: actions/setup-python@v3
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Windows setup - name: Windows setup

View File

@ -3,28 +3,14 @@
This document records all notable changes to [HTTPie](https://httpie.io). This document records all notable changes to [HTTPie](https://httpie.io).
This project adheres to [Semantic Versioning](https://semver.org/). This project adheres to [Semantic Versioning](https://semver.org/).
## [3.2.2](https://github.com/httpie/httpie/compare/3.2.1...3.2.2) (2022-05-19) ## [3.1.1.dev0](https://github.com/httpie/httpie/compare/3.1.0...HEAD) (Unreleased)
- Fixed compatibility with urllib3 2.0.0. ([#1499](https://github.com/httpie/httpie/issue/1499))
## [3.2.1](https://github.com/httpie/httpie/compare/3.1.0...3.2.1) (2022-05-06)
- Improved support for determining auto-streaming when the `Content-Type` header includes encoding information. ([#1383](https://github.com/httpie/httpie/pull/1383))
- Fixed the display of the crash happening in the secondary process for update checks. ([#1388](https://github.com/httpie/httpie/issues/1388))
## [3.2.0](https://github.com/httpie/httpie/compare/3.1.0...3.2.0) (2022-05-05)
- Added a warning for notifying the user about the new updates. ([#1336](https://github.com/httpie/httpie/pull/1336))
- Added support for single binary executables. ([#1330](https://github.com/httpie/httpie/pull/1330))
- Added support for man pages (and auto generation of them from the parser declaration). ([#1317](https://github.com/httpie/httpie/pull/1317))
- Added `http --manual` for man pages & regular manual with pager. ([#1343](https://github.com/httpie/httpie/pull/1343))
- Added support for session persistence of repeated headers with the same name. ([#1335](https://github.com/httpie/httpie/pull/1335)) - Added support for session persistence of repeated headers with the same name. ([#1335](https://github.com/httpie/httpie/pull/1335))
- Added support for sending `Secure` cookies to the `localhost` (and `.local` suffixed domains). ([#1308](https://github.com/httpie/httpie/issues/1308)) - Changed `httpie plugins` to the new `httpie cli` namespace as `httpie cli plugins` (`httpie plugins` continues to work as a hidden alias). ([#1320](https://github.com/httpie/httpie/issues/1320))
- Improved UI for the progress bars. ([#1324](https://github.com/httpie/httpie/pull/1324))
- Fixed redundant creation of `Content-Length` header on `OPTIONS` requests. ([#1310](https://github.com/httpie/httpie/issues/1310)) - Fixed redundant creation of `Content-Length` header on `OPTIONS` requests. ([#1310](https://github.com/httpie/httpie/issues/1310))
- Fixed blocking of warning thread on some use cases. ([#1349](https://github.com/httpie/httpie/issues/1349)) - Fixed blocking of warning thread on some use cases. ([#1349](https://github.com/httpie/httpie/issues/1349))
- Changed `httpie plugins` to the new `httpie cli` namespace as `httpie cli plugins` (`httpie plugins` continues to work as a hidden alias). ([#1320](https://github.com/httpie/httpie/issues/1320)) - Added support for sending `Secure` cookies to the `localhost` (and `.local` suffixed domains). ([#1308](https://github.com/httpie/httpie/issues/1308))
- Soft deprecated the `--history-print`. ([#1380](https://github.com/httpie/httpie/pull/1380))
## [3.1.0](https://github.com/httpie/httpie/compare/3.0.2...3.1.0) (2022-03-08) ## [3.1.0](https://github.com/httpie/httpie/compare/3.0.2...3.1.0) (2022-03-08)

View File

@ -59,10 +59,8 @@ $ git checkout -b my_topical_branch
#### Setup #### Setup
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started. The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started. Just run
You can run `$ make` to see all the available tasks. the following command, which:
To get started, run the command below, which:
- Creates an isolated Python virtual environment inside `./venv` - Creates an isolated Python virtual environment inside `./venv`
(via the standard library [venv](https://docs.python.org/3/library/venv.html) tool); (via the standard library [venv](https://docs.python.org/3/library/venv.html) tool);
@ -72,7 +70,7 @@ To get started, run the command below, which:
- and runs tests (It is the same as running `make install test`). - and runs tests (It is the same as running `make install test`).
```bash ```bash
$ make all $ make
``` ```
#### Python virtual environment #### Python virtual environment
@ -154,7 +152,7 @@ with the master branch of your repository (or a fresh checkout of HTTPie master,
`--fresh`) and report the results back. `--fresh`) and report the results back.
```bash ```bash
$ python extras/profiling/run.py $ python extras/benchmarks/run.py
``` ```
The benchmarks can also be run on the CI. Since it is a long process, it requires manual The benchmarks can also be run on the CI. Since it is a long process, it requires manual

View File

@ -22,26 +22,6 @@ VENV_PYTHON=$(VENV_BIN)/python
export PATH := $(VENV_BIN):$(PATH) export PATH := $(VENV_BIN):$(PATH)
default: list-tasks
###############################################################################
# Default task to get a list of tasks when `make' is run without args.
# <https://stackoverflow.com/questions/4219255>
###############################################################################
list-tasks:
@echo Available tasks:
@echo ----------------
@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | grep -E -v -e '^[^[:alnum:]]' -e '^$@$$'
@echo
###############################################################################
# Installation
###############################################################################
all: uninstall-httpie install test all: uninstall-httpie install test
@ -53,7 +33,7 @@ install-reqs:
$(VENV_PIP) install --upgrade pip wheel build $(VENV_PIP) install --upgrade pip wheel build
@echo $(H1)Installing dev requirements$(H1END) @echo $(H1)Installing dev requirements$(H1END)
$(VENV_PIP) install --upgrade '.[dev]' '.[test]' $(VENV_PIP) install --upgrade --editable '.[dev]'
@echo $(H1)Installing HTTPie$(H1END) @echo $(H1)Installing HTTPie$(H1END)
$(VENV_PIP) install --upgrade --editable . $(VENV_PIP) install --upgrade --editable .

View File

@ -1,30 +1,10 @@
<h2 align="center"> <br/>
<a href="https://httpie.io" target="blank_"> <a href="https://httpie.io" target="blank_">
<img height="100" alt="HTTPie" src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-logo.svg" /> <img height="100" alt="HTTPie" src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-logo.svg" />
</a> </a>
<br> <br/>
HTTPie for Terminal: human-friendly CLI HTTP client for the API era
</h2>
<div align="center"> # HTTPie: human-friendly CLI HTTP client for the API era
[![HTTPie for Desktop](https://img.shields.io/static/v1?label=HTTPie&message=for%20Desktop&color=4B78E6)](https://httpie.io/product)
[![](https://img.shields.io/static/v1?label=HTTPie&message=for%20Web%20%26%20Mobile&color=73DC8C)](https://httpie.io/app)
[![](https://img.shields.io/static/v1?label=HTTPie&message=for%20Terminal&color=FA9BFA)](https://httpie.io/cli)
[![Twitter](https://img.shields.io/twitter/follow/httpie?style=flat&color=%234B78E6&logoColor=%234B78E6)](https://twitter.com/httpie)
[![Chat](https://img.shields.io/discord/725351238698270761?style=flat&label=Chat%20on%20Discord&color=%23FA9BFA)](https://httpie.io/discord)
</div>
<div align="center">
[![Docs](https://img.shields.io/badge/stable%20docs-httpie.io%2Fdocs%2Fcli-brightgreen?style=flat&color=%2373DC8C&label=Docs)](https://httpie.org/docs/cli)
[![Latest version](https://img.shields.io/pypi/v/httpie.svg?style=flat&label=Latest&color=%234B78E6&logo=&logoColor=white)](https://pypi.python.org/pypi/httpie)
[![Build](https://img.shields.io/github/actions/workflow/status/httpie/httpie/tests.yml?branch=master&color=%23FA9BFA&label=Build)](https://github.com/httpie/httpie/actions)
[![Coverage](https://img.shields.io/codecov/c/github/httpie/httpie?style=flat&label=Coverage&color=%2373DC8C)](https://codecov.io/gh/httpie/httpie)
</div>
HTTPie (pronounced _aitch-tee-tee-pie_) is a command-line HTTP client. HTTPie (pronounced _aitch-tee-tee-pie_) is a command-line HTTP client.
Its goal is to make CLI interaction with web services as human-friendly as possible. Its goal is to make CLI interaction with web services as human-friendly as possible.
@ -32,16 +12,16 @@ HTTPie is designed for testing, debugging, and generally interacting with APIs &
The `http` & `https` commands allow for creating and sending arbitrary HTTP requests. The `http` & `https` commands allow for creating and sending arbitrary HTTP requests.
They use simple and natural syntax and provide formatted and colorized output. They use simple and natural syntax and provide formatted and colorized output.
<div align="center"> [![Docs](https://img.shields.io/badge/stable%20docs-httpie.io%2Fdocs-brightgreen?style=flat&color=%2373DC8C&label=Docs)](https://httpie.org/docs)
[![Latest version](https://img.shields.io/pypi/v/httpie.svg?style=flat&label=Latest&color=%234B78E6&logo=&logoColor=white)](https://pypi.python.org/pypi/httpie)
[![Build](https://img.shields.io/github/workflow/status/httpie/httpie/Build?color=%23FA9BFA&label=Build)](https://github.com/httpie/httpie/actions)
[![Coverage](https://img.shields.io/codecov/c/github/httpie/httpie?style=flat&label=Coverage&color=%2373DC8C)](https://codecov.io/gh/httpie/httpie)
[![Twitter](https://img.shields.io/twitter/follow/httpie?style=flat&color=%234B78E6&logoColor=%234B78E6)](https://twitter.com/httpie)
[![Chat](https://img.shields.io/badge/chat-Discord-brightgreen?style=flat&label=Chat%20on&color=%23FA9BFA)](https://httpie.io/discord)
<img src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-animation.gif" alt="HTTPie in action" width="100%"/> <img src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-animation.gif" alt="HTTPie in action" width="100%"/>
</div>
## We lost 54k GitHub stars ## We lost 54k GitHub stars
Please note we recently accidentally made this repo private for a moment, and GitHub deleted our community that took a decade to build. Read the full story here: https://httpie.io/blog/stardust Please note we recently accidentally made this repo private for a moment, and GitHub deleted our community that took a decade to build. Read the full story here: https://httpie.io/blog/stardust
@ -73,25 +53,25 @@ Please note we recently accidentally made this repo private for a moment, and Gi
Hello World: Hello World:
```bash ```bash
https httpie.io/hello $ https httpie.io/hello
``` ```
Custom [HTTP method](https://httpie.io/docs#http-method), [HTTP headers](https://httpie.io/docs#http-headers) and [JSON](https://httpie.io/docs#json) data: Custom [HTTP method](https://httpie.io/docs#http-method), [HTTP headers](https://httpie.io/docs#http-headers) and [JSON](https://httpie.io/docs#json) data:
```bash ```bash
http PUT pie.dev/put X-API-Token:123 name=John $ http PUT pie.dev/put X-API-Token:123 name=John
``` ```
Build and print a request without sending it using [offline mode](https://httpie.io/docs#offline-mode): Build and print a request without sending it using [offline mode](https://httpie.io/docs#offline-mode):
```bash ```bash
http --offline pie.dev/post hello=offline $ http --offline pie.dev/post hello=offline
``` ```
Use [GitHub API](https://developer.github.com/v3/issues/comments/#create-a-comment) to post a comment on an [Issue](https://github.com/httpie/httpie/issues/83) with [authentication](https://httpie.io/docs#authentication): Use [GitHub API](https://developer.github.com/v3/issues/comments/#create-a-comment) to post a comment on an [Issue](https://github.com/httpie/httpie/issues/83) with [authentication](https://httpie.io/docs#authentication):
```bash ```bash
http -a USERNAME POST https://api.github.com/repos/httpie/httpie/issues/83/comments body='HTTPie is awesome! :heart:' $ http -a USERNAME POST https://api.github.com/repos/httpie/httpie/issues/83/comments body='HTTPie is awesome! :heart:'
``` ```
[See more examples →](https://httpie.io/docs#examples) [See more examples →](https://httpie.io/docs#examples)

View File

@ -162,8 +162,6 @@ Also works for other Debian-derived distributions like MX Linux, Linux Mint, dee
```bash ```bash
# Install httpie # Install httpie
$ curl -SsL https://packages.httpie.io/deb/KEY.gpg | apt-key add -
$ curl -SsL -o /etc/apt/sources.list.d/httpie.list https://packages.httpie.io/deb/httpie.list
$ apt update $ apt update
$ apt install httpie $ apt install httpie
``` ```
@ -215,21 +213,6 @@ $ pacman -Syu httpie
$ pacman -Syu $ pacman -Syu
``` ```
#### Single binary executables
Get the standalone HTTPie Linux executables when you don't want to go through the full installation process
```bash
# Install httpie
$ https --download packages.httpie.io/binaries/linux/http-latest -o http
$ chmod +x ./http
```
```bash
# Upgrade httpie
$ https --download packages.httpie.io/binaries/linux/http-latest -o http
```
### FreeBSD ### FreeBSD
#### FreshPorts #### FreshPorts
@ -250,39 +233,36 @@ $ pkg upgrade www/py-httpie
### Unstable version ### Unstable version
If you want to try out the latest version of HTTPie that hasn't been officially released yet, you can install the development or unstable version directly from the master branch on GitHub. However, keep in mind that the development version is a work in progress and may not be as reliable as the stable version. You can also install the latest unreleased development version directly from the `master` branch on GitHub.
It is a work-in-progress of a future stable release so the experience might be not as smooth.
You can use the following command to install the development version of HTTPie on Linux, macOS, Windows, or FreeBSD operating systems. With this command, the code present in the `master` branch is downloaded and installed using `pip`. You can install it on Linux, macOS, Windows, or FreeBSD with `pip`:
```bash ```bash
$ python -m pip install --upgrade https://github.com/httpie/httpie/archive/master.tar.gz $ python -m pip install --upgrade https://github.com/httpie/httpie/archive/master.tar.gz
``` ```
There are other ways to install the development version of HTTPie on macOS and Linux. Or on macOS, and Linux, with Homebrew:
You can install it using Homebrew by running the following commands:
```bash ```bash
$ brew uninstall --force httpie $ brew uninstall --force httpie
$ brew install --HEAD httpie $ brew install --HEAD httpie
``` ```
You can install it using Snapcraft by running the following commands: And even on macOS, and Linux, with Snapcraft:
```bash ```bash
$ snap remove httpie $ snap remove httpie
$ snap install httpie --edge $ snap install httpie --edge
``` ```
To verify the installation, you can compare the [version identifier on GitHub](https://github.com/httpie/httpie/blob/master/httpie/__init__.py#L6) with the one available on your machine. You can check the version of HTTPie on your machine by using the command `http --version`. Verify that now you have the [current development version identifier](https://github.com/httpie/httpie/blob/master/httpie/__init__.py#L6) with the `.dev0` suffix, for example:
```bash ```bash
$ http --version $ http --version
# 3.X.X.dev0 # 3.X.X.dev0
``` ```
Note that on your machine, the version name will have the `.dev0` suffix.
## Usage ## Usage
Hello World: Hello World:
@ -297,7 +277,7 @@ Synopsis:
$ http [flags] [METHOD] URL [ITEM [ITEM]] $ http [flags] [METHOD] URL [ITEM [ITEM]]
``` ```
See also `http --help` (and for systems where man pages are available, you can use `man http`). See also `http --help`.
### Examples ### Examples
@ -1456,8 +1436,7 @@ $ http --proxy=http:http://user:pass@10.10.1.10:3128 example.org
### Server SSL certificate verification ### Server SSL certificate verification
To skip the hosts SSL certificate verification, you can pass `--verify=no` (default is `yes`):
To skip the hosts SSL certificate verification, you can pass `--verify=no` (default is `yes`):
```bash ```bash
$ http --verify=no https://pie.dev/get $ http --verify=no https://pie.dev/get
@ -2398,9 +2377,12 @@ fi
#### `httpie cli check-updates` #### `httpie cli check-updates`
$ httpie cli check-updates You can check whether a new update is available for your system by running `httpie cli check-updates`:
```bash-termible
$ httpie cli check-updates
```` ````
#### `httpie cli export-args` #### `httpie cli export-args`
`httpie cli export-args` command can expose the parser specification of `http`/`https` commands `httpie cli export-args` command can expose the parser specification of `http`/`https` commands
@ -2426,7 +2408,7 @@ You can check whether a new update is available for your system by running `http
In the past `pip` was used to install/uninstall plugins, but on some environments (e.g., brew installed In the past `pip` was used to install/uninstall plugins, but on some environments (e.g., brew installed
packages) it wasnt working properly. The new interface is a very simple overlay on top of `pip` to allow packages) it wasnt working properly. The new interface is a very simple overlay on top of `pip` to allow
plugin installations on every installation method. plugin installations on every installation method.
By default, the plugins (and their missing dependencies) will be stored under the configuration directory, By default, the plugins (and their missing dependencies) will be stored under the configuration directory,
but this can be modified through `plugins_dir` variable on the config. but this can be modified through `plugins_dir` variable on the config.
@ -2554,7 +2536,7 @@ HTTPie has the following community channels:
See [github.com/httpie/httpie/security/policy](https://github.com/httpie/httpie/security/policy). See [github.com/httpie/httpie/security/policy](https://github.com/httpie/httpie/security/policy).
### Change log ### Change log
See [CHANGELOG](https://github.com/httpie/httpie/blob/master/CHANGELOG.md). See [CHANGELOG](https://github.com/httpie/httpie/blob/master/CHANGELOG.md).

View File

@ -1,3 +1,3 @@
Here we maintain a database of contributors, from which we generate credits on release blog posts and social media. Here we maintain a database of contributors, from which we generate credits on release blog posts and social medias.
For the HTTPie blog see: <https://httpie.io/blog>. For the HTTPie blog see: <https://httpie.io/blog>.

View File

@ -252,7 +252,6 @@ def fetch_missing_users_details(people: People) -> None:
def save_awesome_people(people: People) -> None: def save_awesome_people(people: People) -> None:
with DB_FILE.open(mode='w', encoding='utf-8') as fh: with DB_FILE.open(mode='w', encoding='utf-8') as fh:
json.dump(people, fh, indent=4, sort_keys=True) json.dump(people, fh, indent=4, sort_keys=True)
fh.write("\n")
def debug(*args: Any) -> None: def debug(*args: Any) -> None:

View File

@ -8,27 +8,19 @@ from jinja2 import Template
from fetch import HERE, load_awesome_people from fetch import HERE, load_awesome_people
TPL_FILE = HERE / 'snippet.jinja2' TPL_FILE = HERE / 'snippet.jinja2'
HTTPIE_TEAM = { HTTPIE_TEAM = {
'claudiatd', 'claudiatd',
'jakubroztocil', 'jakubroztocil',
'jkbr', 'jkbr',
'isidentical'
} }
BOT_ACCOUNTS = {
'dependabot-sr'
}
IGNORE_ACCOUNTS = HTTPIE_TEAM | BOT_ACCOUNTS
def generate_snippets(release: str) -> str: def generate_snippets(release: str) -> str:
people = load_awesome_people() people = load_awesome_people()
contributors = { contributors = {
name: details name: details
for name, details in people.items() for name, details in people.items()
if details['github'] not in IGNORE_ACCOUNTS if details['github'] not in HTTPIE_TEAM
and (release in details['committed'] or release in details['reported']) and (release in details['committed'] or release in details['reported'])
} }

View File

@ -53,13 +53,11 @@
}, },
"Batuhan Taskaya": { "Batuhan Taskaya": {
"committed": [ "committed": [
"3.0.0", "3.0.0"
"3.2.0"
], ],
"github": "isidentical", "github": "isidentical",
"reported": [ "reported": [
"3.0.0", "3.0.0"
"3.2.0"
], ],
"twitter": "isidentical" "twitter": "isidentical"
}, },
@ -120,14 +118,6 @@
"reported": [], "reported": [],
"twitter": "elena_lape" "twitter": "elena_lape"
}, },
"Ethan Mills": {
"committed": [
"3.2.0"
],
"github": "ethanmills",
"reported": [],
"twitter": null
},
"Fabio Peruzzo": { "Fabio Peruzzo": {
"committed": [], "committed": [],
"github": "peruzzof", "github": "peruzzof",
@ -199,8 +189,7 @@
"committed": [ "committed": [
"2.5.0", "2.5.0",
"2.6.0", "2.6.0",
"3.0.0", "3.0.0"
"3.2.0"
], ],
"github": "jakubroztocil", "github": "jakubroztocil",
"reported": [ "reported": [
@ -224,8 +213,7 @@
], ],
"github": "blyxxyz", "github": "blyxxyz",
"reported": [ "reported": [
"3.0.0", "3.0.0"
"3.2.0"
], ],
"twitter": null "twitter": null
}, },
@ -321,8 +309,7 @@
"committed": [], "committed": [],
"github": "ducaale", "github": "ducaale",
"reported": [ "reported": [
"2.5.0", "2.5.0"
"3.2.0"
], ],
"twitter": null "twitter": null
}, },
@ -334,22 +321,6 @@
], ],
"twitter": "sevenc_nanashi" "twitter": "sevenc_nanashi"
}, },
"Nicklas Ansman Giertz": {
"committed": [],
"github": "ansman",
"reported": [
"3.2.0"
],
"twitter": null
},
"Oliver Fish": {
"committed": [],
"github": "Oliver-Fish",
"reported": [
"3.2.0"
],
"twitter": null
},
"Omer Akram": { "Omer Akram": {
"committed": [ "committed": [
"2.6.0", "2.6.0",
@ -386,14 +357,6 @@
], ],
"twitter": null "twitter": null
}, },
"Roberto L\u00f3pez L\u00f3pez": {
"committed": [],
"github": "robertolopezlopez",
"reported": [
"3.2.0"
],
"twitter": null
},
"Russell Shurts": { "Russell Shurts": {
"committed": [], "committed": [],
"github": "rshurts", "github": "rshurts",
@ -524,14 +487,6 @@
], ],
"twitter": null "twitter": null
}, },
"dependabot[bot]": {
"committed": [
"3.2.0"
],
"github": "dependabot-sr",
"reported": [],
"twitter": null
},
"dkreeft": { "dkreeft": {
"committed": [ "committed": [
"2.6.0", "2.6.0",
@ -598,14 +553,6 @@
], ],
"twitter": null "twitter": null
}, },
"luzpaz": {
"committed": [
"3.2.0"
],
"github": "luzpaz",
"reported": [],
"twitter": null
},
"nixbytes": { "nixbytes": {
"committed": [ "committed": [
"2.5.0" "2.5.0"
@ -646,14 +593,6 @@
], ],
"twitter": null "twitter": null
}, },
"zhaohanqing95": {
"committed": [],
"github": "zhaohanqing95",
"reported": [
"3.2.0"
],
"twitter": null
},
"zoulja": { "zoulja": {
"committed": [], "committed": [],
"github": "zoulja", "github": "zoulja",
@ -688,4 +627,4 @@
], ],
"twitter": null "twitter": null
} }
} }

View File

@ -2,10 +2,9 @@
## Community contributions ## Community contributions
Wed like to thank these amazing people for their contributions to this release: Wed like to thank these amazing people for their contributions to this release: {% for name, details in contributors.items() -%}
{% for name, details in contributors.items() -%} [{{ name }}](https://github.com/{{ details.github }}){{ '' if loop.last else ', ' }}
- [{{ name }}](https://github.com/{{ details.github }}){{ '' if loop.last else '\n' }} {%- endfor %}.
{%- endfor %}
<!-- Twitter --> <!-- Twitter -->

View File

@ -55,7 +55,7 @@ def build_docs_structure(database: Database):
tree = database[KEY_DOC_STRUCTURE] tree = database[KEY_DOC_STRUCTURE]
structure = [] structure = []
for platform, tools_ids in tree.items(): for platform, tools_ids in tree.items():
assert platform.isalnum(), f'{platform=} must be alphanumeric for generated links to work' assert platform.isalnum(), f'{platform=} must be alpha-numeric for generated links to work'
platform_tools = [tools[tool_id] for tool_id in tools_ids] platform_tools = [tools[tool_id] for tool_id in tools_ids]
structure.append((platform, platform_tools)) structure.append((platform, platform_tools))
return structure return structure

View File

@ -17,12 +17,11 @@ docs-structure:
Windows: Windows:
- chocolatey - chocolatey
Linux: Linux:
- snap-linux
- brew-linux
- apt - apt
- dnf - dnf
- yum - yum
- single-binary
- snap-linux
- brew-linux
- pacman - pacman
FreeBSD: FreeBSD:
- pkg - pkg
@ -37,8 +36,6 @@ tools:
package: https://packages.debian.org/sid/web/httpie package: https://packages.debian.org/sid/web/httpie
commands: commands:
install: install:
- curl -SsL https://packages.httpie.io/deb/KEY.gpg | apt-key add -
- curl -SsL -o /etc/apt/sources.list.d/httpie.list https://packages.httpie.io/deb/httpie.list
- apt update - apt update
- apt install httpie - apt install httpie
upgrade: upgrade:
@ -182,16 +179,3 @@ tools:
- yum install httpie - yum install httpie
upgrade: upgrade:
- yum upgrade httpie - yum upgrade httpie
single-binary:
title: Single binary executables
name: Single binary executables
note: Get the standalone HTTPie Linux executables when you don't want to go through the full installation process.
links:
commands:
install:
- https --download packages.httpie.io/binaries/linux/http-latest -o http
- ln -ls ./http ./https
- chmod +x ./http ./https
upgrade:
- https --download packages.httpie.io/binaries/linux/http-latest -o http

View File

@ -22,7 +22,7 @@ If it is needed to be done manually, the following command can be used:
$ brew bump-formula-pr httpie --version={TARGET_VERSION} $ brew bump-formula-pr httpie --version={TARGET_VERSION}
``` ```
which will bump the formula, and create a PR against the package index. which will bump the formala, and create a PR against the package index.
## Hacking ## Hacking

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata> <metadata>
<id>httpie</id> <id>httpie</id>
<version>3.2.1</version> <version>3.1.0</version>
<summary>Modern, user-friendly command-line HTTP client for the API era</summary> <summary>Modern, user-friendly command-line HTTP client for the API era</summary>
<description> <description>
HTTPie *aitch-tee-tee-pie* is a user-friendly command-line HTTP client for the API era. HTTPie *aitch-tee-tee-pie* is a user-friendly command-line HTTP client for the API era.
@ -33,7 +33,7 @@ Main features:
<licenseUrl>https://raw.githubusercontent.com/httpie/httpie/master/LICENSE</licenseUrl> <licenseUrl>https://raw.githubusercontent.com/httpie/httpie/master/LICENSE</licenseUrl>
<iconUrl>https://pie-assets.s3.eu-central-1.amazonaws.com/LogoIcons/GB.png</iconUrl> <iconUrl>https://pie-assets.s3.eu-central-1.amazonaws.com/LogoIcons/GB.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<releaseNotes>See the [changelog](https://github.com/httpie/httpie/releases/tag/3.2.0).</releaseNotes> <releaseNotes>See the [changelog](https://github.com/httpie/httpie/releases/tag/3.1.0).</releaseNotes>
<tags>httpie http https rest api client curl python ssl cli foss oss url</tags> <tags>httpie http https rest api client curl python ssl cli foss oss url</tags>
<projectUrl>https://httpie.io</projectUrl> <projectUrl>https://httpie.io</projectUrl>
<packageSourceUrl>https://github.com/httpie/httpie/tree/master/docs/packaging/windows-chocolatey</packageSourceUrl> <packageSourceUrl>https://github.com/httpie/httpie/tree/master/docs/packaging/windows-chocolatey</packageSourceUrl>

View File

@ -1,24 +1,52 @@
function __fish_httpie_styles function __fish_httpie_styles
printf '%s\n' abap algol algol_nu arduino auto autumn borland bw colorful default emacs friendly fruity gruvbox-dark gruvbox-light igor inkpot lovelace manni material monokai murphy native paraiso-dark paraiso-light pastie perldoc pie pie-dark pie-light rainbow_dash rrt sas solarized solarized-dark solarized-light stata stata-dark stata-light tango trac vim vs xcode zenburn echo "
end abap
algol
function __fish_httpie_mime_types algol_nu
test -r /usr/share/mime/types && cat /usr/share/mime/types arduino
end auto
autumn
function __fish_httpie_print_args borland
set -l arg (commandline -t) bw
string match -qe H "$arg" || echo -e $arg"H\trequest headers" colorful
string match -qe B "$arg" || echo -e $arg"B\trequest body" default
string match -qe h "$arg" || echo -e $arg"h\tresponse headers" emacs
string match -qe b "$arg" || echo -e $arg"b\tresponse body" friendly
string match -qe m "$arg" || echo -e $arg"m\tresponse metadata" fruity
gruvbox-dark
gruvbox-light
igor
inkpot
lovelace
manni
material
monokai
murphy
native
paraiso-dark
paraiso-light
pastie
perldoc
rainbow_dash
rrt
sas
solarized
solarized-dark
solarized-light
stata
stata-dark
stata-light
tango
trac
vim
vs
xcode
zenburn"
end end
function __fish_httpie_auth_types function __fish_httpie_auth_types
echo -e "basic\tBasic HTTP auth" echo -e "basic\tBasic HTTP auth"
echo -e "digest\tDigest HTTP auth" echo -e "digest\tDigest HTTP auth"
echo -e "bearer\tBearer HTTP Auth"
end end
function __fish_http_verify_options function __fish_http_verify_options
@ -26,7 +54,6 @@ function __fish_http_verify_options
echo -e "no\tDisable cert verification" echo -e "no\tDisable cert verification"
end end
# Predefined Content Types # Predefined Content Types
complete -c http -s j -l json -d 'Data items are serialized as a JSON object' complete -c http -s j -l json -d 'Data items are serialized as a JSON object'
@ -43,28 +70,26 @@ complete -c http -s x -l compress -d 'Content compressed with Deflate algorithm'
# Output Processing # Output Processing
complete -c http -l pretty -xa "all colors format none" -d 'Controls output processing' complete -c http -l pretty -xa "all colors format none" -d 'Controls output processing'
complete -c http -s s -l style -xa "(__fish_httpie_styles)" -d 'Output coloring style' complete -c http -s s -l style -xa "(__fish_httpie_styles)" -d 'Output coloring style'
complete -c http -l unsorted -d 'Disables all sorting while formatting output' complete -c http -l unsorted -d 'Disables all sorting while formatting output'
complete -c http -l sorted -d 'Re-enables all sorting options while formatting output' complete -c http -l sorted -d 'Re-enables all sorting options while formatting output'
complete -c http -l response-charset -x -d 'Override the response encoding' complete -c http -l format-options -x -d 'Controls output formatting'
complete -c http -l response-mime -xa "(__fish_httpie_mime_types)" -d 'Override the response mime type for coloring and formatting'
complete -c http -l format-options -x -d 'Controls output formatting'
# Output Options # Output Options
complete -c http -s p -l print -xa "(__fish_httpie_print_args)" -d 'String specifying what the output should contain' complete -c http -s p -l print -x -d 'String specifying what the output should contain'
complete -c http -s h -l headers -d 'Print only the response headers' complete -c http -s h -l headers -d 'Print only the response headers'
complete -c http -s m -l meta -d 'Print only the response metadata' complete -c http -s b -l body -d 'Print only the response body'
complete -c http -s b -l body -d 'Print only the response body' complete -c http -s v -l verbose -d 'Print the whole request as well as the response'
complete -c http -s v -l verbose -d 'Print the whole request as well as the response' complete -c http -l all -d 'Show any intermediary requests/responses'
complete -c http -l all -d 'Show any intermediary requests/responses' complete -c http -s P -l history-print -x -d 'The same as --print but applies only to intermediary requests/responses'
complete -c http -s S -l stream -d 'Always stream the response body by line' complete -c http -s S -l stream -d 'Always stream the response body by line'
complete -c http -s o -l output -F -d 'Save output to FILE' complete -c http -s o -l output -F -d 'Save output to FILE'
complete -c http -s d -l download -d 'Download a file' complete -c http -s d -l download -d 'Download a file'
complete -c http -s c -l continue -d 'Resume an interrupted download' complete -c http -s c -l continue -d 'Resume an interrupted download'
complete -c http -s q -l quiet -d 'Do not print to stdout or stderr' complete -c http -s q -l quiet -d 'Do not print to stdout or stderr'
# Sessions # Sessions
@ -90,24 +115,22 @@ complete -c http -l max-headers -x -d 'Maximum number of response headers
complete -c http -l timeout -x -d 'Connection timeout in seconds' complete -c http -l timeout -x -d 'Connection timeout in seconds'
complete -c http -l check-status -d 'Error with non-200 HTTP status code' complete -c http -l check-status -d 'Error with non-200 HTTP status code'
complete -c http -l path-as-is -d 'Bypass dot segment URL squashing' complete -c http -l path-as-is -d 'Bypass dot segment URL squashing'
complete -c http -l chunked -d 'Enable streaming via chunked transfer encoding' complete -c http -l chunked -d ''
# SSL # SSL
complete -c http -l verify -xa "(__fish_http_verify_options)" -d 'Enable/disable cert verification' complete -c http -l verify -xa "(__fish_http_verify_options)" -d 'Enable/disable cert verification'
complete -c http -l ssl -x -d 'Desired protocol version to use' complete -c http -l ssl -x -d 'Desired protocol version to use'
complete -c http -l ciphers -x -d 'String in the OpenSSL cipher list format' complete -c http -l ciphers -x -d 'String in the OpenSSL cipher list format'
complete -c http -l cert -F -d 'Client side SSL certificate' complete -c http -l cert -F -d 'Client side SSL certificate'
complete -c http -l cert-key -F -d 'Private key to use with SSL' complete -c http -l cert-key -F -d 'Private key to use with SSL'
complete -c http -l cert-key-pass -x -d 'Passphrase for the given private key'
# Troubleshooting # Troubleshooting
complete -c http -s I -l ignore-stdin -d 'Do not attempt to read stdin' complete -c http -s I -l ignore-stdin -d 'Do not attempt to read stdin'
complete -c http -l help -d 'Show help' complete -c http -l help -d 'Show help'
complete -c http -l manual -d 'Show the full manual'
complete -c http -l version -d 'Show version' complete -c http -l version -d 'Show version'
complete -c http -l traceback -d 'Prints exception traceback should one occur' complete -c http -l traceback -d 'Prints exception traceback should one occur'
complete -c http -l default-scheme -x -d 'The default scheme to use' complete -c http -l default-scheme -x -d 'The default scheme to use'

View File

@ -1,5 +1,4 @@
.\" This file is auto-generated from the parser declaration in httpie/cli/definition.py by extras/scripts/generate_man_pages.py. .TH http 1 "2022-03-08" "HTTPie 3.1.1.dev0" "HTTPie Manual"
.TH http 1 "2022-05-06" "HTTPie 3.2.1" "HTTPie Manual"
.SH NAME .SH NAME
http http
.SH SYNOPSIS .SH SYNOPSIS
@ -7,7 +6,7 @@ http [METHOD] URL [REQUEST_ITEM ...]
.SH DESCRIPTION .SH DESCRIPTION
HTTPie: modern, user-friendly command-line HTTP client for the API era. <https://httpie.io> HTTPie: modern, user-friendly command-line HTTP client for the API era. <https://httpie.io>
.SH Positional arguments .SH Positional Arguments
These arguments come after any flags and in the order they are listed here. These arguments come after any flags and in the order they are listed here.
Only URL is required. Only URL is required.
@ -28,8 +27,8 @@ is some data to be sent, otherwise GET:
.IP "\fB\,URL\/\fR" .IP "\fB\,URL\/\fR"
The request URL. Scheme defaults to \[aq]http://\[aq] if the URL The request URL. Scheme defaults to \'http://\' if the URL
does not include one. (You can override this with: \fB\,--default-scheme\/\fR=http/https) does not include one. (You can override this with:\fB\,--default-scheme\/\fR=http/https)
You can also use a shorthand for localhost You can also use a shorthand for localhost
@ -44,44 +43,44 @@ You can also use a shorthand for localhost
Optional key-value pairs to be included in the request. The separator used Optional key-value pairs to be included in the request. The separator used
determines the type: determines the type:
\[aq]:\[aq] HTTP headers: \':\' HTTP headers:
Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0 Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0
\[aq]==\[aq] URL parameters to be appended to the request URI: \'==\' URL parameters to be appended to the request URI:
search==httpie search==httpie
\[aq]=\[aq] Data fields to be serialized into a JSON object (with \fB\,--json\/\fR, \fB\,-j\/\fR) \'=\' Data fields to be serialized into a JSON object (with\fB\,--json\/\fR,\fB\,-j\/\fR)
or form data (with \fB\,--form\/\fR, \fB\,-f\/\fR): or form data (with\fB\,--form\/\fR,\fB\,-f\/\fR):
name=HTTPie language=Python description=\[aq]CLI HTTP client\[aq] name=HTTPie language=Python description=\'CLI HTTP client\'
\[aq]:=\[aq] Non-string JSON data fields (only with \fB\,--json\/\fR, \fB\,-j\/\fR): \':=\' Non-string JSON data fields (only with\fB\,--json\/\fR,\fB\,-j\/\fR):
awesome:=true amount:=42 colors:=\[aq][\[dq]red\[dq], \[dq]green\[dq], \[dq]blue\[dq]]\[aq] awesome:=true amount:=42 colors:=\'["red", "green", "blue"]\'
\[aq]@\[aq] Form file fields (only with \fB\,--form\/\fR or \fB\,--multipart\/\fR): \'@\' Form file fields (only with\fB\,--form\/\fR or\fB\,--multipart\/\fR):
cv@\(ti/Documents/CV.pdf cv@\~/Documents/CV.pdf
cv@\[aq]\(ti/Documents/CV.pdf;type=application/pdf\[aq] cv@\'\~/Documents/CV.pdf;type=application/pdf\'
\[aq]=@\[aq] A data field like \[aq]=\[aq], but takes a file path and embeds its content: \'=@\' A data field like \'=\', but takes a file path and embeds its content:
essay=@Documents/essay.txt essay=@Documents/essay.txt
\[aq]:=@\[aq] A raw JSON field like \[aq]:=\[aq], but takes a file path and embeds its content: \':=@\' A raw JSON field like \':=\', but takes a file path and embeds its content:
package:=@./package.json package:=@./package.json
You can use a backslash to escape a colliding separator in the field name: You can use a backslash to escape a colliding separator in the field name:
field-name-with\e:colon=value field-name-with\\:colon=value
.PP .PP
.SH Predefined content types .SH Predefined Content Types
.IP "\fB\,--json\/\fR, \fB\,-j\/\fR" .IP "\fB\,--json\/\fR, \fB\,-j\/\fR"
@ -105,13 +104,13 @@ multipart/form-data request.
.IP "\fB\,--multipart\/\fR" .IP "\fB\,--multipart\/\fR"
Similar to \fB\,--form\/\fR, but always sends a multipart/form-data request (i.e., even without files). Similar to\fB\,--form\/\fR, but always sends a multipart/form-data request (i.e., even without files).
.IP "\fB\,--boundary\/\fR" .IP "\fB\,--boundary\/\fR"
Specify a custom boundary string for multipart/form-data requests. Only has effect only together with \fB\,--form\/\fR. Specify a custom boundary string for multipart/form-data requests. Only has effect only together with\fB\,--form\/\fR.
.IP "\fB\,--raw\/\fR" .IP "\fB\,--raw\/\fR"
@ -120,7 +119,7 @@ Specify a custom boundary string for multipart/form-data requests. Only has effe
This option allows you to pass raw request data without extra processing This option allows you to pass raw request data without extra processing
(as opposed to the structured request items syntax): (as opposed to the structured request items syntax):
$ http \fB\,--raw\/\fR=\[aq]data\[aq] pie.dev/post $ http\fB\,--raw\/\fR=\'data\' pie.dev/post
You can achieve the same by piping the data via stdin: You can achieve the same by piping the data via stdin:
@ -134,7 +133,7 @@ Or have HTTPie load the raw data from a file:
.PP .PP
.SH Content processing options .SH Content Processing Options
.IP "\fB\,--compress\/\fR, \fB\,-x\/\fR" .IP "\fB\,--compress\/\fR, \fB\,-x\/\fR"
@ -147,39 +146,39 @@ negative. Compression can be forced by repeating the argument.
.PP .PP
.SH Output processing .SH Output Processing
.IP "\fB\,--pretty\/\fR" .IP "\fB\,--pretty\/\fR"
Controls output processing. The value can be \[dq]none\[dq] to not prettify Controls output processing. The value can be "none" to not prettify
the output (default for redirected output), \[dq]all\[dq] to apply both colors the output (default for redirected output), "all" to apply both colors
and formatting (default for terminal output), \[dq]colors\[dq], or \[dq]format\[dq]. and formatting (default for terminal output), "colors", or "format".
.IP "\fB\,--style\/\fR, \fB\,-s\/\fR \fI\,STYLE\/\fR" .IP "\fB\,--style\/\fR, \fB\,-s\/\fR \fI\,STYLE\/\fR"
Output coloring style (default is \[dq]auto\[dq]). It can be one of: Output coloring style (default is "auto"). It can be one of:
auto, pie, pie-dark, pie-light, solarized auto, pie, pie-dark, pie-light, solarized
For finding out all available styles in your system, try: For finding out all available styles in your system, try:
$ http \fB\,--style\/\fR $ http\fB\,--style\/\fR
The \[dq]auto\[dq] style follows your terminal\[aq]s ANSI color styles. The "auto" style follows your terminal\'s ANSI color styles.
For non-auto styles to work properly, please make sure that the For non-auto styles to work properly, please make sure that the
$TERM environment variable is set to \[dq]xterm-256color\[dq] or similar $TERM environment variable is set to "xterm-256color" or similar
(e.g., via `export TERM=xterm-256color\[aq] in your \(ti/.bashrc). (e.g., via `export TERM=xterm-256color\' in your \~/.bashrc).
.IP "\fB\,--unsorted\/\fR" .IP "\fB\,--unsorted\/\fR"
Disables all sorting while formatting output. It is a shortcut for: Disables all sorting while formatting output. It is a shortcut for:
\fB\,--format-options\/\fR=headers.sort:false,json.sort_keys:false \fB\,--format-options\/\fR=headers.sort:false,json.sort_keys:false
@ -188,7 +187,7 @@ Disables all sorting while formatting output. It is a shortcut for:
Re-enables all sorting options while formatting output. It is a shortcut for: Re-enables all sorting options while formatting output. It is a shortcut for:
\fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true \fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true
@ -197,8 +196,8 @@ Re-enables all sorting options while formatting output. It is a shortcut for:
Override the response encoding for terminal display purposes, e.g.: Override the response encoding for terminal display purposes, e.g.:
\fB\,--response-charset\/\fR=utf8 \fB\,--response-charset\/\fR=utf8
\fB\,--response-charset\/\fR=big5 \fB\,--response-charset\/\fR=big5
@ -207,8 +206,8 @@ Override the response encoding for terminal display purposes, e.g.:
Override the response mime type for coloring and formatting for the terminal, e.g.: Override the response mime type for coloring and formatting for the terminal, e.g.:
\fB\,--response-mime\/\fR=application/json \fB\,--response-mime\/\fR=application/json
\fB\,--response-mime\/\fR=text/xml \fB\,--response-mime\/\fR=text/xml
@ -216,7 +215,7 @@ Override the response mime type for coloring and formatting for the terminal, e.
Controls output formatting. Only relevant when formatting is enabled Controls output formatting. Only relevant when formatting is enabled
through (explicit or implied) \fB\,--pretty\/\fR=all or \fB\,--pretty\/\fR=format. through (explicit or implied)\fB\,--pretty\/\fR=all or\fB\,--pretty\/\fR=format.
The following are the default options: The following are the default options:
headers.sort:true headers.sort:true
@ -230,26 +229,26 @@ You may use this option multiple times, as well as specify multiple
comma-separated options at the same time. For example, this modifies the 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: settings to disable the sorting of JSON keys, and sets the indent size to 2:
\fB\,--format-options\/\fR json.sort_keys:false,json.indent:2 \fB\,--format-options\/\fR json.sort_keys:false,json.indent:2
This is something you will typically put into your config file. This is something you will typically put into your config file.
.PP .PP
.SH Output options .SH Output Options
.IP "\fB\,--print\/\fR, \fB\,-p\/\fR \fI\,WHAT\/\fR" .IP "\fB\,--print\/\fR, \fB\,-p\/\fR \fI\,WHAT\/\fR"
String specifying what the output should contain: String specifying what the output should contain:
\[aq]H\[aq] request headers \'H\' request headers
\[aq]B\[aq] request body \'B\' request body
\[aq]h\[aq] response headers \'h\' response headers
\[aq]b\[aq] response body \'b\' response body
\[aq]m\[aq] response metadata \'m\' response metadata
The default behaviour is \[aq]hb\[aq] (i.e., the response The default behaviour is \'hb\' (i.e., the response
headers and body is printed), if standard output is not redirected. headers and body is printed), if standard output is not redirected.
If the output is piped to another program or to a file, then only the If the output is piped to another program or to a file, then only the
response body is printed by default. response body is printed by default.
@ -259,34 +258,34 @@ response body is printed by default.
.IP "\fB\,--headers\/\fR, \fB\,-h\/\fR" .IP "\fB\,--headers\/\fR, \fB\,-h\/\fR"
Print only the response headers. Shortcut for \fB\,--print\/\fR=h. Print only the response headers. Shortcut for\fB\,--print\/\fR=h.
.IP "\fB\,--meta\/\fR, \fB\,-m\/\fR" .IP "\fB\,--meta\/\fR, \fB\,-m\/\fR"
Print only the response metadata. Shortcut for \fB\,--print\/\fR=m. Print only the response metadata. Shortcut for\fB\,--print\/\fR=m.
.IP "\fB\,--body\/\fR, \fB\,-b\/\fR" .IP "\fB\,--body\/\fR, \fB\,-b\/\fR"
Print only the response body. Shortcut for \fB\,--print\/\fR=b. Print only the response body. Shortcut for\fB\,--print\/\fR=b.
.IP "\fB\,--verbose\/\fR, \fB\,-v\/\fR" .IP "\fB\,--verbose\/\fR, \fB\,-v\/\fR"
Verbose output. For the level one (with single `\fB\,-v\/\fR`/`\fB\,--verbose\/\fR`), print Verbose output. For the level one (with single \fB\,-v\/\fR`/\fB\,--verbose\/\fR`), print
the whole request as well as the response. Also print any intermediary the whole request as well as the response. Also print any intermediary
requests/responses (such as redirects). For the second level and higher, requests/responses (such as redirects). For the second level and higher,
print these as well as the response metadata. print these as well as the response metadata.
Level one is a shortcut for: \fB\,--all\/\fR \fB\,--print\/\fR=BHbh Level one is a shortcut for:\fB\,--all\/\fR\fB\,--print\/\fR=BHbh
Level two is a shortcut for: \fB\,--all\/\fR \fB\,--print\/\fR=BHbhm Level two is a shortcut for:\fB\,--all\/\fR\fB\,--print\/\fR=BHbhm
.IP "\fB\,--all\/\fR" .IP "\fB\,--all\/\fR"
@ -294,23 +293,23 @@ Level two is a shortcut for: \fB\,--all\/\fR \fB\,--print\/\fR=BHbhm
By default, only the final request/response is shown. Use this flag to show By default, only the final request/response is shown. Use this flag to show
any intermediary requests/responses as well. Intermediary requests include any intermediary requests/responses as well. Intermediary requests include
followed redirects (with \fB\,--follow\/\fR), the first unauthorized request when followed redirects (with\fB\,--follow\/\fR), the first unauthorized request when
Digest auth is used (\fB\,--auth\/\fR=digest), etc. Digest auth is used \fB\,--auth\/\fR=digest), etc.
.IP "\fB\,--stream\/\fR, \fB\,-S\/\fR" .IP "\fB\,--stream\/\fR, \fB\,-S\/\fR"
Always stream the response body by line, i.e., behave like `tail \fB\,-f\/\fR\[aq]. Always stream the response body by line, i.e., behave like `tail\fB\,-f\/\fR\'.
Without \fB\,--stream\/\fR and with \fB\,--pretty\/\fR (either set or implied), Without\fB\,--stream\/\fR and with\fB\,--pretty\/\fR (either set or implied),
HTTPie fetches the whole response before it outputs the processed data. HTTPie fetches the whole response before it outputs the processed data.
Set this option when you want to continuously display a prettified Set this option when you want to continuously display a prettified
long-lived response, such as one from the Twitter streaming API. long-lived response, such as one from the Twitter streaming API.
It is useful also without \fB\,--pretty\/\fR: It ensures that the output is flushed It is useful also without\fB\,--pretty\/\fR: It ensures that the output is flushed
more often and in smaller chunks. more often and in smaller chunks.
@ -318,7 +317,7 @@ more often and in smaller chunks.
.IP "\fB\,--output\/\fR, \fB\,-o\/\fR \fI\,FILE\/\fR" .IP "\fB\,--output\/\fR, \fB\,-o\/\fR \fI\,FILE\/\fR"
Save output to FILE instead of stdout. If \fB\,--download\/\fR is also set, then only Save output to FILE instead of stdout. If\fB\,--download\/\fR is also set, then only
the response body is saved to FILE. Other parts of the HTTP exchange are the response body is saved to FILE. Other parts of the HTTP exchange are
printed to stderr. printed to stderr.
@ -328,7 +327,7 @@ printed to stderr.
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 \fB\,--output\/\fR in a file. The filename is guessed unless specified with\fB\,--output\/\fR
[filename]. This action is similar to the default behaviour of wget. [filename]. This action is similar to the default behaviour of wget.
@ -336,7 +335,7 @@ in a file. The filename is guessed unless specified with \fB\,--output\/\fR
.IP "\fB\,--continue\/\fR, \fB\,-c\/\fR" .IP "\fB\,--continue\/\fR, \fB\,-c\/\fR"
Resume an interrupted download. Note that the \fB\,--output\/\fR option needs to be Resume an interrupted download. Note that the\fB\,--output\/\fR option needs to be
specified as well. specified as well.
@ -346,8 +345,8 @@ specified as well.
Do not print to stdout or stderr, except for errors and warnings when provided once. Do not print to stdout or stderr, except for errors and warnings when provided once.
Provide twice to suppress warnings as well. Provide twice to suppress warnings as well.
stdout is still redirected if \fB\,--output\/\fR is specified. stdout is still redirected if\fB\,--output\/\fR is specified.
Flag doesn\[aq]t affect behaviour of download beyond not printing to terminal. Flag doesn\'t affect behaviour of download beyond not printing to terminal.
@ -384,24 +383,24 @@ exchange.
For username/password based authentication mechanisms (e.g For username/password based authentication mechanisms (e.g
basic auth or digest auth) if only the username is provided basic auth or digest auth) if only the username is provided
(\fB\,-a\/\fR username), HTTPie will prompt for the password. \fB\,-a\/\fR username), HTTPie will prompt for the password.
.IP "\fB\,--auth-type\/\fR, \fB\,-A\/\fR" .IP "\fB\,--auth-type\/\fR, \fB\,-A\/\fR"
The authentication mechanism to be used. Defaults to \[dq]basic\[dq]. The authentication mechanism to be used. Defaults to "basic".
\[dq]basic\[dq]: Basic HTTP auth "basic": Basic HTTP auth
\[dq]digest\[dq]: Digest HTTP auth "digest": Digest HTTP auth
\[dq]bearer\[dq]: Bearer HTTP Auth "bearer": Bearer HTTP Auth
For finding out all available authentication types in your system, try: For finding out all available authentication types in your system, try:
$ http \fB\,--auth-type\/\fR $ http\fB\,--auth-type\/\fR
.IP "\fB\,--ignore-netrc\/\fR" .IP "\fB\,--ignore-netrc\/\fR"
@ -414,7 +413,7 @@ Ignore credentials from .netrc.
.IP "\fB\,--offline\/\fR" .IP "\fB\,--offline\/\fR"
Build the request and print it but don\(gat actually send it. Build the request and print it but don\'t actually send it.
.IP "\fB\,--proxy\/\fR \fI\,PROTOCOL:PROXY_URL\/\fR" .IP "\fB\,--proxy\/\fR \fI\,PROTOCOL:PROXY_URL\/\fR"
@ -436,7 +435,7 @@ Follow 30x Location redirects.
.IP "\fB\,--max-redirects\/\fR" .IP "\fB\,--max-redirects\/\fR"
By default, requests have a limit of 30 redirects (works with \fB\,--follow\/\fR). By default, requests have a limit of 30 redirects (works with\fB\,--follow\/\fR).
@ -467,7 +466,7 @@ exit with an error if the status indicates one.
When the server replies with a 4xx (Client Error) or 5xx (Server Error) 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 status code, HTTPie exits with 4 or 5 respectively. If the response is a
3xx (Redirect) and \fB\,--follow\/\fR hasn\[aq]t been set, then the exit status is 3. 3xx (Redirect) and\fB\,--follow\/\fR hasn\'t been set, then the exit status is 3.
Also an error message is written to stderr if stdout is redirected. Also an error message is written to stderr if stdout is redirected.
@ -489,8 +488,8 @@ Enable streaming via chunked transfer encoding. The Transfer-Encoding header is
.IP "\fB\,--verify\/\fR" .IP "\fB\,--verify\/\fR"
Set to \[dq]no\[dq] (or \[dq]false\[dq]) to skip checking the host\[aq]s SSL certificate. Set to "no" (or "false") to skip checking the host\'s SSL certificate.
Defaults to \[dq]yes\[dq] (\[dq]true\[dq]). You can also pass the path to a CA_BUNDLE file Defaults to "yes" ("true"). You can also pass the path to a CA_BUNDLE file
for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment
variable instead.) variable instead.)
@ -522,14 +521,14 @@ ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:ECDH+AESGCM:DH+AESGCM:ECDH+A
You can specify a local cert to use as client side SSL certificate. You can specify a local cert to use as client side SSL certificate.
This file may either contain both private key and certificate or you may This file may either contain both private key and certificate or you may
specify \fB\,--cert-key\/\fR separately. specify\fB\,--cert-key\/\fR separately.
.IP "\fB\,--cert-key\/\fR" .IP "\fB\,--cert-key\/\fR"
The private key to use with SSL. Only needed if \fB\,--cert\/\fR is given and the The private key to use with SSL. Only needed if\fB\,--cert\/\fR is given and the
certificate file does not contain the private key. certificate file does not contain the private key.
@ -537,9 +536,9 @@ certificate file does not contain the private key.
.IP "\fB\,--cert-key-pass\/\fR" .IP "\fB\,--cert-key-pass\/\fR"
The passphrase to be used to with the given private key. Only needed if \fB\,--cert-key\/\fR The passphrase to be used to with the given private key. Only needed if\fB\,--cert-key\/\fR
is given and the key file requires a passphrase. is given and the key file requires a passphrase.
If not provided, you\(gall be prompted interactively. If not provided, you\'ll be prompted interactively.
.PP .PP
@ -588,11 +587,4 @@ information useful for debugging HTTPie itself and for reporting bugs.
.PP .PP
.SH SEE ALSO
For every \fB\,--OPTION\/\fR there is also a \fB\,--no-OPTION\/\fR that reverts OPTION
to its default value.
Suggestions and bug reports are greatly appreciated:
https://github.com/httpie/httpie/issues

View File

@ -1,9 +1,9 @@
.\" This file is auto-generated from the parser declaration in httpie/manager/cli.py by extras/scripts/generate_man_pages.py. .TH httpie 1 "2022-03-08" "HTTPie 3.1.1.dev0" "HTTPie Manual"
.TH httpie 1 "2022-05-06" "HTTPie 3.2.1" "HTTPie Manual"
.SH NAME .SH NAME
httpie httpie
.SH SYNOPSIS .SH SYNOPSIS
httpie httpie HOSTNAME SESSION_NAME_OR_PATH TARGET TARGET TARGET TARGET TARGET TARGET
.SH DESCRIPTION .SH DESCRIPTION
Managing interface for the HTTPie itself. <https://httpie.io/docs#manager> Managing interface for the HTTPie itself. <https://httpie.io/docs#manager>
@ -12,21 +12,12 @@ Be aware that you might be looking for http/https commands for sending
HTTP requests. This command is only available for managing the HTTTPie HTTP requests. This command is only available for managing the HTTTPie
plugins and the configuration around it. plugins and the configuration around it.
If you are looking for the man pages of http/https commands, try one of the following:
$ man http
$ man https
.SH httpie cli export-args .SH httpie cli export-args
Export available options for the CLI Export available options for the CLI
.IP "\fB\,-f\/\fR, \fB\,--format\/\fR" .IP "\fB\,-f\/\fR, \fB\,--format\/\fR"
Format to export in.
.PP
.SH httpie cli check-updates
Check for updates
.PP .PP
.SH httpie cli sessions upgrade .SH httpie cli sessions upgrade
Upgrade the given HTTPie session with the latest layout. A list of changes between different session versions can be found in the official documentation. Upgrade the given HTTPie session with the latest layout. A list of changes between different session versions can be found in the official documentation.

View File

@ -1,5 +1,4 @@
.\" This file is auto-generated from the parser declaration in httpie/cli/definition.py by extras/scripts/generate_man_pages.py. .TH https 1 "2022-03-08" "HTTPie 3.1.1.dev0" "HTTPie Manual"
.TH https 1 "2022-05-06" "HTTPie 3.2.1" "HTTPie Manual"
.SH NAME .SH NAME
https https
.SH SYNOPSIS .SH SYNOPSIS
@ -7,7 +6,7 @@ https [METHOD] URL [REQUEST_ITEM ...]
.SH DESCRIPTION .SH DESCRIPTION
HTTPie: modern, user-friendly command-line HTTP client for the API era. <https://httpie.io> HTTPie: modern, user-friendly command-line HTTP client for the API era. <https://httpie.io>
.SH Positional arguments .SH Positional Arguments
These arguments come after any flags and in the order they are listed here. These arguments come after any flags and in the order they are listed here.
Only URL is required. Only URL is required.
@ -28,8 +27,8 @@ is some data to be sent, otherwise GET:
.IP "\fB\,URL\/\fR" .IP "\fB\,URL\/\fR"
The request URL. Scheme defaults to \[aq]http://\[aq] if the URL The request URL. Scheme defaults to \'http://\' if the URL
does not include one. (You can override this with: \fB\,--default-scheme\/\fR=http/https) does not include one. (You can override this with:\fB\,--default-scheme\/\fR=http/https)
You can also use a shorthand for localhost You can also use a shorthand for localhost
@ -44,44 +43,44 @@ You can also use a shorthand for localhost
Optional key-value pairs to be included in the request. The separator used Optional key-value pairs to be included in the request. The separator used
determines the type: determines the type:
\[aq]:\[aq] HTTP headers: \':\' HTTP headers:
Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0 Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0
\[aq]==\[aq] URL parameters to be appended to the request URI: \'==\' URL parameters to be appended to the request URI:
search==httpie search==httpie
\[aq]=\[aq] Data fields to be serialized into a JSON object (with \fB\,--json\/\fR, \fB\,-j\/\fR) \'=\' Data fields to be serialized into a JSON object (with\fB\,--json\/\fR,\fB\,-j\/\fR)
or form data (with \fB\,--form\/\fR, \fB\,-f\/\fR): or form data (with\fB\,--form\/\fR,\fB\,-f\/\fR):
name=HTTPie language=Python description=\[aq]CLI HTTP client\[aq] name=HTTPie language=Python description=\'CLI HTTP client\'
\[aq]:=\[aq] Non-string JSON data fields (only with \fB\,--json\/\fR, \fB\,-j\/\fR): \':=\' Non-string JSON data fields (only with\fB\,--json\/\fR,\fB\,-j\/\fR):
awesome:=true amount:=42 colors:=\[aq][\[dq]red\[dq], \[dq]green\[dq], \[dq]blue\[dq]]\[aq] awesome:=true amount:=42 colors:=\'["red", "green", "blue"]\'
\[aq]@\[aq] Form file fields (only with \fB\,--form\/\fR or \fB\,--multipart\/\fR): \'@\' Form file fields (only with\fB\,--form\/\fR or\fB\,--multipart\/\fR):
cv@\(ti/Documents/CV.pdf cv@\~/Documents/CV.pdf
cv@\[aq]\(ti/Documents/CV.pdf;type=application/pdf\[aq] cv@\'\~/Documents/CV.pdf;type=application/pdf\'
\[aq]=@\[aq] A data field like \[aq]=\[aq], but takes a file path and embeds its content: \'=@\' A data field like \'=\', but takes a file path and embeds its content:
essay=@Documents/essay.txt essay=@Documents/essay.txt
\[aq]:=@\[aq] A raw JSON field like \[aq]:=\[aq], but takes a file path and embeds its content: \':=@\' A raw JSON field like \':=\', but takes a file path and embeds its content:
package:=@./package.json package:=@./package.json
You can use a backslash to escape a colliding separator in the field name: You can use a backslash to escape a colliding separator in the field name:
field-name-with\e:colon=value field-name-with\\:colon=value
.PP .PP
.SH Predefined content types .SH Predefined Content Types
.IP "\fB\,--json\/\fR, \fB\,-j\/\fR" .IP "\fB\,--json\/\fR, \fB\,-j\/\fR"
@ -105,13 +104,13 @@ multipart/form-data request.
.IP "\fB\,--multipart\/\fR" .IP "\fB\,--multipart\/\fR"
Similar to \fB\,--form\/\fR, but always sends a multipart/form-data request (i.e., even without files). Similar to\fB\,--form\/\fR, but always sends a multipart/form-data request (i.e., even without files).
.IP "\fB\,--boundary\/\fR" .IP "\fB\,--boundary\/\fR"
Specify a custom boundary string for multipart/form-data requests. Only has effect only together with \fB\,--form\/\fR. Specify a custom boundary string for multipart/form-data requests. Only has effect only together with\fB\,--form\/\fR.
.IP "\fB\,--raw\/\fR" .IP "\fB\,--raw\/\fR"
@ -120,7 +119,7 @@ Specify a custom boundary string for multipart/form-data requests. Only has effe
This option allows you to pass raw request data without extra processing This option allows you to pass raw request data without extra processing
(as opposed to the structured request items syntax): (as opposed to the structured request items syntax):
$ http \fB\,--raw\/\fR=\[aq]data\[aq] pie.dev/post $ http\fB\,--raw\/\fR=\'data\' pie.dev/post
You can achieve the same by piping the data via stdin: You can achieve the same by piping the data via stdin:
@ -134,7 +133,7 @@ Or have HTTPie load the raw data from a file:
.PP .PP
.SH Content processing options .SH Content Processing Options
.IP "\fB\,--compress\/\fR, \fB\,-x\/\fR" .IP "\fB\,--compress\/\fR, \fB\,-x\/\fR"
@ -147,39 +146,39 @@ negative. Compression can be forced by repeating the argument.
.PP .PP
.SH Output processing .SH Output Processing
.IP "\fB\,--pretty\/\fR" .IP "\fB\,--pretty\/\fR"
Controls output processing. The value can be \[dq]none\[dq] to not prettify Controls output processing. The value can be "none" to not prettify
the output (default for redirected output), \[dq]all\[dq] to apply both colors the output (default for redirected output), "all" to apply both colors
and formatting (default for terminal output), \[dq]colors\[dq], or \[dq]format\[dq]. and formatting (default for terminal output), "colors", or "format".
.IP "\fB\,--style\/\fR, \fB\,-s\/\fR \fI\,STYLE\/\fR" .IP "\fB\,--style\/\fR, \fB\,-s\/\fR \fI\,STYLE\/\fR"
Output coloring style (default is \[dq]auto\[dq]). It can be one of: Output coloring style (default is "auto"). It can be one of:
auto, pie, pie-dark, pie-light, solarized auto, pie, pie-dark, pie-light, solarized
For finding out all available styles in your system, try: For finding out all available styles in your system, try:
$ http \fB\,--style\/\fR $ http\fB\,--style\/\fR
The \[dq]auto\[dq] style follows your terminal\[aq]s ANSI color styles. The "auto" style follows your terminal\'s ANSI color styles.
For non-auto styles to work properly, please make sure that the For non-auto styles to work properly, please make sure that the
$TERM environment variable is set to \[dq]xterm-256color\[dq] or similar $TERM environment variable is set to "xterm-256color" or similar
(e.g., via `export TERM=xterm-256color\[aq] in your \(ti/.bashrc). (e.g., via `export TERM=xterm-256color\' in your \~/.bashrc).
.IP "\fB\,--unsorted\/\fR" .IP "\fB\,--unsorted\/\fR"
Disables all sorting while formatting output. It is a shortcut for: Disables all sorting while formatting output. It is a shortcut for:
\fB\,--format-options\/\fR=headers.sort:false,json.sort_keys:false \fB\,--format-options\/\fR=headers.sort:false,json.sort_keys:false
@ -188,7 +187,7 @@ Disables all sorting while formatting output. It is a shortcut for:
Re-enables all sorting options while formatting output. It is a shortcut for: Re-enables all sorting options while formatting output. It is a shortcut for:
\fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true \fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true
@ -197,8 +196,8 @@ Re-enables all sorting options while formatting output. It is a shortcut for:
Override the response encoding for terminal display purposes, e.g.: Override the response encoding for terminal display purposes, e.g.:
\fB\,--response-charset\/\fR=utf8 \fB\,--response-charset\/\fR=utf8
\fB\,--response-charset\/\fR=big5 \fB\,--response-charset\/\fR=big5
@ -207,8 +206,8 @@ Override the response encoding for terminal display purposes, e.g.:
Override the response mime type for coloring and formatting for the terminal, e.g.: Override the response mime type for coloring and formatting for the terminal, e.g.:
\fB\,--response-mime\/\fR=application/json \fB\,--response-mime\/\fR=application/json
\fB\,--response-mime\/\fR=text/xml \fB\,--response-mime\/\fR=text/xml
@ -216,7 +215,7 @@ Override the response mime type for coloring and formatting for the terminal, e.
Controls output formatting. Only relevant when formatting is enabled Controls output formatting. Only relevant when formatting is enabled
through (explicit or implied) \fB\,--pretty\/\fR=all or \fB\,--pretty\/\fR=format. through (explicit or implied)\fB\,--pretty\/\fR=all or\fB\,--pretty\/\fR=format.
The following are the default options: The following are the default options:
headers.sort:true headers.sort:true
@ -230,26 +229,26 @@ You may use this option multiple times, as well as specify multiple
comma-separated options at the same time. For example, this modifies the 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: settings to disable the sorting of JSON keys, and sets the indent size to 2:
\fB\,--format-options\/\fR json.sort_keys:false,json.indent:2 \fB\,--format-options\/\fR json.sort_keys:false,json.indent:2
This is something you will typically put into your config file. This is something you will typically put into your config file.
.PP .PP
.SH Output options .SH Output Options
.IP "\fB\,--print\/\fR, \fB\,-p\/\fR \fI\,WHAT\/\fR" .IP "\fB\,--print\/\fR, \fB\,-p\/\fR \fI\,WHAT\/\fR"
String specifying what the output should contain: String specifying what the output should contain:
\[aq]H\[aq] request headers \'H\' request headers
\[aq]B\[aq] request body \'B\' request body
\[aq]h\[aq] response headers \'h\' response headers
\[aq]b\[aq] response body \'b\' response body
\[aq]m\[aq] response metadata \'m\' response metadata
The default behaviour is \[aq]hb\[aq] (i.e., the response The default behaviour is \'hb\' (i.e., the response
headers and body is printed), if standard output is not redirected. headers and body is printed), if standard output is not redirected.
If the output is piped to another program or to a file, then only the If the output is piped to another program or to a file, then only the
response body is printed by default. response body is printed by default.
@ -259,34 +258,34 @@ response body is printed by default.
.IP "\fB\,--headers\/\fR, \fB\,-h\/\fR" .IP "\fB\,--headers\/\fR, \fB\,-h\/\fR"
Print only the response headers. Shortcut for \fB\,--print\/\fR=h. Print only the response headers. Shortcut for\fB\,--print\/\fR=h.
.IP "\fB\,--meta\/\fR, \fB\,-m\/\fR" .IP "\fB\,--meta\/\fR, \fB\,-m\/\fR"
Print only the response metadata. Shortcut for \fB\,--print\/\fR=m. Print only the response metadata. Shortcut for\fB\,--print\/\fR=m.
.IP "\fB\,--body\/\fR, \fB\,-b\/\fR" .IP "\fB\,--body\/\fR, \fB\,-b\/\fR"
Print only the response body. Shortcut for \fB\,--print\/\fR=b. Print only the response body. Shortcut for\fB\,--print\/\fR=b.
.IP "\fB\,--verbose\/\fR, \fB\,-v\/\fR" .IP "\fB\,--verbose\/\fR, \fB\,-v\/\fR"
Verbose output. For the level one (with single `\fB\,-v\/\fR`/`\fB\,--verbose\/\fR`), print Verbose output. For the level one (with single \fB\,-v\/\fR`/\fB\,--verbose\/\fR`), print
the whole request as well as the response. Also print any intermediary the whole request as well as the response. Also print any intermediary
requests/responses (such as redirects). For the second level and higher, requests/responses (such as redirects). For the second level and higher,
print these as well as the response metadata. print these as well as the response metadata.
Level one is a shortcut for: \fB\,--all\/\fR \fB\,--print\/\fR=BHbh Level one is a shortcut for:\fB\,--all\/\fR\fB\,--print\/\fR=BHbh
Level two is a shortcut for: \fB\,--all\/\fR \fB\,--print\/\fR=BHbhm Level two is a shortcut for:\fB\,--all\/\fR\fB\,--print\/\fR=BHbhm
.IP "\fB\,--all\/\fR" .IP "\fB\,--all\/\fR"
@ -294,23 +293,23 @@ Level two is a shortcut for: \fB\,--all\/\fR \fB\,--print\/\fR=BHbhm
By default, only the final request/response is shown. Use this flag to show By default, only the final request/response is shown. Use this flag to show
any intermediary requests/responses as well. Intermediary requests include any intermediary requests/responses as well. Intermediary requests include
followed redirects (with \fB\,--follow\/\fR), the first unauthorized request when followed redirects (with\fB\,--follow\/\fR), the first unauthorized request when
Digest auth is used (\fB\,--auth\/\fR=digest), etc. Digest auth is used \fB\,--auth\/\fR=digest), etc.
.IP "\fB\,--stream\/\fR, \fB\,-S\/\fR" .IP "\fB\,--stream\/\fR, \fB\,-S\/\fR"
Always stream the response body by line, i.e., behave like `tail \fB\,-f\/\fR\[aq]. Always stream the response body by line, i.e., behave like `tail\fB\,-f\/\fR\'.
Without \fB\,--stream\/\fR and with \fB\,--pretty\/\fR (either set or implied), Without\fB\,--stream\/\fR and with\fB\,--pretty\/\fR (either set or implied),
HTTPie fetches the whole response before it outputs the processed data. HTTPie fetches the whole response before it outputs the processed data.
Set this option when you want to continuously display a prettified Set this option when you want to continuously display a prettified
long-lived response, such as one from the Twitter streaming API. long-lived response, such as one from the Twitter streaming API.
It is useful also without \fB\,--pretty\/\fR: It ensures that the output is flushed It is useful also without\fB\,--pretty\/\fR: It ensures that the output is flushed
more often and in smaller chunks. more often and in smaller chunks.
@ -318,7 +317,7 @@ more often and in smaller chunks.
.IP "\fB\,--output\/\fR, \fB\,-o\/\fR \fI\,FILE\/\fR" .IP "\fB\,--output\/\fR, \fB\,-o\/\fR \fI\,FILE\/\fR"
Save output to FILE instead of stdout. If \fB\,--download\/\fR is also set, then only Save output to FILE instead of stdout. If\fB\,--download\/\fR is also set, then only
the response body is saved to FILE. Other parts of the HTTP exchange are the response body is saved to FILE. Other parts of the HTTP exchange are
printed to stderr. printed to stderr.
@ -328,7 +327,7 @@ printed to stderr.
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 \fB\,--output\/\fR in a file. The filename is guessed unless specified with\fB\,--output\/\fR
[filename]. This action is similar to the default behaviour of wget. [filename]. This action is similar to the default behaviour of wget.
@ -336,7 +335,7 @@ in a file. The filename is guessed unless specified with \fB\,--output\/\fR
.IP "\fB\,--continue\/\fR, \fB\,-c\/\fR" .IP "\fB\,--continue\/\fR, \fB\,-c\/\fR"
Resume an interrupted download. Note that the \fB\,--output\/\fR option needs to be Resume an interrupted download. Note that the\fB\,--output\/\fR option needs to be
specified as well. specified as well.
@ -346,8 +345,8 @@ specified as well.
Do not print to stdout or stderr, except for errors and warnings when provided once. Do not print to stdout or stderr, except for errors and warnings when provided once.
Provide twice to suppress warnings as well. Provide twice to suppress warnings as well.
stdout is still redirected if \fB\,--output\/\fR is specified. stdout is still redirected if\fB\,--output\/\fR is specified.
Flag doesn\[aq]t affect behaviour of download beyond not printing to terminal. Flag doesn\'t affect behaviour of download beyond not printing to terminal.
@ -384,24 +383,24 @@ exchange.
For username/password based authentication mechanisms (e.g For username/password based authentication mechanisms (e.g
basic auth or digest auth) if only the username is provided basic auth or digest auth) if only the username is provided
(\fB\,-a\/\fR username), HTTPie will prompt for the password. \fB\,-a\/\fR username), HTTPie will prompt for the password.
.IP "\fB\,--auth-type\/\fR, \fB\,-A\/\fR" .IP "\fB\,--auth-type\/\fR, \fB\,-A\/\fR"
The authentication mechanism to be used. Defaults to \[dq]basic\[dq]. The authentication mechanism to be used. Defaults to "basic".
\[dq]basic\[dq]: Basic HTTP auth "basic": Basic HTTP auth
\[dq]digest\[dq]: Digest HTTP auth "digest": Digest HTTP auth
\[dq]bearer\[dq]: Bearer HTTP Auth "bearer": Bearer HTTP Auth
For finding out all available authentication types in your system, try: For finding out all available authentication types in your system, try:
$ http \fB\,--auth-type\/\fR $ http\fB\,--auth-type\/\fR
.IP "\fB\,--ignore-netrc\/\fR" .IP "\fB\,--ignore-netrc\/\fR"
@ -414,7 +413,7 @@ Ignore credentials from .netrc.
.IP "\fB\,--offline\/\fR" .IP "\fB\,--offline\/\fR"
Build the request and print it but don\(gat actually send it. Build the request and print it but don\'t actually send it.
.IP "\fB\,--proxy\/\fR \fI\,PROTOCOL:PROXY_URL\/\fR" .IP "\fB\,--proxy\/\fR \fI\,PROTOCOL:PROXY_URL\/\fR"
@ -436,7 +435,7 @@ Follow 30x Location redirects.
.IP "\fB\,--max-redirects\/\fR" .IP "\fB\,--max-redirects\/\fR"
By default, requests have a limit of 30 redirects (works with \fB\,--follow\/\fR). By default, requests have a limit of 30 redirects (works with\fB\,--follow\/\fR).
@ -467,7 +466,7 @@ exit with an error if the status indicates one.
When the server replies with a 4xx (Client Error) or 5xx (Server Error) 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 status code, HTTPie exits with 4 or 5 respectively. If the response is a
3xx (Redirect) and \fB\,--follow\/\fR hasn\[aq]t been set, then the exit status is 3. 3xx (Redirect) and\fB\,--follow\/\fR hasn\'t been set, then the exit status is 3.
Also an error message is written to stderr if stdout is redirected. Also an error message is written to stderr if stdout is redirected.
@ -489,8 +488,8 @@ Enable streaming via chunked transfer encoding. The Transfer-Encoding header is
.IP "\fB\,--verify\/\fR" .IP "\fB\,--verify\/\fR"
Set to \[dq]no\[dq] (or \[dq]false\[dq]) to skip checking the host\[aq]s SSL certificate. Set to "no" (or "false") to skip checking the host\'s SSL certificate.
Defaults to \[dq]yes\[dq] (\[dq]true\[dq]). You can also pass the path to a CA_BUNDLE file Defaults to "yes" ("true"). You can also pass the path to a CA_BUNDLE file
for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment
variable instead.) variable instead.)
@ -522,14 +521,14 @@ ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:ECDH+AESGCM:DH+AESGCM:ECDH+A
You can specify a local cert to use as client side SSL certificate. You can specify a local cert to use as client side SSL certificate.
This file may either contain both private key and certificate or you may This file may either contain both private key and certificate or you may
specify \fB\,--cert-key\/\fR separately. specify\fB\,--cert-key\/\fR separately.
.IP "\fB\,--cert-key\/\fR" .IP "\fB\,--cert-key\/\fR"
The private key to use with SSL. Only needed if \fB\,--cert\/\fR is given and the The private key to use with SSL. Only needed if\fB\,--cert\/\fR is given and the
certificate file does not contain the private key. certificate file does not contain the private key.
@ -537,9 +536,9 @@ certificate file does not contain the private key.
.IP "\fB\,--cert-key-pass\/\fR" .IP "\fB\,--cert-key-pass\/\fR"
The passphrase to be used to with the given private key. Only needed if \fB\,--cert-key\/\fR The passphrase to be used to with the given private key. Only needed if\fB\,--cert-key\/\fR
is given and the key file requires a passphrase. is given and the key file requires a passphrase.
If not provided, you\(gall be prompted interactively. If not provided, you\'ll be prompted interactively.
.PP .PP
@ -588,11 +587,4 @@ information useful for debugging HTTPie itself and for reporting bugs.
.PP .PP
.SH SEE ALSO
For every \fB\,--OPTION\/\fR there is also a \fB\,--no-OPTION\/\fR that reverts OPTION
to its default value.
Suggestions and bug reports are greatly appreciated:
https://github.com/httpie/httpie/issues

View File

@ -6,9 +6,6 @@ from typing import Iterator, Tuple
BUILD_DIR = Path(__file__).parent BUILD_DIR = Path(__file__).parent
HTTPIE_DIR = BUILD_DIR.parent.parent.parent HTTPIE_DIR = BUILD_DIR.parent.parent.parent
EXTRAS_DIR = HTTPIE_DIR / 'extras'
MAN_PAGES_DIR = EXTRAS_DIR / 'man'
SCRIPT_DIR = BUILD_DIR / Path('scripts') SCRIPT_DIR = BUILD_DIR / Path('scripts')
HOOKS_DIR = SCRIPT_DIR / 'hooks' HOOKS_DIR = SCRIPT_DIR / 'hooks'
@ -53,11 +50,6 @@ def build_packages(http_binary: Path, httpie_binary: Path) -> None:
(http_binary, '/usr/bin/https'), (http_binary, '/usr/bin/https'),
(httpie_binary, '/usr/bin/httpie'), (httpie_binary, '/usr/bin/httpie'),
] ]
files.extend(
(man_page, f'/usr/share/man/man1/{man_page.name}')
for man_page in MAN_PAGES_DIR.glob('*.1')
)
# A list of additional dependencies # A list of additional dependencies
deps = [ deps = [
'python3 >= 3.7', 'python3 >= 3.7',

View File

@ -10,7 +10,7 @@ Ensure the following requirements are satisfied:
- Python 3.7+ - Python 3.7+
- `pyperf` - `pyperf`
Then, run the `extras/profiling/run.py`: Then, run the `extras/benchmarks/run.py`:
```console ```console
$ python extras/profiling/run.py $ python extras/profiling/run.py

View File

@ -9,11 +9,11 @@ timings.
The benchmarks are run through 'pyperf', which allows to The benchmarks are run through 'pyperf', which allows to
do get very precise results. For micro-benchmarks like startup, do get very precise results. For micro-benchmarks like startup,
please run `pyperf system tune` to get even more accurate results. please run `pyperf system tune` to get even more acurrate results.
Examples: Examples:
# Run everything as usual, the default is that we do 3 warm-up runs # Run everything as usual, the default is that we do 3 warmup runs
# and 5 actual runs. # and 5 actual runs.
$ python extras/profiling/benchmarks.py $ python extras/profiling/benchmarks.py
@ -188,7 +188,7 @@ DownloadRunner('download', '`http --download :/big_file.txt` (3GB)', '3G')
def main() -> None: def main() -> None:
# PyPerf will bring it's own argument parser, so configure the script. # PyPerf will bring it's own argument parser, so configure the script.
# The somewhat fast and also precise enough configuration is this. We run # The somewhat fast and also precise enough configuration is this. We run
# benchmarks 3 times to warm up (e.g especially for download benchmark, this # benchmarks 3 times to warmup (e.g especially for download benchmark, this
# is important). And then 5 actual runs where we record. # is important). And then 5 actual runs where we record.
sys.argv.extend( sys.argv.extend(
['--worker', '--loops=1', '--warmup=3', '--values=5', '--processes=2'] ['--worker', '--loops=1', '--warmup=3', '--values=5', '--processes=2']

View File

@ -19,19 +19,19 @@ which would include additional dependencies like pyOpenSSL.
Examples: Examples:
# Run everything as usual, and compare last commit with master # Run everything as usual, and compare last commit with master
$ python extras/profiling/run.py $ python extras/benchmarks/run.py
# Include complex environments # Include complex environments
$ python extras/profiling/run.py --complex $ python extras/benchmarks/run.py --complex
# Compare against a fresh copy # Compare against a fresh copy
$ python extras/profiling/run.py --fresh $ python extras/benchmarks/run.py --fresh
# Compare against a custom branch of a custom repo # Compare against a custom branch of a custom repo
$ python extras/profiling/run.py --target-repo my_repo --target-branch my_branch $ python extras/benchmarks/run.py --target-repo my_repo --target-branch my_branch
# Debug changes made on this script (only run benchmarks once) # Debug changes made on this script (only run benchmarks once)
$ python extras/profiling/run.py --debug $ python extras/benchmarks/run.py --debug
""" """
import dataclasses import dataclasses

View File

@ -9,24 +9,21 @@ from httpie.cli.options import ParserSpec
from httpie.manager.cli import options as manager_options from httpie.manager.cli import options as manager_options
from httpie.output.ui.rich_help import OptionsHighlighter, to_usage from httpie.output.ui.rich_help import OptionsHighlighter, to_usage
from httpie.output.ui.rich_utils import render_as_string from httpie.output.ui.rich_utils import render_as_string
from httpie.utils import split_iterable from httpie.utils import split
# Escape certain characters so they are rendered properly on # Escape certain characters so they are rendered properly on
# all terminals. # all terminals.
# https://man7.org/linux/man-pages/man7/groff_char.7.html
ESCAPE_MAP = { ESCAPE_MAP = {
'"': '\[dq]', "'": "\\'",
"'": '\[aq]', '~': '\\~',
'~': '\(ti', '': "\\'",
'': "\(ga", '\\': '\\\\',
'\\': '\e',
} }
ESCAPE_MAP = {ord(key): value for key, value in ESCAPE_MAP.items()} ESCAPE_MAP = {ord(key): value for key, value in ESCAPE_MAP.items()}
EXTRAS_DIR = Path(__file__).parent.parent EXTRAS_DIR = Path(__file__).parent.parent
MAN_PAGE_PATH = EXTRAS_DIR / 'man' MAN_PAGE_PATH = EXTRAS_DIR / 'man'
PROJECT_ROOT = EXTRAS_DIR.parent
OPTION_HIGHLIGHT_RE = re.compile( OPTION_HIGHLIGHT_RE = re.compile(
OptionsHighlighter.highlights[0] OptionsHighlighter.highlights[0]
@ -60,18 +57,6 @@ class ManPageBuilder:
def separate(self) -> None: def separate(self) -> None:
self.source.append('.PP') self.source.append('.PP')
def format_desc(self, desc: str) -> str:
description = _escape_and_dedent(desc)
description = OPTION_HIGHLIGHT_RE.sub(
# Boldify the option part, but don't remove the prefix (start of the match).
lambda match: match[1] + self.boldify(match['option']),
description
)
return description
def add_comment(self, comment: str) -> None:
self.source.append(f'.\\" {comment}')
def add_options(self, options: Iterable[str], *, metavar: Optional[str] = None) -> None: def add_options(self, options: Iterable[str], *, metavar: Optional[str] = None) -> None:
text = ", ".join(map(self.boldify, options)) text = ", ".join(map(self.boldify, options))
if metavar: if metavar:
@ -107,13 +92,8 @@ def _escape_and_dedent(text: str) -> str:
return '\n'.join(lines).translate(ESCAPE_MAP) return '\n'.join(lines).translate(ESCAPE_MAP)
def to_man_page(program_name: str, spec: ParserSpec, *, is_top_level_cmd: bool = False) -> str: def to_man_page(program_name: str, spec: ParserSpec) -> str:
builder = ManPageBuilder() builder = ManPageBuilder()
builder.add_comment(
f"This file is auto-generated from the parser declaration "
+ (f"in {Path(spec.source_file).relative_to(PROJECT_ROOT)} " if spec.source_file else "")
+ f"by {Path(__file__).relative_to(PROJECT_ROOT)}."
)
builder.title_line( builder.title_line(
full_name='HTTPie', full_name='HTTPie',
@ -124,19 +104,10 @@ def to_man_page(program_name: str, spec: ParserSpec, *, is_top_level_cmd: bool =
builder.set_name(program_name) builder.set_name(program_name)
with builder.section('SYNOPSIS'): with builder.section('SYNOPSIS'):
# `http` and `https` are commands that can be directly used, so they can have builder.write(render_as_string(to_usage(spec, program_name=program_name)))
# have a valid usage. But `httpie` is a top-level command with multiple sub commands,
# so for the synopsis we'll only reference the `httpie` name.
if is_top_level_cmd:
synopsis = program_name
else:
synopsis = render_as_string(to_usage(spec, program_name=program_name))
builder.write(synopsis)
with builder.section('DESCRIPTION'): with builder.section('DESCRIPTION'):
builder.write(spec.description) builder.write(spec.description)
if spec.man_page_hint:
builder.write(spec.man_page_hint)
for index, group in enumerate(spec.groups, 1): for index, group in enumerate(spec.groups, 1):
with builder.section(group.name): with builder.section(group.name):
@ -156,26 +127,28 @@ def to_man_page(program_name: str, spec: ParserSpec, *, is_top_level_cmd: bool =
metavar = None metavar = None
builder.add_options(raw_arg['options'], metavar=metavar) builder.add_options(raw_arg['options'], metavar=metavar)
desc = builder.format_desc(raw_arg.get('description', '')) description = _escape_and_dedent(raw_arg.get('description', ''))
builder.write('\n' + desc + '\n') description = OPTION_HIGHLIGHT_RE.sub(
lambda match: builder.boldify(match['option']),
description
)
builder.write('\n' + description + '\n')
builder.separate() builder.separate()
if spec.epilog:
with builder.section('SEE ALSO'):
builder.write(builder.format_desc(spec.epilog))
return builder.build() return builder.build()
def main() -> None: def main() -> None:
for program_name, spec, config in [ for program_name, spec in [
('http', core_options, {}), ('http', core_options),
('https', core_options, {}), ('https', core_options),
('httpie', manager_options, {'is_top_level_cmd': True}), ('httpie', manager_options),
]: ]:
with open((MAN_PAGE_PATH / program_name).with_suffix('.1'), 'w') as stream: with open((MAN_PAGE_PATH / program_name).with_suffix('.1'), 'w') as stream:
stream.write(to_man_page(program_name, spec, **config)) stream.write(to_man_page(program_name, spec))

View File

@ -3,7 +3,7 @@ HTTPie: modern, user-friendly command-line HTTP client for the API era.
""" """
__version__ = '3.2.2' __version__ = '3.1.1.dev0'
__date__ = '2022-05-06' __date__ = '2022-03-08'
__author__ = 'Jakub Roztocil' __author__ = 'Jakub Roztocil'
__licence__ = 'BSD' __licence__ = 'BSD'

View File

@ -572,6 +572,12 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
highlight=False highlight=False
) )
def print_help(self):
from httpie.output.ui import rich_help
for renderable in rich_help.to_help_message(self.spec):
self.env.rich_console.print(renderable)
def print_usage(self, file): def print_usage(self, file):
from rich.text import Text from rich.text import Text
from httpie.output.ui import rich_help from httpie.output.ui import rich_help

View File

@ -132,3 +132,10 @@ class RequestType(enum.Enum):
FORM = enum.auto() FORM = enum.auto()
MULTIPART = enum.auto() MULTIPART = enum.auto()
JSON = enum.auto() JSON = enum.auto()
EMPTY_STRING = ''
OPEN_BRACKET = '['
CLOSE_BRACKET = ']'
BACKSLASH = '\\'
HIGHLIGHTER = '^'

View File

@ -20,19 +20,22 @@ from httpie.output.formatters.colors import (AUTO_STYLE, DEFAULT_STYLE, BUNDLED_
get_available_styles) get_available_styles)
from httpie.plugins.builtin import BuiltinAuthPlugin from httpie.plugins.builtin import BuiltinAuthPlugin
from httpie.plugins.registry import plugin_manager from httpie.plugins.registry import plugin_manager
from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS_STRING from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS
options = ParserSpec( options = ParserSpec(
'http', 'http',
description=f'{__doc__.strip()} <https://httpie.io>', description=f'{__doc__.strip()} <https://httpie.io>',
epilog=""" epilog="""
To learn more, you can try:
-> running 'http --manual'
-> visiting our full documentation at https://httpie.io/docs/cli
For every --OPTION there is also a --no-OPTION that reverts OPTION For every --OPTION there is also a --no-OPTION that reverts OPTION
to its default value. to its default value.
Suggestions and bug reports are greatly appreciated: Suggestions and bug reports are greatly appreciated:
https://github.com/httpie/httpie/issues https://github.com/httpie/httpie/issues
""", """,
source_file=__file__
) )
@ -832,9 +835,9 @@ ssl.add_argument(
help=f""" help=f"""
A string in the OpenSSL cipher list format. By default, the following A string in the OpenSSL cipher list format. By default, the following
ciphers are used on your system: is used:
{DEFAULT_SSL_CIPHERS_STRING} {DEFAULT_SSL_CIPHERS}
""", """,
) )

View File

@ -92,3 +92,7 @@ class MultipartRequestDataDict(MultiValueOrderedDict):
class RequestFilesDict(RequestDataDict): class RequestFilesDict(RequestDataDict):
pass pass
class NestedJSONArray(list):
"""Denotes a top-level JSON array."""

392
httpie/cli/nested_json.py Normal file
View File

@ -0,0 +1,392 @@
from enum import Enum, auto
from typing import (
Any,
Iterator,
NamedTuple,
Optional,
List,
NoReturn,
Type,
Union,
)
from httpie.cli.dicts import NestedJSONArray
from httpie.cli.constants import EMPTY_STRING, OPEN_BRACKET, CLOSE_BRACKET, BACKSLASH, HIGHLIGHTER
class HTTPieSyntaxError(ValueError):
def __init__(
self,
source: str,
token: Optional['Token'],
message: str,
message_kind: str = 'Syntax',
) -> None:
self.source = source
self.token = token
self.message = message
self.message_kind = message_kind
def __str__(self):
lines = [f'HTTPie {self.message_kind} Error: {self.message}']
if self.token is not None:
lines.append(self.source)
lines.append(
' ' * (self.token.start)
+ HIGHLIGHTER * (self.token.end - self.token.start)
)
return '\n'.join(lines)
class TokenKind(Enum):
TEXT = auto()
NUMBER = auto()
LEFT_BRACKET = auto()
RIGHT_BRACKET = auto()
def to_name(self) -> str:
for key, value in OPERATORS.items():
if value is self:
return repr(key)
else:
return 'a ' + self.name.lower()
OPERATORS = {OPEN_BRACKET: TokenKind.LEFT_BRACKET, CLOSE_BRACKET: TokenKind.RIGHT_BRACKET}
SPECIAL_CHARS = OPERATORS.keys() | {BACKSLASH}
LITERAL_TOKENS = [TokenKind.TEXT, TokenKind.NUMBER]
class Token(NamedTuple):
kind: TokenKind
value: Union[str, int]
start: int
end: int
def assert_cant_happen() -> NoReturn:
raise ValueError('Unexpected value')
def check_escaped_int(value: str) -> str:
if not value.startswith(BACKSLASH):
raise ValueError('Not an escaped int')
try:
int(value[1:])
except ValueError as exc:
raise ValueError('Not an escaped int') from exc
else:
return value[1:]
def tokenize(source: str) -> Iterator[Token]:
cursor = 0
backslashes = 0
buffer = []
def send_buffer() -> Iterator[Token]:
nonlocal backslashes
if not buffer:
return None
value = ''.join(buffer)
kind = TokenKind.TEXT
if not backslashes:
for variation, kind in [
(int, TokenKind.NUMBER),
(check_escaped_int, TokenKind.TEXT),
]:
try:
value = variation(value)
except ValueError:
continue
else:
break
yield Token(
kind, value, start=cursor - (len(buffer) + backslashes), end=cursor
)
buffer.clear()
backslashes = 0
def can_advance() -> bool:
return cursor < len(source)
while can_advance():
index = source[cursor]
if index in OPERATORS:
yield from send_buffer()
yield Token(OPERATORS[index], index, cursor, cursor + 1)
elif index == BACKSLASH and can_advance():
if source[cursor + 1] in SPECIAL_CHARS:
backslashes += 1
else:
buffer.append(index)
buffer.append(source[cursor + 1])
cursor += 1
else:
buffer.append(index)
cursor += 1
yield from send_buffer()
class PathAction(Enum):
KEY = auto()
INDEX = auto()
APPEND = auto()
# Pseudo action, used by the interpreter
SET = auto()
def to_string(self) -> str:
return self.name.lower()
class Path:
def __init__(
self,
kind: PathAction,
accessor: Optional[Union[str, int]] = None,
tokens: Optional[List[Token]] = None,
is_root: bool = False,
):
self.kind = kind
self.accessor = accessor
self.tokens = tokens or []
self.is_root = is_root
def reconstruct(self) -> str:
if self.kind is PathAction.KEY:
if self.is_root:
return str(self.accessor)
return OPEN_BRACKET + self.accessor + CLOSE_BRACKET
elif self.kind is PathAction.INDEX:
return OPEN_BRACKET + str(self.accessor) + CLOSE_BRACKET
elif self.kind is PathAction.APPEND:
return OPEN_BRACKET + CLOSE_BRACKET
else:
assert_cant_happen()
def parse(source: str) -> Iterator[Path]:
"""
start: root_path path*
root_path: (literal | index_path | append_path)
literal: TEXT | NUMBER
path:
key_path
| index_path
| append_path
key_path: LEFT_BRACKET TEXT RIGHT_BRACKET
index_path: LEFT_BRACKET NUMBER RIGHT_BRACKET
append_path: LEFT_BRACKET RIGHT_BRACKET
"""
tokens = list(tokenize(source))
cursor = 0
def can_advance():
return cursor < len(tokens)
def expect(*kinds):
nonlocal cursor
assert len(kinds) > 0
if can_advance():
token = tokens[cursor]
cursor += 1
if token.kind in kinds:
return token
elif tokens:
token = tokens[-1]._replace(
start=tokens[-1].end + 0, end=tokens[-1].end + 1
)
else:
token = None
if len(kinds) == 1:
suffix = kinds[0].to_name()
else:
suffix = ', '.join(kind.to_name() for kind in kinds[:-1])
suffix += ' or ' + kinds[-1].to_name()
message = f'Expecting {suffix}'
raise HTTPieSyntaxError(source, token, message)
def parse_root():
tokens = []
if not can_advance():
return Path(
PathAction.KEY,
EMPTY_STRING,
is_root=True
)
# (literal | index_path | append_path)?
token = expect(*LITERAL_TOKENS, TokenKind.LEFT_BRACKET)
tokens.append(token)
if token.kind in LITERAL_TOKENS:
action = PathAction.KEY
value = str(token.value)
elif token.kind is TokenKind.LEFT_BRACKET:
token = expect(TokenKind.NUMBER, TokenKind.RIGHT_BRACKET)
tokens.append(token)
if token.kind is TokenKind.NUMBER:
action = PathAction.INDEX
value = token.value
tokens.append(expect(TokenKind.RIGHT_BRACKET))
elif token.kind is TokenKind.RIGHT_BRACKET:
action = PathAction.APPEND
value = None
else:
assert_cant_happen()
else:
assert_cant_happen()
return Path(
action,
value,
tokens=tokens,
is_root=True
)
yield parse_root()
# path*
while can_advance():
path_tokens = []
path_tokens.append(expect(TokenKind.LEFT_BRACKET))
token = expect(
TokenKind.TEXT, TokenKind.NUMBER, TokenKind.RIGHT_BRACKET
)
path_tokens.append(token)
if token.kind is TokenKind.RIGHT_BRACKET:
path = Path(PathAction.APPEND, tokens=path_tokens)
elif token.kind is TokenKind.TEXT:
path = Path(PathAction.KEY, token.value, tokens=path_tokens)
path_tokens.append(expect(TokenKind.RIGHT_BRACKET))
elif token.kind is TokenKind.NUMBER:
path = Path(PathAction.INDEX, token.value, tokens=path_tokens)
path_tokens.append(expect(TokenKind.RIGHT_BRACKET))
else:
assert_cant_happen()
yield path
JSON_TYPE_MAPPING = {
dict: 'object',
list: 'array',
int: 'number',
float: 'number',
str: 'string',
}
def interpret(context: Any, key: str, value: Any) -> Any:
cursor = context
paths = list(parse(key))
paths.append(Path(PathAction.SET, value))
def type_check(index: int, path: Path, expected_type: Type[Any]) -> None:
if not isinstance(cursor, expected_type):
if path.tokens:
pseudo_token = Token(
None, None, path.tokens[0].start, path.tokens[-1].end
)
else:
pseudo_token = None
cursor_type = JSON_TYPE_MAPPING.get(
type(cursor), type(cursor).__name__
)
required_type = JSON_TYPE_MAPPING[expected_type]
message = f"Can't perform {path.kind.to_string()!r} based access on "
message += repr(
''.join(path.reconstruct() for path in paths[:index])
)
message += (
f' which has a type of {cursor_type!r} but this operation'
)
message += f' requires a type of {required_type!r}.'
raise HTTPieSyntaxError(
key, pseudo_token, message, message_kind='Type'
)
def object_for(kind: str) -> Any:
if kind is PathAction.KEY:
return {}
elif kind in {PathAction.INDEX, PathAction.APPEND}:
return []
else:
assert_cant_happen()
for index, (path, next_path) in enumerate(zip(paths, paths[1:])):
# If there is no context yet, set it.
if cursor is None:
context = cursor = object_for(path.kind)
if path.kind is PathAction.KEY:
type_check(index, path, dict)
if next_path.kind is PathAction.SET:
cursor[path.accessor] = next_path.accessor
break
cursor = cursor.setdefault(
path.accessor, object_for(next_path.kind)
)
elif path.kind is PathAction.INDEX:
type_check(index, path, list)
if path.accessor < 0:
raise HTTPieSyntaxError(
key,
path.tokens[1],
'Negative indexes are not supported.',
message_kind='Value',
)
cursor.extend([None] * (path.accessor - len(cursor) + 1))
if next_path.kind is PathAction.SET:
cursor[path.accessor] = next_path.accessor
break
if cursor[path.accessor] is None:
cursor[path.accessor] = object_for(next_path.kind)
cursor = cursor[path.accessor]
elif path.kind is PathAction.APPEND:
type_check(index, path, list)
if next_path.kind is PathAction.SET:
cursor.append(next_path.accessor)
break
cursor.append(object_for(next_path.kind))
cursor = cursor[-1]
else:
assert_cant_happen()
return context
def wrap_with_dict(context):
if context is None:
return {}
elif isinstance(context, list):
return {EMPTY_STRING: NestedJSONArray(context)}
else:
assert isinstance(context, dict)
return context
def interpret_nested_json(pairs):
context = None
for key, value in pairs:
context = interpret(context, key, value)
return wrap_with_dict(context)

View File

@ -1,20 +0,0 @@
"""
A library for parsing the HTTPie nested JSON key syntax and constructing the resulting objects.
<https://httpie.io/docs/cli/nested-json>
It has no dependencies.
"""
from .interpret import interpret_nested_json, unwrap_top_level_list_if_needed
from .errors import NestedJSONSyntaxError
from .tokens import EMPTY_STRING, NestedJSONArray
__all__ = [
'interpret_nested_json',
'unwrap_top_level_list_if_needed',
'EMPTY_STRING',
'NestedJSONArray',
'NestedJSONSyntaxError'
]

View File

@ -1,27 +0,0 @@
from typing import Optional
from .tokens import Token, HIGHLIGHTER
class NestedJSONSyntaxError(ValueError):
def __init__(
self,
source: str,
token: Optional[Token],
message: str,
message_kind: str = 'Syntax',
) -> None:
self.source = source
self.token = token
self.message = message
self.message_kind = message_kind
def __str__(self):
lines = [f'HTTPie {self.message_kind} Error: {self.message}']
if self.token is not None:
lines.append(self.source)
lines.append(
' ' * self.token.start
+ HIGHLIGHTER * (self.token.end - self.token.start)
)
return '\n'.join(lines)

View File

@ -1,129 +0,0 @@
from typing import Type, Union, Any, Iterable, Tuple
from .parse import parse, assert_cant_happen
from .errors import NestedJSONSyntaxError
from .tokens import EMPTY_STRING, TokenKind, Token, PathAction, Path, NestedJSONArray
__all__ = [
'interpret_nested_json',
'unwrap_top_level_list_if_needed',
]
JSONType = Type[Union[dict, list, int, float, str]]
JSON_TYPE_MAPPING = {
dict: 'object',
list: 'array',
int: 'number',
float: 'number',
str: 'string',
}
def interpret_nested_json(pairs: Iterable[Tuple[str, str]]) -> dict:
context = None
for key, value in pairs:
context = interpret(context, key, value)
return wrap_with_dict(context)
def interpret(context: Any, key: str, value: Any) -> Any:
cursor = context
paths = list(parse(key))
paths.append(Path(PathAction.SET, value))
# noinspection PyShadowingNames
def type_check(index: int, path: Path, expected_type: JSONType):
if not isinstance(cursor, expected_type):
if path.tokens:
pseudo_token = Token(
kind=TokenKind.PSEUDO,
value='',
start=path.tokens[0].start,
end=path.tokens[-1].end,
)
else:
pseudo_token = None
cursor_type = JSON_TYPE_MAPPING.get(type(cursor), type(cursor).__name__)
required_type = JSON_TYPE_MAPPING[expected_type]
message = f'Cannot perform {path.kind.to_string()!r} based access on '
message += repr(''.join(path.reconstruct() for path in paths[:index]))
message += f' which has a type of {cursor_type!r} but this operation'
message += f' requires a type of {required_type!r}.'
raise NestedJSONSyntaxError(
source=key,
token=pseudo_token,
message=message,
message_kind='Type',
)
def object_for(kind: PathAction) -> Any:
if kind is PathAction.KEY:
return {}
elif kind in {PathAction.INDEX, PathAction.APPEND}:
return []
else:
assert_cant_happen()
for index, (path, next_path) in enumerate(zip(paths, paths[1:])):
# If there is no context yet, set it.
if cursor is None:
context = cursor = object_for(path.kind)
if path.kind is PathAction.KEY:
type_check(index, path, dict)
if next_path.kind is PathAction.SET:
cursor[path.accessor] = next_path.accessor
break
cursor = cursor.setdefault(path.accessor, object_for(next_path.kind))
elif path.kind is PathAction.INDEX:
type_check(index, path, list)
if path.accessor < 0:
raise NestedJSONSyntaxError(
source=key,
token=path.tokens[1],
message='Negative indexes are not supported.',
message_kind='Value',
)
cursor.extend([None] * (path.accessor - len(cursor) + 1))
if next_path.kind is PathAction.SET:
cursor[path.accessor] = next_path.accessor
break
if cursor[path.accessor] is None:
cursor[path.accessor] = object_for(next_path.kind)
cursor = cursor[path.accessor]
elif path.kind is PathAction.APPEND:
type_check(index, path, list)
if next_path.kind is PathAction.SET:
cursor.append(next_path.accessor)
break
cursor.append(object_for(next_path.kind))
cursor = cursor[-1]
else:
assert_cant_happen()
return context
def wrap_with_dict(context):
if context is None:
return {}
elif isinstance(context, list):
return {
EMPTY_STRING: NestedJSONArray(context),
}
else:
assert isinstance(context, dict)
return context
def unwrap_top_level_list_if_needed(data: dict):
"""
Propagate the top-level list, if thats what we got.
"""
if len(data) == 1:
key, value = list(data.items())[0]
if isinstance(value, NestedJSONArray):
assert key == EMPTY_STRING
return value
return data

View File

@ -1,193 +0,0 @@
from typing import Iterator
from .errors import NestedJSONSyntaxError
from .tokens import (
EMPTY_STRING,
BACKSLASH,
TokenKind,
OPERATORS,
SPECIAL_CHARS,
LITERAL_TOKENS,
Token,
PathAction,
Path,
)
__all__ = [
'parse',
'assert_cant_happen',
]
def parse(source: str) -> Iterator[Path]:
"""
start: root_path path*
root_path: (literal | index_path | append_path)
literal: TEXT | NUMBER
path:
key_path
| index_path
| append_path
key_path: LEFT_BRACKET TEXT RIGHT_BRACKET
index_path: LEFT_BRACKET NUMBER RIGHT_BRACKET
append_path: LEFT_BRACKET RIGHT_BRACKET
"""
tokens = list(tokenize(source))
cursor = 0
def can_advance():
return cursor < len(tokens)
# noinspection PyShadowingNames
def expect(*kinds):
nonlocal cursor
assert kinds
if can_advance():
token = tokens[cursor]
cursor += 1
if token.kind in kinds:
return token
elif tokens:
token = tokens[-1]._replace(
start=tokens[-1].end + 0,
end=tokens[-1].end + 1,
)
else:
token = None
if len(kinds) == 1:
suffix = kinds[0].to_name()
else:
suffix = ', '.join(kind.to_name() for kind in kinds[:-1])
suffix += ' or ' + kinds[-1].to_name()
message = f'Expecting {suffix}'
raise NestedJSONSyntaxError(source, token, message)
# noinspection PyShadowingNames
def parse_root():
tokens = []
if not can_advance():
return Path(
kind=PathAction.KEY,
accessor=EMPTY_STRING,
is_root=True
)
# (literal | index_path | append_path)?
token = expect(*LITERAL_TOKENS, TokenKind.LEFT_BRACKET)
tokens.append(token)
if token.kind in LITERAL_TOKENS:
action = PathAction.KEY
value = str(token.value)
elif token.kind is TokenKind.LEFT_BRACKET:
token = expect(TokenKind.NUMBER, TokenKind.RIGHT_BRACKET)
tokens.append(token)
if token.kind is TokenKind.NUMBER:
action = PathAction.INDEX
value = token.value
tokens.append(expect(TokenKind.RIGHT_BRACKET))
elif token.kind is TokenKind.RIGHT_BRACKET:
action = PathAction.APPEND
value = None
else:
assert_cant_happen()
else:
assert_cant_happen()
# noinspection PyUnboundLocalVariable
return Path(
kind=action,
accessor=value,
tokens=tokens,
is_root=True
)
yield parse_root()
# path*
while can_advance():
path_tokens = [expect(TokenKind.LEFT_BRACKET)]
token = expect(TokenKind.TEXT, TokenKind.NUMBER, TokenKind.RIGHT_BRACKET)
path_tokens.append(token)
if token.kind is TokenKind.RIGHT_BRACKET:
path = Path(PathAction.APPEND, tokens=path_tokens)
elif token.kind is TokenKind.TEXT:
path = Path(PathAction.KEY, token.value, tokens=path_tokens)
path_tokens.append(expect(TokenKind.RIGHT_BRACKET))
elif token.kind is TokenKind.NUMBER:
path = Path(PathAction.INDEX, token.value, tokens=path_tokens)
path_tokens.append(expect(TokenKind.RIGHT_BRACKET))
else:
assert_cant_happen()
# noinspection PyUnboundLocalVariable
yield path
def tokenize(source: str) -> Iterator[Token]:
cursor = 0
backslashes = 0
buffer = []
def send_buffer() -> Iterator[Token]:
nonlocal backslashes
if not buffer:
return None
value = ''.join(buffer)
kind = TokenKind.TEXT
if not backslashes:
for variation, kind in [
(int, TokenKind.NUMBER),
(check_escaped_int, TokenKind.TEXT),
]:
try:
value = variation(value)
except ValueError:
continue
else:
break
yield Token(
kind=kind,
value=value,
start=cursor - (len(buffer) + backslashes),
end=cursor,
)
buffer.clear()
backslashes = 0
def can_advance() -> bool:
return cursor < len(source)
while can_advance():
index = source[cursor]
if index in OPERATORS:
yield from send_buffer()
yield Token(OPERATORS[index], index, cursor, cursor + 1)
elif index == BACKSLASH and can_advance():
if source[cursor + 1] in SPECIAL_CHARS:
backslashes += 1
else:
buffer.append(index)
buffer.append(source[cursor + 1])
cursor += 1
else:
buffer.append(index)
cursor += 1
yield from send_buffer()
def check_escaped_int(value: str) -> str:
if not value.startswith(BACKSLASH):
raise ValueError('Not an escaped int')
try:
int(value[1:])
except ValueError as exc:
raise ValueError('Not an escaped int') from exc
else:
return value[1:]
def assert_cant_happen():
raise ValueError('Unexpected value')

View File

@ -1,80 +0,0 @@
from enum import Enum, auto
from typing import NamedTuple, Union, Optional, List
EMPTY_STRING = ''
HIGHLIGHTER = '^'
OPEN_BRACKET = '['
CLOSE_BRACKET = ']'
BACKSLASH = '\\'
class TokenKind(Enum):
TEXT = auto()
NUMBER = auto()
LEFT_BRACKET = auto()
RIGHT_BRACKET = auto()
PSEUDO = auto() # Not a real token, use when representing location only.
def to_name(self) -> str:
for key, value in OPERATORS.items():
if value is self:
return repr(key)
else:
return 'a ' + self.name.lower()
OPERATORS = {
OPEN_BRACKET: TokenKind.LEFT_BRACKET,
CLOSE_BRACKET: TokenKind.RIGHT_BRACKET,
}
SPECIAL_CHARS = OPERATORS.keys() | {BACKSLASH}
LITERAL_TOKENS = [
TokenKind.TEXT,
TokenKind.NUMBER,
]
class Token(NamedTuple):
kind: TokenKind
value: Union[str, int]
start: int
end: int
class PathAction(Enum):
KEY = auto()
INDEX = auto()
APPEND = auto()
# Pseudo action, used by the interpreter
SET = auto()
def to_string(self) -> str:
return self.name.lower()
class Path:
def __init__(
self,
kind: PathAction,
accessor: Optional[Union[str, int]] = None,
tokens: Optional[List[Token]] = None,
is_root: bool = False,
):
self.kind = kind
self.accessor = accessor
self.tokens = tokens or []
self.is_root = is_root
def reconstruct(self) -> str:
if self.kind is PathAction.KEY:
if self.is_root:
return str(self.accessor)
return OPEN_BRACKET + self.accessor + CLOSE_BRACKET
elif self.kind is PathAction.INDEX:
return OPEN_BRACKET + str(self.accessor) + CLOSE_BRACKET
elif self.kind is PathAction.APPEND:
return OPEN_BRACKET + CLOSE_BRACKET
class NestedJSONArray(list):
"""Denotes a top-level JSON array."""

View File

@ -35,6 +35,17 @@ def drop_keys(
} }
def _get_first_line(source: str) -> str:
parts = []
for line in source.strip().splitlines():
line = line.strip()
parts.append(line)
if line.endswith("."):
break
return " ".join(parts)
PARSER_SPEC_VERSION = '0.0.1a0' PARSER_SPEC_VERSION = '0.0.1a0'
@ -44,8 +55,6 @@ class ParserSpec:
description: Optional[str] = None description: Optional[str] = None
epilog: Optional[str] = None epilog: Optional[str] = None
groups: List['Group'] = field(default_factory=list) groups: List['Group'] = field(default_factory=list)
man_page_hint: Optional[str] = None
source_file: Optional[str] = None
def finalize(self) -> 'ParserSpec': def finalize(self) -> 'ParserSpec':
if self.description: if self.description:
@ -239,11 +248,10 @@ def to_data(abstract_options: ParserSpec) -> Dict[str, Any]:
return {'version': PARSER_SPEC_VERSION, 'spec': abstract_options.serialize()} return {'version': PARSER_SPEC_VERSION, 'spec': abstract_options.serialize()}
def parser_to_parser_spec(parser: argparse.ArgumentParser, **kwargs) -> ParserSpec: def parser_to_parser_spec(parser: argparse.ArgumentParser) -> ParserSpec:
"""Take an existing argparse parser, and create a spec from it.""" """Take an existing argparse parser, and create a spec from it."""
return ParserSpec( return ParserSpec(
program=parser.prog, program=parser.prog,
description=parser.description, description=parser.description,
epilog=parser.epilog, epilog=parser.epilog
**kwargs
) )

View File

@ -18,7 +18,7 @@ from .dicts import (
) )
from .exceptions import ParseError from .exceptions import ParseError
from .nested_json import interpret_nested_json from .nested_json import interpret_nested_json
from ..utils import get_content_type, load_json_preserve_order_and_dupe_keys, split_iterable from ..utils import get_content_type, load_json_preserve_order_and_dupe_keys, split
class RequestItems: class RequestItems:
@ -78,28 +78,25 @@ class RequestItems:
instance.data, instance.data,
), ),
SEPARATOR_DATA_RAW_JSON: ( SEPARATOR_DATA_RAW_JSON: (
convert_json_value_to_form_if_needed( json_only(instance, process_data_raw_json_embed_arg),
in_json_mode=instance.is_json,
processor=process_data_raw_json_embed_arg
),
instance.data, instance.data,
), ),
SEPARATOR_DATA_EMBED_RAW_JSON_FILE: ( SEPARATOR_DATA_EMBED_RAW_JSON_FILE: (
convert_json_value_to_form_if_needed( json_only(instance, process_data_embed_raw_json_file_arg),
in_json_mode=instance.is_json,
processor=process_data_embed_raw_json_file_arg,
),
instance.data, instance.data,
), ),
} }
if instance.is_json: if instance.is_json:
json_item_args, request_item_args = split_iterable( json_item_args, request_item_args = split(
iterable=request_item_args, request_item_args,
key=lambda arg: arg.sep in SEPARATOR_GROUP_NESTED_JSON_ITEMS lambda arg: arg.sep in SEPARATOR_GROUP_NESTED_JSON_ITEMS
) )
if json_item_args: if json_item_args:
pairs = [(arg.key, rules[arg.sep][0](arg)) for arg in json_item_args] pairs = [
(arg.key, rules[arg.sep][0](arg))
for arg in json_item_args
]
processor_func, target_dict = rules[SEPARATOR_GROUP_NESTED_JSON_ITEMS] processor_func, target_dict = rules[SEPARATOR_GROUP_NESTED_JSON_ITEMS]
value = processor_func(pairs) value = processor_func(pairs)
target_dict.update(value) target_dict.update(value)
@ -162,30 +159,6 @@ def process_file_upload_arg(arg: KeyValueArg) -> Tuple[str, IO, str]:
) )
def convert_json_value_to_form_if_needed(in_json_mode: bool, processor: Callable[[KeyValueArg], JSONType]) -> Callable[[], str]:
"""
We allow primitive values to be passed to forms via JSON key/value syntax.
But complex values lead to an error because theres no clear way to serialize them.
"""
if in_json_mode:
return processor
@functools.wraps(processor)
def wrapper(*args, **kwargs) -> str:
try:
output = processor(*args, **kwargs)
except ParseError:
output = None
if isinstance(output, (str, int, float)):
return str(output)
else:
raise ParseError('Cannot use complex JSON value types with --form/--multipart.')
return wrapper
def process_data_item_arg(arg: KeyValueArg) -> str: def process_data_item_arg(arg: KeyValueArg) -> str:
return arg.value return arg.value
@ -194,6 +167,29 @@ def process_data_embed_file_contents_arg(arg: KeyValueArg) -> str:
return load_text_file(arg) return load_text_file(arg)
def json_only(items: RequestItems, func: Callable[[KeyValueArg], JSONType]) -> str:
if items.is_json:
return func
@functools.wraps(func)
def wrapper(*args, **kwargs) -> str:
try:
ret = func(*args, **kwargs)
except ParseError:
ret = None
# If it is a basic type, then allow it
if isinstance(ret, (str, int, float)):
return str(ret)
else:
raise ParseError(
'Can\'t use complex JSON value types with '
'--form/--multipart.'
)
return wrapper
def process_data_embed_raw_json_file_arg(arg: KeyValueArg) -> JSONType: def process_data_embed_raw_json_file_arg(arg: KeyValueArg) -> JSONType:
contents = load_text_file(arg) contents = load_text_file(arg)
value = load_json(arg, contents) value = load_json(arg, contents)

View File

@ -10,13 +10,11 @@ from urllib.parse import urlparse, urlunparse
import requests import requests
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import urllib3 import urllib3
from . import __version__ from . import __version__
from .adapters import HTTPieHTTPAdapter from .adapters import HTTPieHTTPAdapter
from .cli.constants import HTTP_OPTIONS
from .cli.dicts import HTTPHeadersDict
from .cli.nested_json import unwrap_top_level_list_if_needed
from .context import Environment from .context import Environment
from .cli.constants import EMPTY_STRING, HTTP_OPTIONS
from .cli.dicts import HTTPHeadersDict, NestedJSONArray
from .encoding import UTF8 from .encoding import UTF8
from .models import RequestsMessage from .models import RequestsMessage
from .plugins.registry import plugin_manager from .plugins.registry import plugin_manager
@ -307,13 +305,21 @@ def make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict:
def json_dict_to_request_body(data: Dict[str, Any]) -> str: def json_dict_to_request_body(data: Dict[str, Any]) -> str:
data = unwrap_top_level_list_if_needed(data) # Propagate the top-level list if there is only one
# item in the object, with an en empty key.
if len(data) == 1:
[(key, value)] = data.items()
if isinstance(value, NestedJSONArray):
assert key == EMPTY_STRING
data = value
if data: if data:
data = json.dumps(data) data = json.dumps(data)
else: else:
# We need to set data to an empty string to prevent requests # We need to set data to an empty string to prevent requests
# from assigning an empty list to `response.request.data`. # from assigning an empty list to `response.request.data`.
data = '' data = ''
return data return data

View File

@ -145,7 +145,7 @@ class Environment:
try: try:
config.load() config.load()
except ConfigFileError as e: except ConfigFileError as e:
self.log_error(e, level=LogLevel.WARNING) self.log_error(e, level='warning')
return config return config
@property @property
@ -174,7 +174,7 @@ class Environment:
stderr = self._orig_stderr stderr = self._orig_stderr
rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty()) rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty())
rich_console.print( rich_console.print(
f'\n{self.program_name}: {level.value}: {msg}\n\n', f'\n{self.program_name}: {level}: {msg}\n\n',
style=LOG_LEVEL_COLORS[level], style=LOG_LEVEL_COLORS[level],
markup=False, markup=False,
highlight=False, highlight=False,

View File

@ -11,7 +11,7 @@ from requests import __version__ as requests_version
from . import __version__ as httpie_version from . import __version__ as httpie_version
from .cli.constants import OUT_REQ_BODY from .cli.constants import OUT_REQ_BODY
from .cli.nested_json import NestedJSONSyntaxError from .cli.nested_json import HTTPieSyntaxError
from .client import collect_messages from .client import collect_messages
from .context import Environment, LogLevel from .context import Environment, LogLevel
from .downloads import Downloader from .downloads import Downloader
@ -78,7 +78,7 @@ def raw_main(
args=args, args=args,
env=env, env=env,
) )
except NestedJSONSyntaxError as exc: except HTTPieSyntaxError as exc:
env.stderr.write(str(exc) + "\n") env.stderr.write(str(exc) + "\n")
if include_traceback: if include_traceback:
raise raise

View File

@ -3,7 +3,7 @@ from contextlib import redirect_stderr, redirect_stdout
from typing import List from typing import List
from httpie.context import Environment from httpie.context import Environment
from httpie.internal.update_warnings import _fetch_updates, _get_suppress_context from httpie.internal.update_warnings import _fetch_updates
from httpie.status import ExitStatus from httpie.status import ExitStatus
STATUS_FILE = '.httpie-test-daemon-status' STATUS_FILE = '.httpie-test-daemon-status'
@ -44,7 +44,6 @@ def run_daemon_task(env: Environment, args: List[str]) -> ExitStatus:
assert options.daemon assert options.daemon
assert options.task_id in DAEMONIZED_TASKS assert options.task_id in DAEMONIZED_TASKS
with redirect_stdout(env.devnull), redirect_stderr(env.devnull): with redirect_stdout(env.devnull), redirect_stderr(env.devnull):
with _get_suppress_context(env): DAEMONIZED_TASKS[options.task_id](env)
DAEMONIZED_TASKS[options.task_id](env)
return ExitStatus.SUCCESS return ExitStatus.SUCCESS

View File

@ -1,6 +1,6 @@
""" """
This module provides an interface to spawn a detached task to be This module provides an interface to spawn a detached task to be
run with httpie.internal.daemon_runner on a separate process. It is runned with httpie.internal.daemon_runner on a separate process. It is
based on DVC's daemon system. based on DVC's daemon system.
https://github.com/iterative/dvc/blob/main/dvc/daemon.py https://github.com/iterative/dvc/blob/main/dvc/daemon.py
""" """
@ -11,7 +11,7 @@ import platform
import sys import sys
import httpie.__main__ import httpie.__main__
from contextlib import suppress from contextlib import suppress
from subprocess import Popen, DEVNULL from subprocess import Popen
from typing import Dict, List from typing import Dict, List
from httpie.compat import is_frozen, is_windows from httpie.compat import is_frozen, is_windows
@ -26,7 +26,7 @@ def _start_process(cmd: List[str], **kwargs) -> Popen:
if not is_frozen: if not is_frozen:
main_entrypoint = httpie.__main__.__file__ main_entrypoint = httpie.__main__.__file__
prefix += [main_entrypoint] prefix += [main_entrypoint]
return Popen(prefix + cmd, close_fds=True, shell=False, stdout=DEVNULL, stderr=DEVNULL, **kwargs) return Popen(prefix + cmd, close_fds=True, shell=False, **kwargs)
def _spawn_windows(cmd: List[str], process_context: ProcessContext) -> None: def _spawn_windows(cmd: List[str], process_context: ProcessContext) -> None:

View File

@ -52,6 +52,7 @@ def program():
try: try:
exit_status = main() exit_status = main()
except KeyboardInterrupt: except KeyboardInterrupt:
from httpie.status import ExitStatus
exit_status = ExitStatus.ERROR_CTRL_C exit_status = ExitStatus.ERROR_CTRL_C
return exit_status return exit_status

View File

@ -20,7 +20,6 @@ COMMANDS = {
{ {
'flags': ['-f', '--format'], 'flags': ['-f', '--format'],
'choices': ['json'], 'choices': ['json'],
'help': 'Format to export in.',
'default': 'json' 'default': 'json'
} }
], ],
@ -170,12 +169,5 @@ parser.add_argument(
''' '''
) )
man_page_hint = ''' options = parser_to_parser_spec(parser)
If you are looking for the man pages of http/https commands, try one of the following:
$ man http
$ man https
'''
options = parser_to_parser_spec(parser, man_page_hint=man_page_hint, source_file=__file__)
generate_subparsers(parser, parser, COMMANDS, options) generate_subparsers(parser, parser, COMMANDS, options)

View File

@ -43,6 +43,7 @@ def _discover_system_pip() -> List[str]:
def _run_pip_subprocess(pip_executable: List[str], args: List[str]) -> bytes: def _run_pip_subprocess(pip_executable: List[str], args: List[str]) -> bytes:
import subprocess
cmd = [*pip_executable, *args] cmd = [*pip_executable, *args]
try: try:

View File

@ -16,6 +16,7 @@ from .cli.constants import (
from .compat import cached_property from .compat import cached_property
from .utils import split_cookies, parse_content_type_header from .utils import split_cookies, parse_content_type_header
ELAPSED_TIME_LABEL = 'Elapsed time' ELAPSED_TIME_LABEL = 'Elapsed time'
@ -66,10 +67,27 @@ class HTTPResponse(HTTPMessage):
def iter_lines(self, chunk_size): def iter_lines(self, chunk_size):
return ((line, b'\n') for line in self._orig.iter_lines(chunk_size)) return ((line, b'\n') for line in self._orig.iter_lines(chunk_size))
# noinspection PyProtectedMember
@property @property
def headers(self): def headers(self):
try:
raw = self._orig.raw
if getattr(raw, '_original_response', None):
raw_version = raw._original_response.version
else:
raw_version = raw.version
except AttributeError:
# Assume HTTP/1.1
raw_version = 11
version = {
9: '0.9',
10: '1.0',
11: '1.1',
20: '2.0',
}[raw_version]
original = self._orig original = self._orig
status_line = f'HTTP/{self.version} {original.status_code} {original.reason}' status_line = f'HTTP/{version} {original.status_code} {original.reason}'
headers = [status_line] headers = [status_line]
headers.extend( headers.extend(
': '.join(header) ': '.join(header)
@ -99,32 +117,6 @@ class HTTPResponse(HTTPMessage):
for key, value in data.items() for key, value in data.items()
) )
@property
def version(self) -> str:
"""
Return the HTTP version used by the server, e.g. '1.1'.
Assume HTTP/1.1 if version is not available.
"""
mapping = {
9: '0.9',
10: '1.0',
11: '1.1',
20: '2.0',
}
fallback = 11
version = None
try:
raw = self._orig.raw
if getattr(raw, '_original_response', None):
version = raw._original_response.version
else:
version = raw.version
except AttributeError:
pass
return mapping[version or fallback]
class HTTPRequest(HTTPMessage): class HTTPRequest(HTTPMessage):
"""A :class:`requests.models.Request` wrapper.""" """A :class:`requests.models.Request` wrapper."""

View File

@ -7,13 +7,6 @@ from httpie.context import Environment
MAN_COMMAND = 'man' MAN_COMMAND = 'man'
NO_MAN_PAGES = os.getenv('HTTPIE_NO_MAN_PAGES', False) NO_MAN_PAGES = os.getenv('HTTPIE_NO_MAN_PAGES', False)
# On some systems, HTTP(n) might exist but we are only
# interested in HTTP(1).
#
# For more information on man page sections: https://unix.stackexchange.com/a/138643
MAN_PAGE_SECTION = '1'
def is_available(program: str) -> bool: def is_available(program: str) -> bool:
"""Check whether HTTPie's man pages are available in this system.""" """Check whether HTTPie's man pages are available in this system."""
@ -21,27 +14,18 @@ def is_available(program: str) -> bool:
if NO_MAN_PAGES or os.system == 'nt': if NO_MAN_PAGES or os.system == 'nt':
return False return False
try: process = subprocess.run(
process = subprocess.run( [MAN_COMMAND, program],
[MAN_COMMAND, MAN_PAGE_SECTION, program], shell=False,
shell=False, stdout=subprocess.DEVNULL,
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
stderr=subprocess.DEVNULL )
) return process.returncode == 0
except Exception:
# There might be some errors outside of the process, e.g
# a permission error to execute something that is not an
# executable.
return False
else:
return process.returncode == 0
def display_for(env: Environment, program: str) -> None: def display_for(env: Environment, program: str) -> None:
"""Display the man page for the given command (http/https).""" """Display the man page for the given command (http/https)."""
subprocess.run( subprocess.run(
[MAN_COMMAND, MAN_PAGE_SECTION, program], [MAN_COMMAND, program], stdout=env.stdout, stderr=env.stderr
stdout=env.stdout,
stderr=env.stderr
) )

View File

@ -1,6 +1,5 @@
from dataclasses import dataclass, field
from enum import Enum, auto from enum import Enum, auto
from typing import Optional, List from typing import Optional
PYGMENTS_BRIGHT_BLACK = 'ansibrightblack' PYGMENTS_BRIGHT_BLACK = 'ansibrightblack'
@ -35,21 +34,7 @@ class ColorString(str):
E.g: PieColor.BLUE | BOLD | ITALIC E.g: PieColor.BLUE | BOLD | ITALIC
""" """
if isinstance(other, str): return ColorString(self + ' ' + other)
# In case of PieColor.BLUE | SOMETHING
# we just create a new string.
return ColorString(self + ' ' + other)
elif isinstance(other, GenericColor):
# If we see a GenericColor, then we'll wrap it
# in with the desired property in a different class.
return _StyledGenericColor(other, styles=self.split())
elif isinstance(other, _StyledGenericColor):
# And if it is already wrapped, we'll just extend the
# list of properties.
other.styles.extend(self.split())
return other
else:
return NotImplemented
class PieColor(ColorString, Enum): class PieColor(ColorString, Enum):
@ -101,12 +86,6 @@ class GenericColor(Enum):
return exposed_color return exposed_color
@dataclass
class _StyledGenericColor:
color: 'GenericColor'
styles: List[str] = field(default_factory=list)
# noinspection PyDictCreation # noinspection PyDictCreation
COLOR_PALETTE = { COLOR_PALETTE = {
# Copy the brand palette # Copy the brand palette

View File

@ -26,7 +26,6 @@ STYLE_BOLD = 'bold'
MAX_CHOICE_CHARS = 80 MAX_CHOICE_CHARS = 80
LEFT_PADDING_2 = (0, 0, 0, 2) LEFT_PADDING_2 = (0, 0, 0, 2)
LEFT_PADDING_3 = (0, 0, 0, 3)
LEFT_PADDING_4 = (0, 0, 0, 4) LEFT_PADDING_4 = (0, 0, 0, 4)
LEFT_PADDING_5 = (0, 0, 0, 4) LEFT_PADDING_5 = (0, 0, 0, 4)
@ -34,12 +33,6 @@ LEFT_INDENT_2 = (1, 0, 0, 2)
LEFT_INDENT_3 = (1, 0, 0, 3) LEFT_INDENT_3 = (1, 0, 0, 3)
LEFT_INDENT_BOTTOM_3 = (0, 0, 1, 3) LEFT_INDENT_BOTTOM_3 = (0, 0, 1, 3)
MORE_INFO_COMMANDS = """
To learn more, you can try:
-> running 'http --manual'
-> visiting our full documentation at https://httpie.io/docs/cli
"""
class OptionsHighlighter(RegexHighlighter): class OptionsHighlighter(RegexHighlighter):
highlights = [ highlights = [
@ -220,10 +213,6 @@ def to_help_message(
Text('More Information', style=STYLE_SWITCH), Text('More Information', style=STYLE_SWITCH),
LEFT_INDENT_2, LEFT_INDENT_2,
) )
yield Padding(
MORE_INFO_COMMANDS.rstrip('\n'),
LEFT_PADDING_3
)
yield Padding( yield Padding(
spec.epilog.rstrip('\n'), spec.epilog.rstrip('\n'),
LEFT_INDENT_BOTTOM_3, LEFT_INDENT_BOTTOM_3,

View File

@ -4,22 +4,20 @@ from typing import TYPE_CHECKING, Any, Optional
if TYPE_CHECKING: if TYPE_CHECKING:
from rich.theme import Theme from rich.theme import Theme
from httpie.output.ui.palette import GenericColor, PieStyle, Styles, ColorString, _StyledGenericColor # noqa from httpie.output.ui.palette import GenericColor, PieStyle, Styles # noqa
RICH_BOLD = ColorString('bold')
# Rich-specific color code declarations # Rich-specific color code declarations
# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py> # <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>
CUSTOM_STYLES = { CUSTOM_STYLES = {
'progress.description': RICH_BOLD | GenericColor.WHITE, 'progress.description': GenericColor.WHITE,
'progress.data.speed': RICH_BOLD | GenericColor.GREEN, 'progress.data.speed': GenericColor.GREEN,
'progress.percentage': RICH_BOLD | GenericColor.AQUA, 'progress.percentage': GenericColor.AQUA,
'progress.download': RICH_BOLD | GenericColor.AQUA, 'progress.download': GenericColor.AQUA,
'progress.remaining': RICH_BOLD | GenericColor.ORANGE, 'progress.remaining': GenericColor.ORANGE,
'bar.complete': RICH_BOLD | GenericColor.PURPLE, 'bar.complete': GenericColor.PURPLE,
'bar.finished': RICH_BOLD | GenericColor.GREEN, 'bar.finished': GenericColor.GREEN,
'bar.pulse': RICH_BOLD | GenericColor.PURPLE, 'bar.pulse': GenericColor.PURPLE,
'option': RICH_BOLD | GenericColor.PINK, 'option': GenericColor.PINK,
} }
@ -42,7 +40,7 @@ class _GenericColorCaster(dict):
return super().get(self._translate(key)) return super().get(self._translate(key))
def _make_rich_color_theme(style_name: Optional[str] = None) -> 'Theme': def _make_rich_color_theme(style_name: Optional[str]) -> 'Theme':
from rich.style import Style from rich.style import Style
from rich.theme import Theme from rich.theme import Theme
@ -57,15 +55,8 @@ def _make_rich_color_theme(style_name: Optional[str] = None) -> 'Theme':
for color, color_set in ChainMap( for color, color_set in ChainMap(
GenericColor.__members__, CUSTOM_STYLES GenericColor.__members__, CUSTOM_STYLES
).items(): ).items():
if isinstance(color_set, _StyledGenericColor):
properties = dict.fromkeys(color_set.styles, True)
color_set = color_set.color
else:
properties = {}
theme.styles[color.lower()] = Style( theme.styles[color.lower()] = Style(
color=color_set.apply_style(style, style_name=style_name), color=color_set.apply_style(style, style_name=style_name)
**properties,
) )
# E.g translate GenericColor.BLUE into blue on key access # E.g translate GenericColor.BLUE into blue on key access

View File

@ -6,15 +6,13 @@ from contextlib import contextmanager
from rich.console import Console, RenderableType from rich.console import Console, RenderableType
from rich.highlighter import Highlighter from rich.highlighter import Highlighter
from httpie.output.ui.rich_palette import _make_rich_color_theme
def render_as_string(renderable: RenderableType) -> str: def render_as_string(renderable: RenderableType) -> str:
"""Render any `rich` object in a fake console and """Render any `rich` object in a fake console and
return a *style-less* version of it as a string.""" return a *style-less* version of it as a string."""
with open(os.devnull, 'w') as null_stream: with open(os.devnull, 'w') as null_stream:
fake_console = Console(file=null_stream, record=True, theme=_make_rich_color_theme()) fake_console = Console(file=null_stream, record=True)
fake_console.print(renderable) fake_console.print(renderable)
return fake_console.export_text() return fake_console.export_text()
@ -24,7 +22,7 @@ def enable_highlighter(
console: Console, console: Console,
highlighter: Highlighter, highlighter: Highlighter,
) -> Iterator[Console]: ) -> Iterator[Console]:
"""Enable a highlighter temporarily.""" """Enable a higlighter temporarily."""
original_highlighter = console.highlighter original_highlighter = console.highlighter
try: try:

View File

@ -17,7 +17,6 @@ from .processing import Conversion, Formatting
from .streams import ( from .streams import (
BaseStream, BufferedPrettyStream, EncodedStream, PrettyStream, RawStream, BaseStream, BufferedPrettyStream, EncodedStream, PrettyStream, RawStream,
) )
from ..utils import parse_content_type_header
MESSAGE_SEPARATOR = '\n\n' MESSAGE_SEPARATOR = '\n\n'
@ -164,10 +163,7 @@ def get_stream_type_and_kwargs(
if not is_stream and message_type is HTTPResponse: if not is_stream and message_type is HTTPResponse:
# If this is a response, then check the headers for determining # If this is a response, then check the headers for determining
# auto-streaming. # auto-streaming.
raw_content_type_header = headers.get('Content-Type', None) is_stream = headers.get('Content-Type') == 'text/event-stream'
if raw_content_type_header:
content_type_header, _ = parse_content_type_header(raw_content_type_header)
is_stream = (content_type_header == 'text/event-stream')
if not env.stdout_isatty and not prettify_groups: if not env.stdout_isatty and not prettify_groups:
stream_class = RawStream stream_class = RawStream

View File

@ -4,11 +4,12 @@ from typing import NamedTuple, Optional
from httpie.adapters import HTTPAdapter from httpie.adapters import HTTPAdapter
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
from urllib3.util.ssl_ import ( from urllib3.util.ssl_ import (
create_urllib3_context, DEFAULT_CIPHERS, create_urllib3_context,
resolve_ssl_version, resolve_ssl_version,
) )
DEFAULT_SSL_CIPHERS = DEFAULT_CIPHERS
SSL_VERSION_ARG_MAPPING = { SSL_VERSION_ARG_MAPPING = {
'ssl2.3': 'PROTOCOL_SSLv23', 'ssl2.3': 'PROTOCOL_SSLv23',
'ssl3': 'PROTOCOL_SSLv3', 'ssl3': 'PROTOCOL_SSLv3',
@ -80,10 +81,6 @@ class HTTPieHTTPSAdapter(HTTPAdapter):
cert_reqs=ssl.CERT_REQUIRED if verify else ssl.CERT_NONE cert_reqs=ssl.CERT_REQUIRED if verify else ssl.CERT_NONE
) )
@classmethod
def get_default_ciphers_names(cls):
return [cipher['name'] for cipher in cls._create_ssl_context(verify=False).get_ciphers()]
def _is_key_file_encrypted(key_file): def _is_key_file_encrypted(key_file):
"""Detects if a key file is encrypted or not. """Detects if a key file is encrypted or not.
@ -97,9 +94,3 @@ def _is_key_file_encrypted(key_file):
return True return True
return False return False
# We used to import the default set of TLS ciphers from urllib3, but they removed it.
# Instead, now urllib3 uses the list of ciphers configured by the system.
# <https://github.com/httpie/httpie/pull/1501>
DEFAULT_SSL_CIPHERS_STRING = ':'.join(HTTPieHTTPSAdapter.get_default_ciphers_names())

View File

@ -245,7 +245,7 @@ def get_site_paths(path: Path) -> Iterable[Path]:
yield as_site(path) yield as_site(path)
def split_iterable(iterable: Iterable[T], key: Callable[[T], bool]) -> Tuple[List[T], List[T]]: def split(iterable: Iterable[T], key: Callable[[T], bool]) -> Tuple[List[T], List[T]]:
left, right = [], [] left, right = [], []
for item in iterable: for item in iterable:
if key(item): if key(item):
@ -277,7 +277,7 @@ def open_with_lockfile(file: Path, *args, **kwargs) -> Generator[IO[Any], None,
target_file = Path(tempfile.gettempdir()) / file_id target_file = Path(tempfile.gettempdir()) / file_id
# Have an atomic-like touch here, so we'll tighten the possibility of # Have an atomic-like touch here, so we'll tighten the possibility of
# a race occurring between multiple processes accessing the same file. # a race occuring between multiple processes accessing the same file.
try: try:
target_file.touch(exist_ok=False) target_file.touch(exist_ok=False)
except FileExistsError as exc: except FileExistsError as exc:

View File

@ -117,6 +117,5 @@ setup(
data_files=[ data_files=[
('share/man/man1', ['extras/man/http.1']), ('share/man/man1', ['extras/man/http.1']),
('share/man/man1', ['extras/man/https.1']), ('share/man/man1', ['extras/man/https.1']),
('share/man/man1', ['extras/man/httpie.1']),
] ]
) )

View File

@ -5,7 +5,7 @@ import responses
from httpie.cli.constants import PRETTY_MAP from httpie.cli.constants import PRETTY_MAP
from httpie.cli.exceptions import ParseError from httpie.cli.exceptions import ParseError
from httpie.cli.nested_json import NestedJSONSyntaxError from httpie.cli.nested_json import HTTPieSyntaxError
from httpie.output.formatters.colors import ColorFormatter from httpie.output.formatters.colors import ColorFormatter
from httpie.utils import JsonDictPreservingDuplicateKeys from httpie.utils import JsonDictPreservingDuplicateKeys
@ -157,7 +157,7 @@ def test_complex_json_arguments_with_non_json(httpbin, request_type, value):
f'option:={json.dumps(value)}', f'option:={json.dumps(value)}',
) )
cm.match('Cannot use complex JSON value types') cm.match("Can't use complex JSON value types")
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -508,23 +508,23 @@ def test_nested_json_syntax(input_json, expected_json, httpbin):
), ),
( (
['foo=1', 'foo[key]:=2'], ['foo=1', 'foo[key]:=2'],
"HTTPie Type Error: Cannot perform 'key' based access on 'foo' which has a type of 'string' but this operation requires a type of 'object'.\nfoo[key]\n ^^^^^", "HTTPie Type Error: Can't perform 'key' based access on 'foo' which has a type of 'string' but this operation requires a type of 'object'.\nfoo[key]\n ^^^^^",
), ),
( (
['foo=1', 'foo[0]:=2'], ['foo=1', 'foo[0]:=2'],
"HTTPie Type Error: Cannot perform 'index' based access on 'foo' which has a type of 'string' but this operation requires a type of 'array'.\nfoo[0]\n ^^^", "HTTPie Type Error: Can't perform 'index' based access on 'foo' which has a type of 'string' but this operation requires a type of 'array'.\nfoo[0]\n ^^^",
), ),
( (
['foo=1', 'foo[]:=2'], ['foo=1', 'foo[]:=2'],
"HTTPie Type Error: Cannot perform 'append' based access on 'foo' which has a type of 'string' but this operation requires a type of 'array'.\nfoo[]\n ^^", "HTTPie Type Error: Can't perform 'append' based access on 'foo' which has a type of 'string' but this operation requires a type of 'array'.\nfoo[]\n ^^",
), ),
( (
['data[key]=value', 'data[key 2]=value 2', 'data[0]=value'], ['data[key]=value', 'data[key 2]=value 2', 'data[0]=value'],
"HTTPie Type Error: Cannot perform 'index' based access on 'data' which has a type of 'object' but this operation requires a type of 'array'.\ndata[0]\n ^^^", "HTTPie Type Error: Can't perform 'index' based access on 'data' which has a type of 'object' but this operation requires a type of 'array'.\ndata[0]\n ^^^",
), ),
( (
['data[key]=value', 'data[key 2]=value 2', 'data[]=value'], ['data[key]=value', 'data[key 2]=value 2', 'data[]=value'],
"HTTPie Type Error: Cannot perform 'append' based access on 'data' which has a type of 'object' but this operation requires a type of 'array'.\ndata[]\n ^^", "HTTPie Type Error: Can't perform 'append' based access on 'data' which has a type of 'object' but this operation requires a type of 'array'.\ndata[]\n ^^",
), ),
( (
[ [
@ -532,7 +532,7 @@ def test_nested_json_syntax(input_json, expected_json, httpbin):
'foo[bar][baz][5][]:=4', 'foo[bar][baz][5][]:=4',
'foo[bar][baz][key][]:=5', 'foo[bar][baz][key][]:=5',
], ],
"HTTPie Type Error: Cannot perform 'key' based access on 'foo[bar][baz]' which has a type of 'array' but this operation requires a type of 'object'.\nfoo[bar][baz][key][]\n ^^^^^", "HTTPie Type Error: Can't perform 'key' based access on 'foo[bar][baz]' which has a type of 'array' but this operation requires a type of 'object'.\nfoo[bar][baz][key][]\n ^^^^^",
), ),
( (
['foo[-10]:=[1,2]'], ['foo[-10]:=[1,2]'],
@ -540,32 +540,32 @@ def test_nested_json_syntax(input_json, expected_json, httpbin):
), ),
( (
['foo[0]:=1', 'foo[]:=2', 'foo[\\2]:=3'], ['foo[0]:=1', 'foo[]:=2', 'foo[\\2]:=3'],
"HTTPie Type Error: Cannot perform 'key' based access on 'foo' which has a type of 'array' but this operation requires a type of 'object'.\nfoo[\\2]\n ^^^^", "HTTPie Type Error: Can't perform 'key' based access on 'foo' which has a type of 'array' but this operation requires a type of 'object'.\nfoo[\\2]\n ^^^^",
), ),
( (
['foo[\\1]:=2', 'foo[5]:=3'], ['foo[\\1]:=2', 'foo[5]:=3'],
"HTTPie Type Error: Cannot perform 'index' based access on 'foo' which has a type of 'object' but this operation requires a type of 'array'.\nfoo[5]\n ^^^", "HTTPie Type Error: Can't perform 'index' based access on 'foo' which has a type of 'object' but this operation requires a type of 'array'.\nfoo[5]\n ^^^",
), ),
( (
['x=y', '[]:=2'], ['x=y', '[]:=2'],
"HTTPie Type Error: Cannot perform 'append' based access on '' which has a type of 'object' but this operation requires a type of 'array'.", "HTTPie Type Error: Can't perform 'append' based access on '' which has a type of 'object' but this operation requires a type of 'array'.",
), ),
( (
['[]:=2', 'x=y'], ['[]:=2', 'x=y'],
"HTTPie Type Error: Cannot perform 'key' based access on '' which has a type of 'array' but this operation requires a type of 'object'.", "HTTPie Type Error: Can't perform 'key' based access on '' which has a type of 'array' but this operation requires a type of 'object'.",
), ),
( (
[':=[1,2,3]', '[]:=4'], [':=[1,2,3]', '[]:=4'],
"HTTPie Type Error: Cannot perform 'append' based access on '' which has a type of 'object' but this operation requires a type of 'array'.", "HTTPie Type Error: Can't perform 'append' based access on '' which has a type of 'object' but this operation requires a type of 'array'.",
), ),
( (
['[]:=4', ':=[1,2,3]'], ['[]:=4', ':=[1,2,3]'],
"HTTPie Type Error: Cannot perform 'key' based access on '' which has a type of 'array' but this operation requires a type of 'object'.", "HTTPie Type Error: Can't perform 'key' based access on '' which has a type of 'array' but this operation requires a type of 'object'.",
), ),
], ],
) )
def test_nested_json_errors(input_json, expected_error, httpbin): def test_nested_json_errors(input_json, expected_error, httpbin):
with pytest.raises(NestedJSONSyntaxError) as exc: with pytest.raises(HTTPieSyntaxError) as exc:
http(httpbin + '/post', *input_json) http(httpbin + '/post', *input_json)
exc_lines = str(exc.value).splitlines() exc_lines = str(exc.value).splitlines()

View File

@ -117,8 +117,6 @@ def test_plugins_double_uninstall(httpie_plugins, httpie_plugins_success, dummy_
) )
# TODO: Make this work on CI (stopped working at some point)
@pytest.mark.skip(reason='Doesnt work in CI')
@pytest.mark.requires_installation @pytest.mark.requires_installation
def test_plugins_upgrade(httpie_plugins, httpie_plugins_success, dummy_plugin): def test_plugins_upgrade(httpie_plugins, httpie_plugins_success, dummy_plugin):
httpie_plugins_success("install", dummy_plugin.path) httpie_plugins_success("install", dummy_plugin.path)

View File

@ -446,7 +446,7 @@ class TestExpiredCookies(CookieTestBase):
class TestCookieStorage(CookieTestBase): class TestCookieStorage(CookieTestBase):
@pytest.mark.parametrize( @pytest.mark.parametrize(
['specified_cookie_header', 'new_cookies_dict', 'expected_effective_cookie_header'], 'new_cookies, new_cookies_dict, expected',
[( [(
'new=bar', 'new=bar',
{'new': 'bar'}, {'new': 'bar'},
@ -463,9 +463,9 @@ class TestCookieStorage(CookieTestBase):
'chocolate=milk; cookie1=foo; cookie2=foo; new=bar' 'chocolate=milk; cookie1=foo; cookie2=foo; new=bar'
), ),
( (
'new=bar; chocolate=milk', 'new=bar;; chocolate=milk;;;',
{'new': 'bar', 'chocolate': 'milk'}, {'new': 'bar', 'chocolate': 'milk'},
'cookie1=foo; cookie2=foo; new=bar; chocolate=milk' 'cookie1=foo; cookie2=foo; new=bar'
), ),
( (
'new=bar; chocolate=milk;;;', 'new=bar; chocolate=milk;;;',
@ -474,35 +474,20 @@ class TestCookieStorage(CookieTestBase):
) )
] ]
) )
def test_existing_and_new_cookies_sent_in_request( def test_existing_and_new_cookies_sent_in_request(self, new_cookies, new_cookies_dict, expected, httpbin):
self,
specified_cookie_header,
new_cookies_dict,
expected_effective_cookie_header,
httpbin,
):
r = http( r = http(
'--session', str(self.session_path), '--session', str(self.session_path),
'--print=H', '--print=H',
httpbin.url, httpbin.url,
'Cookie:' + specified_cookie_header, 'Cookie:' + new_cookies,
) )
parsed_request_headers = { # noqa # Note: cookies in response are in alphabetical order
name: value for name, value in [ assert f'Cookie: {expected}' in r
line.split(': ', 1)
for line in r.splitlines()
if line and ':' in line
]
}
# Note: cookies in the request are in an undefined order.
expected_request_cookie_set = set(expected_effective_cookie_header.split('; '))
actual_request_cookie_set = set(parsed_request_headers['Cookie'].split('; '))
assert actual_request_cookie_set == expected_request_cookie_set
updated_session = json.loads(self.session_path.read_text(encoding=UTF8)) updated_session = json.loads(self.session_path.read_text(encoding=UTF8))
assert 'Cookie' not in updated_session['headers']
for name, value in new_cookies_dict.items(): for name, value in new_cookies_dict.items():
assert updated_session['cookies'][name]['value'] == value assert name, value in updated_session['cookies']
assert 'Cookie' not in updated_session['headers']
@pytest.mark.parametrize( @pytest.mark.parametrize(
'cli_cookie, set_cookie, expected', 'cli_cookie, set_cookie, expected',

View File

@ -7,7 +7,7 @@ import urllib3
from unittest import mock from unittest import mock
from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS_STRING from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS
from httpie.status import ExitStatus from httpie.status import ExitStatus
from .utils import HTTP_OK, TESTS_ROOT, IS_PYOPENSSL, http from .utils import HTTP_OK, TESTS_ROOT, IS_PYOPENSSL, http
@ -146,7 +146,7 @@ def test_ciphers(httpbin_secure):
r = http( r = http(
httpbin_secure.url + '/get', httpbin_secure.url + '/get',
'--ciphers', '--ciphers',
DEFAULT_SSL_CIPHERS_STRING, DEFAULT_SSL_CIPHERS,
) )
assert HTTP_OK in r assert HTTP_OK in r

View File

@ -124,10 +124,6 @@ def test_redirected_stream(httpbin):
['Accept:text/event-stream'], ['Accept:text/event-stream'],
3 3
), ),
(
['Accept:text/event-stream; charset=utf-8'],
3
),
( (
['Accept:text/plain'], ['Accept:text/plain'],
1 1