mirror of
https://github.com/httpie/cli.git
synced 2025-03-13 06:18:33 +01:00
Merge branch 'httpie:master' into master
This commit is contained in:
commit
24d16251ec
2
.github/workflows/autogenerated-files.yml
vendored
2
.github/workflows/autogenerated-files.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-python@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
|
2
.github/workflows/benchmark.yml
vendored
2
.github/workflows/benchmark.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
|
||||
|
2
.github/workflows/code-style.yml
vendored
2
.github/workflows/code-style.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
- run: make venv
|
||||
|
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- run: make install
|
||||
|
17
.github/workflows/release-choco.yml
vendored
17
.github/workflows/release-choco.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
name: Release the Chocolatey
|
||||
runs-on: windows-2019
|
||||
env:
|
||||
package-dir: ./httpie/docs/packaging/windows-chocolatey
|
||||
package-dir: docs\packaging\windows-chocolatey
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@ -30,9 +30,21 @@ jobs:
|
||||
run: choco info httpie -s .
|
||||
working-directory: ${{ env.package-dir }}
|
||||
|
||||
- name: Check the Installation
|
||||
- name: Local installation
|
||||
run: |
|
||||
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
|
||||
https --version
|
||||
httpie --version
|
||||
@ -46,3 +58,4 @@ jobs:
|
||||
run: |
|
||||
choco apikey --key $CHOCO_API_KEY --source https://push.chocolatey.org/
|
||||
choco push httpie*.nupkg --source https://push.chocolatey.org/
|
||||
working-directory: ${{ env.package-dir }}
|
||||
|
23
.github/workflows/release-linux-standalone.yml
vendored
23
.github/workflows/release-linux-standalone.yml
vendored
@ -7,6 +7,9 @@ on:
|
||||
description: "The branch, tag or SHA to release from"
|
||||
required: true
|
||||
default: "master"
|
||||
tag_name:
|
||||
description: "Which release to upload the artifacts to (e.g., 3.0)"
|
||||
required: true
|
||||
|
||||
release:
|
||||
types: [released, prereleased]
|
||||
@ -21,7 +24,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
|
||||
- uses: actions/setup-python@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
@ -45,24 +48,30 @@ jobs:
|
||||
name: httpie.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
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: extras/packaging/linux/artifacts/dist/httpie-${{ github.event.release.tag_name }}.deb
|
||||
asset_name: httpie-${{ github.event.release.tag_name }}.deb
|
||||
upload_url: ${{ steps.release_id.outputs.UPLOAD_URL }}
|
||||
asset_path: extras/packaging/linux/artifacts/dist/httpie_${{ github.event.inputs.tag_name }}_amd64.deb
|
||||
asset_name: httpie-${{ github.event.inputs.tag_name }}.deb
|
||||
asset_content_type: binary/octet-stream
|
||||
|
||||
- name: Publish Single Executable
|
||||
if: github.event_name == 'release'
|
||||
uses: actions/upload-release-asset@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
upload_url: ${{ steps.release_id.outputs.UPLOAD_URL }}
|
||||
asset_path: extras/packaging/linux/artifacts/dist/http
|
||||
asset_name: http
|
||||
asset_content_type: binary/octet-stream
|
||||
|
7
.github/workflows/release-pypi.yml
vendored
7
.github/workflows/release-pypi.yml
vendored
@ -17,15 +17,12 @@ jobs:
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
|
||||
- uses: actions/setup-python@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install pypa/build
|
||||
run: python -m pip install build
|
||||
|
||||
- name: Build a binary wheel and a source tarball
|
||||
run: python -m build --sdist --wheel --outdir dist/
|
||||
run: make install && make build
|
||||
|
||||
- name: Release on PyPI
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Windows setup
|
||||
|
@ -7,11 +7,6 @@ actions:
|
||||
# Use this when the latest spec is not up-to-date.
|
||||
# post-upstream-clone: "cp docs/packaging/linux-fedora/httpie.spec.txt httpie.spec"
|
||||
jobs:
|
||||
- job: copr_build
|
||||
trigger: pull_request
|
||||
metadata:
|
||||
targets:
|
||||
- fedora-all
|
||||
- job: propose_downstream
|
||||
trigger: release
|
||||
metadata:
|
||||
|
18
CHANGELOG.md
18
CHANGELOG.md
@ -3,14 +3,24 @@
|
||||
This document records all notable changes to [HTTPie](https://httpie.io).
|
||||
This project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [3.1.1.dev0](https://github.com/httpie/httpie/compare/3.1.0...HEAD) (Unreleased)
|
||||
## [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))
|
||||
- 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))
|
||||
- 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 blocking of warning thread on some use cases. ([#1349](https://github.com/httpie/httpie/issues/1349))
|
||||
- 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))
|
||||
- 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)
|
||||
|
||||
|
@ -59,8 +59,10 @@ $ git checkout -b my_topical_branch
|
||||
|
||||
#### Setup
|
||||
|
||||
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started. Just run
|
||||
the following command, which:
|
||||
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started.
|
||||
You can run `$ make` to see all the available tasks.
|
||||
|
||||
To get started, run the command below, which:
|
||||
|
||||
- Creates an isolated Python virtual environment inside `./venv`
|
||||
(via the standard library [venv](https://docs.python.org/3/library/venv.html) tool);
|
||||
@ -70,7 +72,7 @@ the following command, which:
|
||||
- and runs tests (It is the same as running `make install test`).
|
||||
|
||||
```bash
|
||||
$ make
|
||||
$ make all
|
||||
```
|
||||
|
||||
#### Python virtual environment
|
||||
@ -152,7 +154,7 @@ with the master branch of your repository (or a fresh checkout of HTTPie master,
|
||||
`--fresh`) and report the results back.
|
||||
|
||||
```bash
|
||||
$ python extras/benchmarks/run.py
|
||||
$ python extras/profiling/run.py
|
||||
```
|
||||
|
||||
The benchmarks can also be run on the CI. Since it is a long process, it requires manual
|
||||
|
33
Makefile
33
Makefile
@ -22,6 +22,26 @@ VENV_PYTHON=$(VENV_BIN)/python
|
||||
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 | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'
|
||||
@echo
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Installation
|
||||
###############################################################################
|
||||
|
||||
all: uninstall-httpie install test
|
||||
|
||||
|
||||
@ -30,10 +50,10 @@ install: venv install-reqs
|
||||
|
||||
install-reqs:
|
||||
@echo $(H1)Updating package tools$(H1END)
|
||||
$(VENV_PIP) install --upgrade pip wheel
|
||||
$(VENV_PIP) install --upgrade pip wheel build
|
||||
|
||||
@echo $(H1)Installing dev requirements$(H1END)
|
||||
$(VENV_PIP) install --upgrade --editable '.[dev]'
|
||||
$(VENV_PIP) install --upgrade '.[dev]' '.[test]'
|
||||
|
||||
@echo $(H1)Installing HTTPie$(H1END)
|
||||
$(VENV_PIP) install --upgrade --editable .
|
||||
@ -153,8 +173,11 @@ doc-check:
|
||||
|
||||
|
||||
build:
|
||||
rm -rf build/
|
||||
$(VENV_PYTHON) setup.py sdist bdist_wheel
|
||||
rm -rf build/ dist/
|
||||
mv httpie/internal/__build_channel__.py httpie/internal/__build_channel__.py.original
|
||||
echo 'BUILD_CHANNEL = "pip"' > httpie/internal/__build_channel__.py
|
||||
$(VENV_PYTHON) -m build --sdist --wheel --outdir dist/
|
||||
mv httpie/internal/__build_channel__.py.original httpie/internal/__build_channel__.py
|
||||
|
||||
|
||||
publish: test-all publish-no-test
|
||||
@ -198,7 +221,7 @@ brew-test:
|
||||
- brew uninstall httpie
|
||||
|
||||
@echo $(H1)Building from source…$(H1END)
|
||||
- brew install --build-from-source ./docs/packaging/brew/httpie.rb
|
||||
- brew install --HEAD --build-from-source ./docs/packaging/brew/httpie.rb
|
||||
|
||||
@echo $(H1)Verifying…$(H1END)
|
||||
http --version
|
||||
|
13
README.md
13
README.md
@ -1,10 +1,11 @@
|
||||
<br/>
|
||||
<a href="https://httpie.io" target="blank_">
|
||||
<img height="100" alt="HTTPie" src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-logo.svg" />
|
||||
</a>
|
||||
<br/>
|
||||
|
||||
# HTTPie: human-friendly CLI HTTP client for the API era
|
||||
<h2 align="center">
|
||||
<a href="https://httpie.io" target="blank_">
|
||||
<img height="100" alt="HTTPie" src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-logo.svg" />
|
||||
</a>
|
||||
<br>
|
||||
HTTPie: human-friendly CLI HTTP client for the API era
|
||||
</h2>
|
||||
|
||||
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.
|
||||
|
@ -162,6 +162,8 @@ Also works for other Debian-derived distributions like MX Linux, Linux Mint, dee
|
||||
|
||||
```bash
|
||||
# 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 install httpie
|
||||
```
|
||||
@ -213,6 +215,21 @@ $ pacman -Syu httpie
|
||||
$ 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
|
||||
|
||||
#### FreshPorts
|
||||
@ -277,7 +294,7 @@ Synopsis:
|
||||
$ http [flags] [METHOD] URL [ITEM [ITEM]]
|
||||
```
|
||||
|
||||
See also `http --help`.
|
||||
See also `http --help` (and for systems where man pages are available, you can use `man http`).
|
||||
|
||||
### Examples
|
||||
|
||||
@ -1436,7 +1453,8 @@ $ http --proxy=http:http://user:pass@10.10.1.10:3128 example.org
|
||||
|
||||
### Environment variables
|
||||
|
||||
You can also configure proxies by environment variables `ALL_PROXY`, `HTTP_PROXY` and `HTTPS_PROXY`, and the underlying [Requests library](https://python-requests.org/) will pick them up.
|
||||
You can also configure proxies by environment variables `ALL_PROXY`, `HTTP_PROXY` and `HTTPS_PROXY`, and the underlying
|
||||
[Requests library](https://requests.readthedocs.io/en/latest/) will pick them up.
|
||||
If you want to disable proxies configured through the environment variables for certain hosts, you can specify them in `NO_PROXY`.
|
||||
|
||||
In your `~/.bash_profile`:
|
||||
@ -1653,6 +1671,10 @@ If you’d like to silence warnings as well, use `-q` or `--quiet` twice:
|
||||
$ http -qq --check-status pie.dev/post enjoy='the silence without warnings'
|
||||
```
|
||||
|
||||
### Update warnings
|
||||
|
||||
When there is a new release available for your platform (for example; if you installed HTTPie through `pip`, it will check the latest version on `PyPI`), HTTPie will regularly warn you about the new update (once a week). If you want to disable this behavior, you can set `disable_update_warnings` to `true` in your [config](#config) file.
|
||||
|
||||
### Viewing intermediary requests/responses
|
||||
|
||||
To see all the HTTP communication, i.e. the final request/response as well as any possible intermediary requests/responses, use the `--all` option.
|
||||
@ -1665,14 +1687,6 @@ $ http --all --follow pie.dev/redirect/3
|
||||
|
||||
The intermediary requests/responses are by default formatted according to `--print, -p` (and its shortcuts described above).
|
||||
|
||||
If you’d like to change that, use the `--history-print, -P` option.
|
||||
It takes the same arguments as `--print, -p` but applies to the intermediary requests only.
|
||||
|
||||
```bash
|
||||
# Print the intermediary requests/responses differently than the final one:
|
||||
$ http -A digest -a foo:bar --all -p Hh -P H pie.dev/digest-auth/auth/foo/bar
|
||||
```
|
||||
|
||||
### Conditional body download
|
||||
|
||||
As an optimization, the response body is downloaded from the server only if it’s part of the output.
|
||||
@ -2406,6 +2420,14 @@ This command is currently in beta.
|
||||
|
||||
### `httpie cli`
|
||||
|
||||
#### `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` command can expose the parser specification of `http`/`https` commands
|
||||
@ -2532,7 +2554,7 @@ HTTPie has the following community channels:
|
||||
|
||||
Under the hood, HTTPie uses these two amazing libraries:
|
||||
|
||||
- [Requests](https://python-requests.org) — Python HTTP library for humans
|
||||
- [Requests](https://requests.readthedocs.io/en/latest/) — Python HTTP library for humans
|
||||
- [Pygments](https://pygments.org/) — Python syntax highlighter
|
||||
|
||||
#### HTTPie friends
|
||||
|
@ -252,6 +252,7 @@ def fetch_missing_users_details(people: People) -> None:
|
||||
def save_awesome_people(people: People) -> None:
|
||||
with DB_FILE.open(mode='w', encoding='utf-8') as fh:
|
||||
json.dump(people, fh, indent=4, sort_keys=True)
|
||||
fh.write("\n")
|
||||
|
||||
|
||||
def debug(*args: Any) -> None:
|
||||
|
@ -8,19 +8,27 @@ from jinja2 import Template
|
||||
from fetch import HERE, load_awesome_people
|
||||
|
||||
TPL_FILE = HERE / 'snippet.jinja2'
|
||||
|
||||
HTTPIE_TEAM = {
|
||||
'claudiatd',
|
||||
'jakubroztocil',
|
||||
'jkbr',
|
||||
'isidentical'
|
||||
}
|
||||
|
||||
BOT_ACCOUNTS = {
|
||||
'dependabot-sr'
|
||||
}
|
||||
|
||||
IGNORE_ACCOUNTS = HTTPIE_TEAM | BOT_ACCOUNTS
|
||||
|
||||
|
||||
def generate_snippets(release: str) -> str:
|
||||
people = load_awesome_people()
|
||||
contributors = {
|
||||
name: details
|
||||
for name, details in people.items()
|
||||
if details['github'] not in HTTPIE_TEAM
|
||||
if details['github'] not in IGNORE_ACCOUNTS
|
||||
and (release in details['committed'] or release in details['reported'])
|
||||
}
|
||||
|
||||
|
@ -53,11 +53,13 @@
|
||||
},
|
||||
"Batuhan Taskaya": {
|
||||
"committed": [
|
||||
"3.0.0"
|
||||
"3.0.0",
|
||||
"3.2.0"
|
||||
],
|
||||
"github": "isidentical",
|
||||
"reported": [
|
||||
"3.0.0"
|
||||
"3.0.0",
|
||||
"3.2.0"
|
||||
],
|
||||
"twitter": "isidentical"
|
||||
},
|
||||
@ -118,6 +120,14 @@
|
||||
"reported": [],
|
||||
"twitter": "elena_lape"
|
||||
},
|
||||
"Ethan Mills": {
|
||||
"committed": [
|
||||
"3.2.0"
|
||||
],
|
||||
"github": "ethanmills",
|
||||
"reported": [],
|
||||
"twitter": null
|
||||
},
|
||||
"Fabio Peruzzo": {
|
||||
"committed": [],
|
||||
"github": "peruzzof",
|
||||
@ -189,7 +199,8 @@
|
||||
"committed": [
|
||||
"2.5.0",
|
||||
"2.6.0",
|
||||
"3.0.0"
|
||||
"3.0.0",
|
||||
"3.2.0"
|
||||
],
|
||||
"github": "jakubroztocil",
|
||||
"reported": [
|
||||
@ -213,7 +224,8 @@
|
||||
],
|
||||
"github": "blyxxyz",
|
||||
"reported": [
|
||||
"3.0.0"
|
||||
"3.0.0",
|
||||
"3.2.0"
|
||||
],
|
||||
"twitter": null
|
||||
},
|
||||
@ -309,7 +321,8 @@
|
||||
"committed": [],
|
||||
"github": "ducaale",
|
||||
"reported": [
|
||||
"2.5.0"
|
||||
"2.5.0",
|
||||
"3.2.0"
|
||||
],
|
||||
"twitter": null
|
||||
},
|
||||
@ -321,6 +334,22 @@
|
||||
],
|
||||
"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": {
|
||||
"committed": [
|
||||
"2.6.0",
|
||||
@ -357,6 +386,14 @@
|
||||
],
|
||||
"twitter": null
|
||||
},
|
||||
"Roberto L\u00f3pez L\u00f3pez": {
|
||||
"committed": [],
|
||||
"github": "robertolopezlopez",
|
||||
"reported": [
|
||||
"3.2.0"
|
||||
],
|
||||
"twitter": null
|
||||
},
|
||||
"Russell Shurts": {
|
||||
"committed": [],
|
||||
"github": "rshurts",
|
||||
@ -487,6 +524,14 @@
|
||||
],
|
||||
"twitter": null
|
||||
},
|
||||
"dependabot[bot]": {
|
||||
"committed": [
|
||||
"3.2.0"
|
||||
],
|
||||
"github": "dependabot-sr",
|
||||
"reported": [],
|
||||
"twitter": null
|
||||
},
|
||||
"dkreeft": {
|
||||
"committed": [
|
||||
"2.6.0",
|
||||
@ -553,6 +598,14 @@
|
||||
],
|
||||
"twitter": null
|
||||
},
|
||||
"luzpaz": {
|
||||
"committed": [
|
||||
"3.2.0"
|
||||
],
|
||||
"github": "luzpaz",
|
||||
"reported": [],
|
||||
"twitter": null
|
||||
},
|
||||
"nixbytes": {
|
||||
"committed": [
|
||||
"2.5.0"
|
||||
@ -593,6 +646,14 @@
|
||||
],
|
||||
"twitter": null
|
||||
},
|
||||
"zhaohanqing95": {
|
||||
"committed": [],
|
||||
"github": "zhaohanqing95",
|
||||
"reported": [
|
||||
"3.2.0"
|
||||
],
|
||||
"twitter": null
|
||||
},
|
||||
"zoulja": {
|
||||
"committed": [],
|
||||
"github": "zoulja",
|
||||
@ -627,4 +688,4 @@
|
||||
],
|
||||
"twitter": null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,10 @@
|
||||
|
||||
## Community contributions
|
||||
|
||||
We’d like to thank these amazing people for their contributions to this release: {% for name, details in contributors.items() -%}
|
||||
[{{ name }}](https://github.com/{{ details.github }}){{ '' if loop.last else ', ' }}
|
||||
{%- endfor %}.
|
||||
We’d like to thank these amazing people for their contributions to this release:
|
||||
{% for name, details in contributors.items() -%}
|
||||
- [{{ name }}](https://github.com/{{ details.github }}){{ '' if loop.last else '\n' }}
|
||||
{%- endfor %}
|
||||
|
||||
<!-- Twitter -->
|
||||
|
||||
|
@ -17,11 +17,12 @@ docs-structure:
|
||||
Windows:
|
||||
- chocolatey
|
||||
Linux:
|
||||
- snap-linux
|
||||
- brew-linux
|
||||
- apt
|
||||
- dnf
|
||||
- yum
|
||||
- single-binary
|
||||
- snap-linux
|
||||
- brew-linux
|
||||
- pacman
|
||||
FreeBSD:
|
||||
- pkg
|
||||
@ -36,6 +37,8 @@ tools:
|
||||
package: https://packages.debian.org/sid/web/httpie
|
||||
commands:
|
||||
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 install httpie
|
||||
upgrade:
|
||||
@ -179,3 +182,16 @@ tools:
|
||||
- yum install httpie
|
||||
upgrade:
|
||||
- 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
|
||||
|
@ -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}
|
||||
```
|
||||
|
||||
which will bump the formala, and create a PR against the package index.
|
||||
which will bump the formula, and create a PR against the package index.
|
||||
|
||||
## Hacking
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>httpie</id>
|
||||
<version>3.1.0</version>
|
||||
<version>3.2.1</version>
|
||||
<summary>Modern, user-friendly command-line HTTP client for the API era</summary>
|
||||
<description>
|
||||
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>
|
||||
<iconUrl>https://pie-assets.s3.eu-central-1.amazonaws.com/LogoIcons/GB.png</iconUrl>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<releaseNotes>See the [changelog](https://github.com/httpie/httpie/releases/tag/3.1.0).</releaseNotes>
|
||||
<releaseNotes>See the [changelog](https://github.com/httpie/httpie/releases/tag/3.2.0).</releaseNotes>
|
||||
<tags>httpie http https rest api client curl python ssl cli foss oss url</tags>
|
||||
<projectUrl>https://httpie.io</projectUrl>
|
||||
<packageSourceUrl>https://github.com/httpie/httpie/tree/master/docs/packaging/windows-chocolatey</packageSourceUrl>
|
||||
|
@ -1,52 +1,24 @@
|
||||
function __fish_httpie_styles
|
||||
echo "
|
||||
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
|
||||
rainbow_dash
|
||||
rrt
|
||||
sas
|
||||
solarized
|
||||
solarized-dark
|
||||
solarized-light
|
||||
stata
|
||||
stata-dark
|
||||
stata-light
|
||||
tango
|
||||
trac
|
||||
vim
|
||||
vs
|
||||
xcode
|
||||
zenburn"
|
||||
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
|
||||
end
|
||||
|
||||
function __fish_httpie_mime_types
|
||||
test -r /usr/share/mime/types && cat /usr/share/mime/types
|
||||
end
|
||||
|
||||
function __fish_httpie_print_args
|
||||
set -l arg (commandline -t)
|
||||
string match -qe H "$arg" || echo -e $arg"H\trequest headers"
|
||||
string match -qe B "$arg" || echo -e $arg"B\trequest body"
|
||||
string match -qe h "$arg" || echo -e $arg"h\tresponse headers"
|
||||
string match -qe b "$arg" || echo -e $arg"b\tresponse body"
|
||||
string match -qe m "$arg" || echo -e $arg"m\tresponse metadata"
|
||||
end
|
||||
|
||||
function __fish_httpie_auth_types
|
||||
echo -e "basic\tBasic HTTP auth"
|
||||
echo -e "digest\tDigest HTTP auth"
|
||||
echo -e "bearer\tBearer HTTP Auth"
|
||||
end
|
||||
|
||||
function __fish_http_verify_options
|
||||
@ -54,6 +26,7 @@ function __fish_http_verify_options
|
||||
echo -e "no\tDisable cert verification"
|
||||
end
|
||||
|
||||
|
||||
# Predefined Content Types
|
||||
|
||||
complete -c http -s j -l json -d 'Data items are serialized as a JSON object'
|
||||
@ -70,26 +43,28 @@ complete -c http -s x -l compress -d 'Content compressed with Deflate algorithm'
|
||||
|
||||
# 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 -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 format-options -x -d 'Controls output formatting'
|
||||
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 -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 response-charset -x -d 'Override the response encoding'
|
||||
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
|
||||
|
||||
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 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 -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 o -l output -F -d 'Save output to 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 q -l quiet -d 'Do not print to stdout or stderr'
|
||||
complete -c http -s p -l print -xa "(__fish_httpie_print_args)" -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 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 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 -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 d -l download -d 'Download a file'
|
||||
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'
|
||||
|
||||
|
||||
# Sessions
|
||||
@ -115,22 +90,24 @@ 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 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 chunked -d ''
|
||||
complete -c http -l chunked -d 'Enable streaming via chunked transfer encoding'
|
||||
|
||||
|
||||
# SSL
|
||||
|
||||
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 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-key -F -d 'Private key to use with SSL'
|
||||
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 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-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
|
||||
|
||||
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 manual -d 'Show the full manual'
|
||||
complete -c http -l version -d 'Show version'
|
||||
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'
|
||||
|
@ -1,4 +1,5 @@
|
||||
.TH http 1 "2022-03-08" "HTTPie 3.1.1.dev0" "HTTPie Manual"
|
||||
.\" 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-05-06" "HTTPie 3.2.1" "HTTPie Manual"
|
||||
.SH NAME
|
||||
http
|
||||
.SH SYNOPSIS
|
||||
@ -6,7 +7,7 @@ http [METHOD] URL [REQUEST_ITEM ...]
|
||||
|
||||
.SH DESCRIPTION
|
||||
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.
|
||||
Only URL is required.
|
||||
@ -27,8 +28,8 @@ is some data to be sent, otherwise GET:
|
||||
.IP "\fB\,URL\/\fR"
|
||||
|
||||
|
||||
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)
|
||||
The request URL. Scheme defaults to \[aq]http://\[aq] if the URL
|
||||
does not include one. (You can override this with: \fB\,--default-scheme\/\fR=http/https)
|
||||
|
||||
You can also use a shorthand for localhost
|
||||
|
||||
@ -43,44 +44,44 @@ You can also use a shorthand for localhost
|
||||
Optional key-value pairs to be included in the request. The separator used
|
||||
determines the type:
|
||||
|
||||
\':\' HTTP headers:
|
||||
\[aq]:\[aq] HTTP headers:
|
||||
|
||||
Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0
|
||||
|
||||
\'==\' URL parameters to be appended to the request URI:
|
||||
\[aq]==\[aq] URL parameters to be appended to the request URI:
|
||||
|
||||
search==httpie
|
||||
|
||||
\'=\' 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):
|
||||
\[aq]=\[aq] 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):
|
||||
|
||||
name=HTTPie language=Python description=\'CLI HTTP client\'
|
||||
name=HTTPie language=Python description=\[aq]CLI HTTP client\[aq]
|
||||
|
||||
\':=\' Non-string JSON data fields (only with\fB\,--json\/\fR,\fB\,-j\/\fR):
|
||||
\[aq]:=\[aq] Non-string JSON data fields (only with \fB\,--json\/\fR, \fB\,-j\/\fR):
|
||||
|
||||
awesome:=true amount:=42 colors:=\'["red", "green", "blue"]\'
|
||||
awesome:=true amount:=42 colors:=\[aq][\[dq]red\[dq], \[dq]green\[dq], \[dq]blue\[dq]]\[aq]
|
||||
|
||||
\'@\' Form file fields (only with\fB\,--form\/\fR or\fB\,--multipart\/\fR):
|
||||
\[aq]@\[aq] Form file fields (only with \fB\,--form\/\fR or \fB\,--multipart\/\fR):
|
||||
|
||||
cv@\~/Documents/CV.pdf
|
||||
cv@\'\~/Documents/CV.pdf;type=application/pdf\'
|
||||
cv@\(ti/Documents/CV.pdf
|
||||
cv@\[aq]\(ti/Documents/CV.pdf;type=application/pdf\[aq]
|
||||
|
||||
\'=@\' A data field like \'=\', but takes a file path and embeds its content:
|
||||
\[aq]=@\[aq] A data field like \[aq]=\[aq], but takes a file path and embeds its content:
|
||||
|
||||
essay=@Documents/essay.txt
|
||||
|
||||
\':=@\' A raw JSON field like \':=\', but takes a file path and embeds its content:
|
||||
\[aq]:=@\[aq] A raw JSON field like \[aq]:=\[aq], but takes a file path and embeds its content:
|
||||
|
||||
package:=@./package.json
|
||||
|
||||
You can use a backslash to escape a colliding separator in the field name:
|
||||
|
||||
field-name-with\\:colon=value
|
||||
field-name-with\e:colon=value
|
||||
|
||||
|
||||
|
||||
.PP
|
||||
.SH Predefined Content Types
|
||||
.SH Predefined content types
|
||||
.IP "\fB\,--json\/\fR, \fB\,-j\/\fR"
|
||||
|
||||
|
||||
@ -104,13 +105,13 @@ multipart/form-data request.
|
||||
.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"
|
||||
|
||||
|
||||
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"
|
||||
@ -119,7 +120,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
|
||||
(as opposed to the structured request items syntax):
|
||||
|
||||
$ http\fB\,--raw\/\fR=\'data\' pie.dev/post
|
||||
$ http \fB\,--raw\/\fR=\[aq]data\[aq] pie.dev/post
|
||||
|
||||
You can achieve the same by piping the data via stdin:
|
||||
|
||||
@ -133,7 +134,7 @@ Or have HTTPie load the raw data from a file:
|
||||
|
||||
|
||||
.PP
|
||||
.SH Content Processing Options
|
||||
.SH Content processing options
|
||||
.IP "\fB\,--compress\/\fR, \fB\,-x\/\fR"
|
||||
|
||||
|
||||
@ -146,39 +147,39 @@ negative. Compression can be forced by repeating the argument.
|
||||
|
||||
|
||||
.PP
|
||||
.SH Output Processing
|
||||
.SH Output processing
|
||||
.IP "\fB\,--pretty\/\fR"
|
||||
|
||||
|
||||
Controls output processing. The value can be "none" to not prettify
|
||||
the output (default for redirected output), "all" to apply both colors
|
||||
and formatting (default for terminal output), "colors", or "format".
|
||||
Controls output processing. The value can be \[dq]none\[dq] to not prettify
|
||||
the output (default for redirected output), \[dq]all\[dq] to apply both colors
|
||||
and formatting (default for terminal output), \[dq]colors\[dq], or \[dq]format\[dq].
|
||||
|
||||
|
||||
|
||||
.IP "\fB\,--style\/\fR, \fB\,-s\/\fR \fI\,STYLE\/\fR"
|
||||
|
||||
|
||||
Output coloring style (default is "auto"). It can be one of:
|
||||
Output coloring style (default is \[dq]auto\[dq]). It can be one of:
|
||||
|
||||
auto, pie, pie-dark, pie-light, solarized
|
||||
|
||||
|
||||
For finding out all available styles in your system, try:
|
||||
|
||||
$ http\fB\,--style\/\fR
|
||||
$ http \fB\,--style\/\fR
|
||||
|
||||
The "auto" style follows your terminal\'s ANSI color styles.
|
||||
The \[dq]auto\[dq] style follows your terminal\[aq]s ANSI color styles.
|
||||
For non-auto styles to work properly, please make sure that the
|
||||
$TERM environment variable is set to "xterm-256color" or similar
|
||||
(e.g., via `export TERM=xterm-256color\' in your \~/.bashrc).
|
||||
$TERM environment variable is set to \[dq]xterm-256color\[dq] or similar
|
||||
(e.g., via `export TERM=xterm-256color\[aq] in your \(ti/.bashrc).
|
||||
|
||||
.IP "\fB\,--unsorted\/\fR"
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
@ -187,7 +188,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:
|
||||
|
||||
\fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true
|
||||
\fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true
|
||||
|
||||
|
||||
|
||||
@ -196,8 +197,8 @@ Re-enables all sorting options while formatting output. It is a shortcut for:
|
||||
|
||||
Override the response encoding for terminal display purposes, e.g.:
|
||||
|
||||
\fB\,--response-charset\/\fR=utf8
|
||||
\fB\,--response-charset\/\fR=big5
|
||||
\fB\,--response-charset\/\fR=utf8
|
||||
\fB\,--response-charset\/\fR=big5
|
||||
|
||||
|
||||
|
||||
@ -206,8 +207,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.:
|
||||
|
||||
\fB\,--response-mime\/\fR=application/json
|
||||
\fB\,--response-mime\/\fR=text/xml
|
||||
\fB\,--response-mime\/\fR=application/json
|
||||
\fB\,--response-mime\/\fR=text/xml
|
||||
|
||||
|
||||
|
||||
@ -215,7 +216,7 @@ Override the response mime type for coloring and formatting for the terminal, e.
|
||||
|
||||
|
||||
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:
|
||||
|
||||
headers.sort:true
|
||||
@ -229,26 +230,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
|
||||
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.
|
||||
|
||||
|
||||
|
||||
.PP
|
||||
.SH Output Options
|
||||
.SH Output options
|
||||
.IP "\fB\,--print\/\fR, \fB\,-p\/\fR \fI\,WHAT\/\fR"
|
||||
|
||||
|
||||
String specifying what the output should contain:
|
||||
|
||||
\'H\' request headers
|
||||
\'B\' request body
|
||||
\'h\' response headers
|
||||
\'b\' response body
|
||||
\'m\' response metadata
|
||||
\[aq]H\[aq] request headers
|
||||
\[aq]B\[aq] request body
|
||||
\[aq]h\[aq] response headers
|
||||
\[aq]b\[aq] response body
|
||||
\[aq]m\[aq] response metadata
|
||||
|
||||
The default behaviour is \'hb\' (i.e., the response
|
||||
The default behaviour is \[aq]hb\[aq] (i.e., the response
|
||||
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
|
||||
response body is printed by default.
|
||||
@ -258,34 +259,34 @@ response body is printed by default.
|
||||
.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"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
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
|
||||
requests/responses (such as redirects). For the second level and higher,
|
||||
print these as well as the response metadata.
|
||||
|
||||
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 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
|
||||
|
||||
|
||||
.IP "\fB\,--all\/\fR"
|
||||
@ -293,33 +294,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
|
||||
any intermediary requests/responses as well. Intermediary requests include
|
||||
followed redirects (with\fB\,--follow\/\fR), the first unauthorized request when
|
||||
Digest auth is used \fB\,--auth\/\fR=digest), etc.
|
||||
|
||||
|
||||
|
||||
.IP "\fB\,--history-print\/\fR, \fB\,-P\/\fR \fI\,WHAT\/\fR"
|
||||
|
||||
|
||||
The same as\fB\,--print\/\fR,\fB\,-p\/\fR but applies only to intermediary requests/responses
|
||||
(such as redirects) when their inclusion is enabled with\fB\,--all\/\fR. If this
|
||||
options is not specified, then they are formatted the same way as the final
|
||||
response.
|
||||
followed redirects (with \fB\,--follow\/\fR), the first unauthorized request when
|
||||
Digest auth is used (\fB\,--auth\/\fR=digest), etc.
|
||||
|
||||
|
||||
|
||||
.IP "\fB\,--stream\/\fR, \fB\,-S\/\fR"
|
||||
|
||||
|
||||
Always stream the response body by line, i.e., behave like `tail\fB\,-f\/\fR\'.
|
||||
Always stream the response body by line, i.e., behave like `tail \fB\,-f\/\fR\[aq].
|
||||
|
||||
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.
|
||||
|
||||
Set this option when you want to continuously display a prettified
|
||||
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.
|
||||
|
||||
|
||||
@ -327,7 +318,7 @@ more often and in smaller chunks.
|
||||
.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
|
||||
printed to stderr.
|
||||
|
||||
@ -337,7 +328,7 @@ printed to stderr.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@ -345,7 +336,7 @@ in a file. The filename is guessed unless specified with\fB\,--output\/\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.
|
||||
|
||||
|
||||
@ -355,8 +346,8 @@ specified as well.
|
||||
|
||||
Do not print to stdout or stderr, except for errors and warnings when provided once.
|
||||
Provide twice to suppress warnings as well.
|
||||
stdout is still redirected if\fB\,--output\/\fR is specified.
|
||||
Flag doesn\'t affect behaviour of download beyond not printing to terminal.
|
||||
stdout is still redirected if \fB\,--output\/\fR is specified.
|
||||
Flag doesn\[aq]t affect behaviour of download beyond not printing to terminal.
|
||||
|
||||
|
||||
|
||||
@ -393,24 +384,24 @@ exchange.
|
||||
|
||||
For username/password based authentication mechanisms (e.g
|
||||
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"
|
||||
|
||||
|
||||
The authentication mechanism to be used. Defaults to "basic".
|
||||
The authentication mechanism to be used. Defaults to \[dq]basic\[dq].
|
||||
|
||||
"basic": Basic HTTP auth
|
||||
\[dq]basic\[dq]: Basic HTTP auth
|
||||
|
||||
"digest": Digest HTTP auth
|
||||
\[dq]digest\[dq]: Digest HTTP auth
|
||||
|
||||
"bearer": Bearer HTTP Auth
|
||||
\[dq]bearer\[dq]: Bearer HTTP Auth
|
||||
|
||||
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"
|
||||
|
||||
@ -423,7 +414,7 @@ Ignore credentials from .netrc.
|
||||
.IP "\fB\,--offline\/\fR"
|
||||
|
||||
|
||||
Build the request and print it but don\'t actually send it.
|
||||
Build the request and print it but don\(gat actually send it.
|
||||
|
||||
|
||||
.IP "\fB\,--proxy\/\fR \fI\,PROTOCOL:PROXY_URL\/\fR"
|
||||
@ -445,7 +436,7 @@ Follow 30x Location redirects.
|
||||
.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).
|
||||
|
||||
|
||||
|
||||
@ -476,7 +467,7 @@ exit with an error if the status indicates one.
|
||||
|
||||
When the server replies with a 4xx (Client Error) or 5xx (Server Error)
|
||||
status code, HTTPie exits with 4 or 5 respectively. If the response is a
|
||||
3xx (Redirect) and\fB\,--follow\/\fR hasn\'t been set, then the exit status is 3.
|
||||
3xx (Redirect) and \fB\,--follow\/\fR hasn\[aq]t been set, then the exit status is 3.
|
||||
Also an error message is written to stderr if stdout is redirected.
|
||||
|
||||
|
||||
@ -498,8 +489,8 @@ Enable streaming via chunked transfer encoding. The Transfer-Encoding header is
|
||||
.IP "\fB\,--verify\/\fR"
|
||||
|
||||
|
||||
Set to "no" (or "false") to skip checking the host\'s SSL certificate.
|
||||
Defaults to "yes" ("true"). You can also pass the path to a CA_BUNDLE file
|
||||
Set to \[dq]no\[dq] (or \[dq]false\[dq]) to skip checking the host\[aq]s SSL certificate.
|
||||
Defaults to \[dq]yes\[dq] (\[dq]true\[dq]). You can also pass the path to a CA_BUNDLE file
|
||||
for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment
|
||||
variable instead.)
|
||||
|
||||
@ -531,14 +522,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.
|
||||
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"
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@ -546,9 +537,9 @@ certificate file does not contain the private key.
|
||||
.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.
|
||||
If not provided, you\'ll be prompted interactively.
|
||||
If not provided, you\(gall be prompted interactively.
|
||||
|
||||
|
||||
.PP
|
||||
@ -597,4 +588,11 @@ 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
|
@ -1,9 +1,9 @@
|
||||
.TH httpie 1 "2022-03-08" "HTTPie 3.1.1.dev0" "HTTPie Manual"
|
||||
.\" 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-05-06" "HTTPie 3.2.1" "HTTPie Manual"
|
||||
.SH NAME
|
||||
httpie
|
||||
.SH SYNOPSIS
|
||||
httpie HOSTNAME SESSION_NAME_OR_PATH TARGET TARGET TARGET TARGET TARGET TARGET
|
||||
|
||||
httpie
|
||||
.SH DESCRIPTION
|
||||
|
||||
Managing interface for the HTTPie itself. <https://httpie.io/docs#manager>
|
||||
@ -12,12 +12,21 @@ Be aware that you might be looking for http/https commands for sending
|
||||
HTTP requests. This command is only available for managing the HTTTPie
|
||||
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
|
||||
Export available options for the CLI
|
||||
.IP "\fB\,-f\/\fR, \fB\,--format\/\fR"
|
||||
|
||||
Format to export in.
|
||||
|
||||
|
||||
.PP
|
||||
.SH httpie cli check-updates
|
||||
Check for updates
|
||||
.PP
|
||||
.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.
|
||||
|
@ -1,4 +1,5 @@
|
||||
.TH https 1 "2022-03-08" "HTTPie 3.1.1.dev0" "HTTPie Manual"
|
||||
.\" 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-05-06" "HTTPie 3.2.1" "HTTPie Manual"
|
||||
.SH NAME
|
||||
https
|
||||
.SH SYNOPSIS
|
||||
@ -6,7 +7,7 @@ https [METHOD] URL [REQUEST_ITEM ...]
|
||||
|
||||
.SH DESCRIPTION
|
||||
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.
|
||||
Only URL is required.
|
||||
@ -27,8 +28,8 @@ is some data to be sent, otherwise GET:
|
||||
.IP "\fB\,URL\/\fR"
|
||||
|
||||
|
||||
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)
|
||||
The request URL. Scheme defaults to \[aq]http://\[aq] if the URL
|
||||
does not include one. (You can override this with: \fB\,--default-scheme\/\fR=http/https)
|
||||
|
||||
You can also use a shorthand for localhost
|
||||
|
||||
@ -43,44 +44,44 @@ You can also use a shorthand for localhost
|
||||
Optional key-value pairs to be included in the request. The separator used
|
||||
determines the type:
|
||||
|
||||
\':\' HTTP headers:
|
||||
\[aq]:\[aq] HTTP headers:
|
||||
|
||||
Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0
|
||||
|
||||
\'==\' URL parameters to be appended to the request URI:
|
||||
\[aq]==\[aq] URL parameters to be appended to the request URI:
|
||||
|
||||
search==httpie
|
||||
|
||||
\'=\' 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):
|
||||
\[aq]=\[aq] 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):
|
||||
|
||||
name=HTTPie language=Python description=\'CLI HTTP client\'
|
||||
name=HTTPie language=Python description=\[aq]CLI HTTP client\[aq]
|
||||
|
||||
\':=\' Non-string JSON data fields (only with\fB\,--json\/\fR,\fB\,-j\/\fR):
|
||||
\[aq]:=\[aq] Non-string JSON data fields (only with \fB\,--json\/\fR, \fB\,-j\/\fR):
|
||||
|
||||
awesome:=true amount:=42 colors:=\'["red", "green", "blue"]\'
|
||||
awesome:=true amount:=42 colors:=\[aq][\[dq]red\[dq], \[dq]green\[dq], \[dq]blue\[dq]]\[aq]
|
||||
|
||||
\'@\' Form file fields (only with\fB\,--form\/\fR or\fB\,--multipart\/\fR):
|
||||
\[aq]@\[aq] Form file fields (only with \fB\,--form\/\fR or \fB\,--multipart\/\fR):
|
||||
|
||||
cv@\~/Documents/CV.pdf
|
||||
cv@\'\~/Documents/CV.pdf;type=application/pdf\'
|
||||
cv@\(ti/Documents/CV.pdf
|
||||
cv@\[aq]\(ti/Documents/CV.pdf;type=application/pdf\[aq]
|
||||
|
||||
\'=@\' A data field like \'=\', but takes a file path and embeds its content:
|
||||
\[aq]=@\[aq] A data field like \[aq]=\[aq], but takes a file path and embeds its content:
|
||||
|
||||
essay=@Documents/essay.txt
|
||||
|
||||
\':=@\' A raw JSON field like \':=\', but takes a file path and embeds its content:
|
||||
\[aq]:=@\[aq] A raw JSON field like \[aq]:=\[aq], but takes a file path and embeds its content:
|
||||
|
||||
package:=@./package.json
|
||||
|
||||
You can use a backslash to escape a colliding separator in the field name:
|
||||
|
||||
field-name-with\\:colon=value
|
||||
field-name-with\e:colon=value
|
||||
|
||||
|
||||
|
||||
.PP
|
||||
.SH Predefined Content Types
|
||||
.SH Predefined content types
|
||||
.IP "\fB\,--json\/\fR, \fB\,-j\/\fR"
|
||||
|
||||
|
||||
@ -104,13 +105,13 @@ multipart/form-data request.
|
||||
.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"
|
||||
|
||||
|
||||
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"
|
||||
@ -119,7 +120,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
|
||||
(as opposed to the structured request items syntax):
|
||||
|
||||
$ http\fB\,--raw\/\fR=\'data\' pie.dev/post
|
||||
$ http \fB\,--raw\/\fR=\[aq]data\[aq] pie.dev/post
|
||||
|
||||
You can achieve the same by piping the data via stdin:
|
||||
|
||||
@ -133,7 +134,7 @@ Or have HTTPie load the raw data from a file:
|
||||
|
||||
|
||||
.PP
|
||||
.SH Content Processing Options
|
||||
.SH Content processing options
|
||||
.IP "\fB\,--compress\/\fR, \fB\,-x\/\fR"
|
||||
|
||||
|
||||
@ -146,39 +147,39 @@ negative. Compression can be forced by repeating the argument.
|
||||
|
||||
|
||||
.PP
|
||||
.SH Output Processing
|
||||
.SH Output processing
|
||||
.IP "\fB\,--pretty\/\fR"
|
||||
|
||||
|
||||
Controls output processing. The value can be "none" to not prettify
|
||||
the output (default for redirected output), "all" to apply both colors
|
||||
and formatting (default for terminal output), "colors", or "format".
|
||||
Controls output processing. The value can be \[dq]none\[dq] to not prettify
|
||||
the output (default for redirected output), \[dq]all\[dq] to apply both colors
|
||||
and formatting (default for terminal output), \[dq]colors\[dq], or \[dq]format\[dq].
|
||||
|
||||
|
||||
|
||||
.IP "\fB\,--style\/\fR, \fB\,-s\/\fR \fI\,STYLE\/\fR"
|
||||
|
||||
|
||||
Output coloring style (default is "auto"). It can be one of:
|
||||
Output coloring style (default is \[dq]auto\[dq]). It can be one of:
|
||||
|
||||
auto, pie, pie-dark, pie-light, solarized
|
||||
|
||||
|
||||
For finding out all available styles in your system, try:
|
||||
|
||||
$ http\fB\,--style\/\fR
|
||||
$ http \fB\,--style\/\fR
|
||||
|
||||
The "auto" style follows your terminal\'s ANSI color styles.
|
||||
The \[dq]auto\[dq] style follows your terminal\[aq]s ANSI color styles.
|
||||
For non-auto styles to work properly, please make sure that the
|
||||
$TERM environment variable is set to "xterm-256color" or similar
|
||||
(e.g., via `export TERM=xterm-256color\' in your \~/.bashrc).
|
||||
$TERM environment variable is set to \[dq]xterm-256color\[dq] or similar
|
||||
(e.g., via `export TERM=xterm-256color\[aq] in your \(ti/.bashrc).
|
||||
|
||||
.IP "\fB\,--unsorted\/\fR"
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
@ -187,7 +188,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:
|
||||
|
||||
\fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true
|
||||
\fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true
|
||||
|
||||
|
||||
|
||||
@ -196,8 +197,8 @@ Re-enables all sorting options while formatting output. It is a shortcut for:
|
||||
|
||||
Override the response encoding for terminal display purposes, e.g.:
|
||||
|
||||
\fB\,--response-charset\/\fR=utf8
|
||||
\fB\,--response-charset\/\fR=big5
|
||||
\fB\,--response-charset\/\fR=utf8
|
||||
\fB\,--response-charset\/\fR=big5
|
||||
|
||||
|
||||
|
||||
@ -206,8 +207,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.:
|
||||
|
||||
\fB\,--response-mime\/\fR=application/json
|
||||
\fB\,--response-mime\/\fR=text/xml
|
||||
\fB\,--response-mime\/\fR=application/json
|
||||
\fB\,--response-mime\/\fR=text/xml
|
||||
|
||||
|
||||
|
||||
@ -215,7 +216,7 @@ Override the response mime type for coloring and formatting for the terminal, e.
|
||||
|
||||
|
||||
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:
|
||||
|
||||
headers.sort:true
|
||||
@ -229,26 +230,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
|
||||
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.
|
||||
|
||||
|
||||
|
||||
.PP
|
||||
.SH Output Options
|
||||
.SH Output options
|
||||
.IP "\fB\,--print\/\fR, \fB\,-p\/\fR \fI\,WHAT\/\fR"
|
||||
|
||||
|
||||
String specifying what the output should contain:
|
||||
|
||||
\'H\' request headers
|
||||
\'B\' request body
|
||||
\'h\' response headers
|
||||
\'b\' response body
|
||||
\'m\' response metadata
|
||||
\[aq]H\[aq] request headers
|
||||
\[aq]B\[aq] request body
|
||||
\[aq]h\[aq] response headers
|
||||
\[aq]b\[aq] response body
|
||||
\[aq]m\[aq] response metadata
|
||||
|
||||
The default behaviour is \'hb\' (i.e., the response
|
||||
The default behaviour is \[aq]hb\[aq] (i.e., the response
|
||||
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
|
||||
response body is printed by default.
|
||||
@ -258,34 +259,34 @@ response body is printed by default.
|
||||
.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"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
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
|
||||
requests/responses (such as redirects). For the second level and higher,
|
||||
print these as well as the response metadata.
|
||||
|
||||
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 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
|
||||
|
||||
|
||||
.IP "\fB\,--all\/\fR"
|
||||
@ -293,33 +294,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
|
||||
any intermediary requests/responses as well. Intermediary requests include
|
||||
followed redirects (with\fB\,--follow\/\fR), the first unauthorized request when
|
||||
Digest auth is used \fB\,--auth\/\fR=digest), etc.
|
||||
|
||||
|
||||
|
||||
.IP "\fB\,--history-print\/\fR, \fB\,-P\/\fR \fI\,WHAT\/\fR"
|
||||
|
||||
|
||||
The same as\fB\,--print\/\fR,\fB\,-p\/\fR but applies only to intermediary requests/responses
|
||||
(such as redirects) when their inclusion is enabled with\fB\,--all\/\fR. If this
|
||||
options is not specified, then they are formatted the same way as the final
|
||||
response.
|
||||
followed redirects (with \fB\,--follow\/\fR), the first unauthorized request when
|
||||
Digest auth is used (\fB\,--auth\/\fR=digest), etc.
|
||||
|
||||
|
||||
|
||||
.IP "\fB\,--stream\/\fR, \fB\,-S\/\fR"
|
||||
|
||||
|
||||
Always stream the response body by line, i.e., behave like `tail\fB\,-f\/\fR\'.
|
||||
Always stream the response body by line, i.e., behave like `tail \fB\,-f\/\fR\[aq].
|
||||
|
||||
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.
|
||||
|
||||
Set this option when you want to continuously display a prettified
|
||||
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.
|
||||
|
||||
|
||||
@ -327,7 +318,7 @@ more often and in smaller chunks.
|
||||
.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
|
||||
printed to stderr.
|
||||
|
||||
@ -337,7 +328,7 @@ printed to stderr.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@ -345,7 +336,7 @@ in a file. The filename is guessed unless specified with\fB\,--output\/\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.
|
||||
|
||||
|
||||
@ -355,8 +346,8 @@ specified as well.
|
||||
|
||||
Do not print to stdout or stderr, except for errors and warnings when provided once.
|
||||
Provide twice to suppress warnings as well.
|
||||
stdout is still redirected if\fB\,--output\/\fR is specified.
|
||||
Flag doesn\'t affect behaviour of download beyond not printing to terminal.
|
||||
stdout is still redirected if \fB\,--output\/\fR is specified.
|
||||
Flag doesn\[aq]t affect behaviour of download beyond not printing to terminal.
|
||||
|
||||
|
||||
|
||||
@ -393,24 +384,24 @@ exchange.
|
||||
|
||||
For username/password based authentication mechanisms (e.g
|
||||
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"
|
||||
|
||||
|
||||
The authentication mechanism to be used. Defaults to "basic".
|
||||
The authentication mechanism to be used. Defaults to \[dq]basic\[dq].
|
||||
|
||||
"basic": Basic HTTP auth
|
||||
\[dq]basic\[dq]: Basic HTTP auth
|
||||
|
||||
"digest": Digest HTTP auth
|
||||
\[dq]digest\[dq]: Digest HTTP auth
|
||||
|
||||
"bearer": Bearer HTTP Auth
|
||||
\[dq]bearer\[dq]: Bearer HTTP Auth
|
||||
|
||||
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"
|
||||
|
||||
@ -423,7 +414,7 @@ Ignore credentials from .netrc.
|
||||
.IP "\fB\,--offline\/\fR"
|
||||
|
||||
|
||||
Build the request and print it but don\'t actually send it.
|
||||
Build the request and print it but don\(gat actually send it.
|
||||
|
||||
|
||||
.IP "\fB\,--proxy\/\fR \fI\,PROTOCOL:PROXY_URL\/\fR"
|
||||
@ -445,7 +436,7 @@ Follow 30x Location redirects.
|
||||
.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).
|
||||
|
||||
|
||||
|
||||
@ -476,7 +467,7 @@ exit with an error if the status indicates one.
|
||||
|
||||
When the server replies with a 4xx (Client Error) or 5xx (Server Error)
|
||||
status code, HTTPie exits with 4 or 5 respectively. If the response is a
|
||||
3xx (Redirect) and\fB\,--follow\/\fR hasn\'t been set, then the exit status is 3.
|
||||
3xx (Redirect) and \fB\,--follow\/\fR hasn\[aq]t been set, then the exit status is 3.
|
||||
Also an error message is written to stderr if stdout is redirected.
|
||||
|
||||
|
||||
@ -498,8 +489,8 @@ Enable streaming via chunked transfer encoding. The Transfer-Encoding header is
|
||||
.IP "\fB\,--verify\/\fR"
|
||||
|
||||
|
||||
Set to "no" (or "false") to skip checking the host\'s SSL certificate.
|
||||
Defaults to "yes" ("true"). You can also pass the path to a CA_BUNDLE file
|
||||
Set to \[dq]no\[dq] (or \[dq]false\[dq]) to skip checking the host\[aq]s SSL certificate.
|
||||
Defaults to \[dq]yes\[dq] (\[dq]true\[dq]). You can also pass the path to a CA_BUNDLE file
|
||||
for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment
|
||||
variable instead.)
|
||||
|
||||
@ -531,14 +522,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.
|
||||
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"
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@ -546,9 +537,9 @@ certificate file does not contain the private key.
|
||||
.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.
|
||||
If not provided, you\'ll be prompted interactively.
|
||||
If not provided, you\(gall be prompted interactively.
|
||||
|
||||
|
||||
.PP
|
||||
@ -597,4 +588,11 @@ 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
|
@ -27,6 +27,7 @@ RUN python -m pip install /app
|
||||
RUN python -m pip install pyinstaller wheel
|
||||
RUN python -m pip install --force-reinstall --upgrade pip
|
||||
|
||||
RUN echo 'BUILD_CHANNEL="pypi"' > /app/httpie/internal/__build_channel__.py
|
||||
RUN python build.py
|
||||
|
||||
ENTRYPOINT ["mv", "/app/extras/packaging/linux/dist/", "/artifacts"]
|
||||
|
@ -6,6 +6,9 @@ from typing import Iterator, Tuple
|
||||
BUILD_DIR = Path(__file__).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')
|
||||
HOOKS_DIR = SCRIPT_DIR / 'hooks'
|
||||
|
||||
@ -50,6 +53,11 @@ def build_packages(http_binary: Path, httpie_binary: Path) -> None:
|
||||
(http_binary, '/usr/bin/https'),
|
||||
(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
|
||||
deps = [
|
||||
'python3 >= 3.7',
|
||||
@ -92,8 +100,9 @@ def main():
|
||||
build_packages(binaries['http_cli'], binaries['httpie_cli'])
|
||||
|
||||
# Rename http_cli/httpie_cli to http/httpie
|
||||
binaries['http_cli'].rename('http')
|
||||
binaries['httpie_cli'].rename('httpie')
|
||||
binaries['http_cli'].rename(DIST_DIR / 'http')
|
||||
binaries['httpie_cli'].rename(DIST_DIR / 'httpie')
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -10,7 +10,7 @@ Ensure the following requirements are satisfied:
|
||||
- Python 3.7+
|
||||
- `pyperf`
|
||||
|
||||
Then, run the `extras/benchmarks/run.py`:
|
||||
Then, run the `extras/profiling/run.py`:
|
||||
|
||||
```console
|
||||
$ python extras/profiling/run.py
|
||||
|
@ -9,7 +9,7 @@ timings.
|
||||
|
||||
The benchmarks are run through 'pyperf', which allows to
|
||||
do get very precise results. For micro-benchmarks like startup,
|
||||
please run `pyperf system tune` to get even more acurrate results.
|
||||
please run `pyperf system tune` to get even more accurate results.
|
||||
|
||||
Examples:
|
||||
|
||||
|
@ -19,19 +19,19 @@ which would include additional dependencies like pyOpenSSL.
|
||||
Examples:
|
||||
|
||||
# Run everything as usual, and compare last commit with master
|
||||
$ python extras/benchmarks/run.py
|
||||
$ python extras/profiling/run.py
|
||||
|
||||
# Include complex environments
|
||||
$ python extras/benchmarks/run.py --complex
|
||||
$ python extras/profiling/run.py --complex
|
||||
|
||||
# Compare against a fresh copy
|
||||
$ python extras/benchmarks/run.py --fresh
|
||||
$ python extras/profiling/run.py --fresh
|
||||
|
||||
# Compare against a custom branch of a custom repo
|
||||
$ python extras/benchmarks/run.py --target-repo my_repo --target-branch my_branch
|
||||
$ python extras/profiling/run.py --target-repo my_repo --target-branch my_branch
|
||||
|
||||
# Debug changes made on this script (only run benchmarks once)
|
||||
$ python extras/benchmarks/run.py --debug
|
||||
$ python extras/profiling/run.py --debug
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
|
@ -14,16 +14,19 @@ from httpie.utils import split
|
||||
|
||||
# Escape certain characters so they are rendered properly on
|
||||
# all terminals.
|
||||
# https://man7.org/linux/man-pages/man7/groff_char.7.html
|
||||
ESCAPE_MAP = {
|
||||
"'": "\\'",
|
||||
'~': '\\~',
|
||||
'’': "\\'",
|
||||
'\\': '\\\\',
|
||||
'"': '\[dq]',
|
||||
"'": '\[aq]',
|
||||
'~': '\(ti',
|
||||
'’': "\(ga",
|
||||
'\\': '\e',
|
||||
}
|
||||
ESCAPE_MAP = {ord(key): value for key, value in ESCAPE_MAP.items()}
|
||||
|
||||
EXTRAS_DIR = Path(__file__).parent.parent
|
||||
MAN_PAGE_PATH = EXTRAS_DIR / 'man'
|
||||
PROJECT_ROOT = EXTRAS_DIR.parent
|
||||
|
||||
OPTION_HIGHLIGHT_RE = re.compile(
|
||||
OptionsHighlighter.highlights[0]
|
||||
@ -57,6 +60,18 @@ class ManPageBuilder:
|
||||
def separate(self) -> None:
|
||||
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:
|
||||
text = ", ".join(map(self.boldify, options))
|
||||
if metavar:
|
||||
@ -92,8 +107,13 @@ def _escape_and_dedent(text: str) -> str:
|
||||
return '\n'.join(lines).translate(ESCAPE_MAP)
|
||||
|
||||
|
||||
def to_man_page(program_name: str, spec: ParserSpec) -> str:
|
||||
def to_man_page(program_name: str, spec: ParserSpec, *, is_top_level_cmd: bool = False) -> str:
|
||||
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(
|
||||
full_name='HTTPie',
|
||||
@ -104,10 +124,19 @@ def to_man_page(program_name: str, spec: ParserSpec) -> str:
|
||||
builder.set_name(program_name)
|
||||
|
||||
with builder.section('SYNOPSIS'):
|
||||
builder.write(render_as_string(to_usage(spec, program_name=program_name)))
|
||||
# `http` and `https` are commands that can be directly used, so they can have
|
||||
# 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'):
|
||||
builder.write(spec.description)
|
||||
if spec.man_page_hint:
|
||||
builder.write(spec.man_page_hint)
|
||||
|
||||
for index, group in enumerate(spec.groups, 1):
|
||||
with builder.section(group.name):
|
||||
@ -127,28 +156,26 @@ def to_man_page(program_name: str, spec: ParserSpec) -> str:
|
||||
metavar = None
|
||||
builder.add_options(raw_arg['options'], metavar=metavar)
|
||||
|
||||
description = _escape_and_dedent(raw_arg.get('description', ''))
|
||||
description = OPTION_HIGHLIGHT_RE.sub(
|
||||
lambda match: builder.boldify(match['option']),
|
||||
description
|
||||
)
|
||||
builder.write('\n' + description + '\n')
|
||||
desc = builder.format_desc(raw_arg.get('description', ''))
|
||||
builder.write('\n' + desc + '\n')
|
||||
|
||||
builder.separate()
|
||||
|
||||
if spec.epilog:
|
||||
with builder.section('SEE ALSO'):
|
||||
builder.write(builder.format_desc(spec.epilog))
|
||||
|
||||
|
||||
return builder.build()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
for program_name, spec in [
|
||||
('http', core_options),
|
||||
('https', core_options),
|
||||
('httpie', manager_options),
|
||||
for program_name, spec, config in [
|
||||
('http', core_options, {}),
|
||||
('https', core_options, {}),
|
||||
('httpie', manager_options, {'is_top_level_cmd': True}),
|
||||
]:
|
||||
with open((MAN_PAGE_PATH / program_name).with_suffix('.1'), 'w') as stream:
|
||||
stream.write(to_man_page(program_name, spec))
|
||||
stream.write(to_man_page(program_name, spec, **config))
|
||||
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ HTTPie: modern, user-friendly command-line HTTP client for the API era.
|
||||
|
||||
"""
|
||||
|
||||
__version__ = '3.1.1.dev0'
|
||||
__date__ = '2022-03-08'
|
||||
__version__ = '3.2.1'
|
||||
__date__ = '2022-05-06'
|
||||
__author__ = 'Jakub Roztocil'
|
||||
__licence__ = 'BSD'
|
||||
|
@ -572,12 +572,6 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
|
||||
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):
|
||||
from rich.text import Text
|
||||
from httpie.output.ui import rich_help
|
||||
|
@ -132,10 +132,3 @@ class RequestType(enum.Enum):
|
||||
FORM = enum.auto()
|
||||
MULTIPART = enum.auto()
|
||||
JSON = enum.auto()
|
||||
|
||||
|
||||
EMPTY_STRING = ''
|
||||
OPEN_BRACKET = '['
|
||||
CLOSE_BRACKET = ']'
|
||||
BACKSLASH = '\\'
|
||||
HIGHLIGHTER = '^'
|
||||
|
@ -26,16 +26,13 @@ options = ParserSpec(
|
||||
'http',
|
||||
description=f'{__doc__.strip()} <https://httpie.io>',
|
||||
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
|
||||
to its default value.
|
||||
|
||||
Suggestions and bug reports are greatly appreciated:
|
||||
https://github.com/httpie/httpie/issues
|
||||
""",
|
||||
source_file=__file__
|
||||
)
|
||||
|
||||
|
||||
@ -44,7 +41,7 @@ options = ParserSpec(
|
||||
#######################################################################
|
||||
|
||||
positional_arguments = options.add_group(
|
||||
'Positional Arguments',
|
||||
'Positional arguments',
|
||||
description="""
|
||||
These arguments come after any flags and in the order they are listed here.
|
||||
Only URL is required.
|
||||
@ -145,7 +142,7 @@ positional_arguments.add_argument(
|
||||
# Content type.
|
||||
#######################################################################
|
||||
|
||||
content_types = options.add_group('Predefined Content Types')
|
||||
content_types = options.add_group('Predefined content types')
|
||||
|
||||
content_types.add_argument(
|
||||
'--json',
|
||||
@ -219,7 +216,7 @@ content_types.add_argument(
|
||||
# Content processing.
|
||||
#######################################################################
|
||||
|
||||
processing_options = options.add_group('Content Processing Options')
|
||||
processing_options = options.add_group('Content processing options')
|
||||
|
||||
processing_options.add_argument(
|
||||
'--compress',
|
||||
@ -284,7 +281,7 @@ _unsorted_kwargs = {
|
||||
'dest': 'format_options',
|
||||
}
|
||||
|
||||
output_processing = options.add_group('Output Processing')
|
||||
output_processing = options.add_group('Output processing')
|
||||
|
||||
output_processing.add_argument(
|
||||
'--pretty',
|
||||
@ -398,7 +395,7 @@ output_processing.add_argument(
|
||||
# Output options
|
||||
#######################################################################
|
||||
|
||||
output_options = options.add_group('Output Options')
|
||||
output_options = options.add_group('Output options')
|
||||
|
||||
output_options.add_argument(
|
||||
'--print',
|
||||
@ -494,14 +491,7 @@ output_options.add_argument(
|
||||
'-P',
|
||||
dest='output_options_history',
|
||||
metavar='WHAT',
|
||||
short_help='--print for intermediary requests/responses.',
|
||||
help="""
|
||||
The same as --print, -p but applies only to intermediary requests/responses
|
||||
(such as redirects) when their inclusion is enabled with --all. If this
|
||||
options is not specified, then they are formatted the same way as the final
|
||||
response.
|
||||
|
||||
""",
|
||||
help=Qualifiers.SUPPRESS,
|
||||
)
|
||||
output_options.add_argument(
|
||||
'--stream',
|
||||
|
@ -9,8 +9,14 @@ from typing import (
|
||||
Type,
|
||||
Union,
|
||||
)
|
||||
from httpie.cli.dicts import NestedJSONArray
|
||||
from httpie.cli.constants import EMPTY_STRING, OPEN_BRACKET, CLOSE_BRACKET, BACKSLASH, HIGHLIGHTER
|
||||
from .dicts import NestedJSONArray
|
||||
|
||||
|
||||
EMPTY_STRING = ''
|
||||
HIGHLIGHTER = '^'
|
||||
OPEN_BRACKET = '['
|
||||
CLOSE_BRACKET = ']'
|
||||
BACKSLASH = '\\'
|
||||
|
||||
|
||||
class HTTPieSyntaxError(ValueError):
|
||||
@ -31,7 +37,7 @@ class HTTPieSyntaxError(ValueError):
|
||||
if self.token is not None:
|
||||
lines.append(self.source)
|
||||
lines.append(
|
||||
' ' * (self.token.start)
|
||||
' ' * self.token.start
|
||||
+ HIGHLIGHTER * (self.token.end - self.token.start)
|
||||
)
|
||||
return '\n'.join(lines)
|
||||
@ -51,9 +57,15 @@ class TokenKind(Enum):
|
||||
return 'a ' + self.name.lower()
|
||||
|
||||
|
||||
OPERATORS = {OPEN_BRACKET: TokenKind.LEFT_BRACKET, CLOSE_BRACKET: TokenKind.RIGHT_BRACKET}
|
||||
OPERATORS = {
|
||||
OPEN_BRACKET: TokenKind.LEFT_BRACKET,
|
||||
CLOSE_BRACKET: TokenKind.RIGHT_BRACKET,
|
||||
}
|
||||
SPECIAL_CHARS = OPERATORS.keys() | {BACKSLASH}
|
||||
LITERAL_TOKENS = [TokenKind.TEXT, TokenKind.NUMBER]
|
||||
LITERAL_TOKENS = [
|
||||
TokenKind.TEXT,
|
||||
TokenKind.NUMBER,
|
||||
]
|
||||
|
||||
|
||||
class Token(NamedTuple):
|
||||
|
@ -35,17 +35,6 @@ 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'
|
||||
|
||||
|
||||
@ -55,6 +44,8 @@ class ParserSpec:
|
||||
description: Optional[str] = None
|
||||
epilog: Optional[str] = None
|
||||
groups: List['Group'] = field(default_factory=list)
|
||||
man_page_hint: Optional[str] = None
|
||||
source_file: Optional[str] = None
|
||||
|
||||
def finalize(self) -> 'ParserSpec':
|
||||
if self.description:
|
||||
@ -248,10 +239,11 @@ def to_data(abstract_options: ParserSpec) -> Dict[str, Any]:
|
||||
return {'version': PARSER_SPEC_VERSION, 'spec': abstract_options.serialize()}
|
||||
|
||||
|
||||
def parser_to_parser_spec(parser: argparse.ArgumentParser) -> ParserSpec:
|
||||
def parser_to_parser_spec(parser: argparse.ArgumentParser, **kwargs) -> ParserSpec:
|
||||
"""Take an existing argparse parser, and create a spec from it."""
|
||||
return ParserSpec(
|
||||
program=parser.prog,
|
||||
description=parser.description,
|
||||
epilog=parser.epilog
|
||||
epilog=parser.epilog,
|
||||
**kwargs
|
||||
)
|
||||
|
@ -13,7 +13,8 @@ import urllib3
|
||||
from . import __version__
|
||||
from .adapters import HTTPieHTTPAdapter
|
||||
from .context import Environment
|
||||
from .cli.constants import EMPTY_STRING, HTTP_OPTIONS
|
||||
from .cli.constants import HTTP_OPTIONS
|
||||
from .cli.nested_json import EMPTY_STRING
|
||||
from .cli.dicts import HTTPHeadersDict, NestedJSONArray
|
||||
from .encoding import UTF8
|
||||
from .models import RequestsMessage
|
||||
|
@ -149,6 +149,24 @@ class Config(BaseConfigDict):
|
||||
def default_options(self) -> list:
|
||||
return self['default_options']
|
||||
|
||||
def _configured_path(self, config_option: str, default: str) -> None:
|
||||
return Path(
|
||||
self.get(config_option, self.directory / default)
|
||||
).expanduser().resolve()
|
||||
|
||||
@property
|
||||
def plugins_dir(self) -> Path:
|
||||
return Path(self.get('plugins_dir', self.directory / 'plugins')).resolve()
|
||||
return self._configured_path('plugins_dir', 'plugins')
|
||||
|
||||
@property
|
||||
def version_info_file(self) -> Path:
|
||||
return self._configured_path('version_info_file', 'version_info.json')
|
||||
|
||||
@property
|
||||
def developer_mode(self) -> bool:
|
||||
"""This is a special setting for the development environment. It is
|
||||
different from the --debug mode in the terms that it might change
|
||||
the behavior for certain parameters (e.g updater system) that
|
||||
we usually ignore."""
|
||||
|
||||
return self.get('developer_mode')
|
||||
|
@ -18,20 +18,28 @@ from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
|
||||
from .encoding import UTF8
|
||||
|
||||
from .utils import repr_dict
|
||||
from httpie.output.ui import rich_palette as palette
|
||||
from .output.ui.palette import GenericColor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from rich.console import Console
|
||||
|
||||
|
||||
class Levels(str, Enum):
|
||||
class LogLevel(str, Enum):
|
||||
INFO = 'info'
|
||||
WARNING = 'warning'
|
||||
ERROR = 'error'
|
||||
|
||||
|
||||
DISPLAY_THRESHOLDS = {
|
||||
Levels.WARNING: 2,
|
||||
Levels.ERROR: float('inf'), # Never hide errors.
|
||||
LOG_LEVEL_COLORS = {
|
||||
LogLevel.INFO: GenericColor.PINK,
|
||||
LogLevel.WARNING: GenericColor.ORANGE,
|
||||
LogLevel.ERROR: GenericColor.RED,
|
||||
}
|
||||
|
||||
LOG_LEVEL_DISPLAY_THRESHOLDS = {
|
||||
LogLevel.INFO: 1,
|
||||
LogLevel.WARNING: 2,
|
||||
LogLevel.ERROR: float('inf'), # Never hide errors.
|
||||
}
|
||||
|
||||
|
||||
@ -159,16 +167,22 @@ class Environment:
|
||||
self.stdout = original_stdout
|
||||
self.stderr = original_stderr
|
||||
|
||||
def log_error(self, msg: str, level: Levels = Levels.ERROR) -> None:
|
||||
if self.stdout_isatty and self.quiet >= DISPLAY_THRESHOLDS[level]:
|
||||
def log_error(self, msg: str, level: LogLevel = LogLevel.ERROR) -> None:
|
||||
if self.stdout_isatty and self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[level]:
|
||||
stderr = self.stderr # Not directly /dev/null, since stderr might be mocked
|
||||
else:
|
||||
stderr = self._orig_stderr
|
||||
|
||||
stderr.write(f'\n{self.program_name}: {level}: {msg}\n\n')
|
||||
rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty())
|
||||
rich_console.print(
|
||||
f'\n{self.program_name}: {level}: {msg}\n\n',
|
||||
style=LOG_LEVEL_COLORS[level],
|
||||
markup=False,
|
||||
highlight=False,
|
||||
soft_wrap=True
|
||||
)
|
||||
|
||||
def apply_warnings_filter(self) -> None:
|
||||
if self.quiet >= DISPLAY_THRESHOLDS[Levels.WARNING]:
|
||||
if self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[LogLevel.WARNING]:
|
||||
warnings.simplefilter("ignore")
|
||||
|
||||
def _make_rich_console(
|
||||
@ -177,32 +191,17 @@ class Environment:
|
||||
force_terminal: bool
|
||||
) -> 'Console':
|
||||
from rich.console import Console
|
||||
from rich.theme import Theme
|
||||
from rich.style import Style
|
||||
|
||||
style = getattr(self.args, 'style', palette.AUTO_STYLE)
|
||||
theme = {}
|
||||
if style in palette.STYLE_SHADES:
|
||||
shade = palette.STYLE_SHADES[style]
|
||||
theme.update({
|
||||
color: Style(
|
||||
color=palette.get_color(
|
||||
color,
|
||||
shade,
|
||||
palette=palette.RICH_THEME_PALETTE
|
||||
),
|
||||
bold=True
|
||||
)
|
||||
for color in palette.RICH_THEME_PALETTE
|
||||
})
|
||||
from httpie.output.ui.rich_palette import _make_rich_color_theme
|
||||
|
||||
style = getattr(self.args, 'style', None)
|
||||
theme = _make_rich_color_theme(style)
|
||||
# Rich infers the rest of the knowledge (e.g encoding)
|
||||
# dynamically by looking at the file/stderr.
|
||||
return Console(
|
||||
file=file,
|
||||
force_terminal=force_terminal,
|
||||
no_color=(self.colors == 0),
|
||||
theme=Theme(theme)
|
||||
theme=theme
|
||||
)
|
||||
|
||||
# Rich recommends separating the actual console (stdout) from
|
||||
|
@ -13,7 +13,7 @@ from . import __version__ as httpie_version
|
||||
from .cli.constants import OUT_REQ_BODY
|
||||
from .cli.nested_json import HTTPieSyntaxError
|
||||
from .client import collect_messages
|
||||
from .context import Environment, Levels
|
||||
from .context import Environment, LogLevel
|
||||
from .downloads import Downloader
|
||||
from .models import (
|
||||
RequestsMessageKind,
|
||||
@ -24,6 +24,8 @@ from .output.writer import write_message, write_stream, write_raw_data, MESSAGE_
|
||||
from .plugins.registry import plugin_manager
|
||||
from .status import ExitStatus, http_status_to_exit_status
|
||||
from .utils import unwrap_context
|
||||
from .internal.update_warnings import check_updates
|
||||
from .internal.daemon_runner import is_daemon_mode, run_daemon_task
|
||||
|
||||
|
||||
# noinspection PyDefaultArgument
|
||||
@ -37,6 +39,10 @@ def raw_main(
|
||||
program_name, *args = args
|
||||
env.program_name = os.path.basename(program_name)
|
||||
args = decode_raw_args(args, env.stdin_encoding)
|
||||
|
||||
if is_daemon_mode(args):
|
||||
return run_daemon_task(env, args)
|
||||
|
||||
plugin_manager.load_installed_plugins(env.config.plugins_dir)
|
||||
|
||||
if use_default_options and env.config.default_options:
|
||||
@ -89,6 +95,7 @@ def raw_main(
|
||||
raise
|
||||
exit_status = ExitStatus.ERROR
|
||||
else:
|
||||
check_updates(env)
|
||||
try:
|
||||
exit_status = main_program(
|
||||
args=parsed_args,
|
||||
@ -223,7 +230,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
|
||||
if args.check_status or downloader:
|
||||
exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)
|
||||
if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet == 1):
|
||||
env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level=Levels.WARNING)
|
||||
env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level=LogLevel.WARNING)
|
||||
write_message(
|
||||
requests_message=message,
|
||||
env=env,
|
||||
|
5
httpie/internal/__build_channel__.py
Normal file
5
httpie/internal/__build_channel__.py
Normal file
@ -0,0 +1,5 @@
|
||||
# Represents the packaging method. This file should
|
||||
# be overridden by every build system we support on
|
||||
# the packaging step.
|
||||
|
||||
BUILD_CHANNEL = 'unknown'
|
0
httpie/internal/__init__.py
Normal file
0
httpie/internal/__init__.py
Normal file
50
httpie/internal/daemon_runner.py
Normal file
50
httpie/internal/daemon_runner.py
Normal file
@ -0,0 +1,50 @@
|
||||
import argparse
|
||||
from contextlib import redirect_stderr, redirect_stdout
|
||||
from typing import List
|
||||
|
||||
from httpie.context import Environment
|
||||
from httpie.internal.update_warnings import _fetch_updates, _get_suppress_context
|
||||
from httpie.status import ExitStatus
|
||||
|
||||
STATUS_FILE = '.httpie-test-daemon-status'
|
||||
|
||||
|
||||
def _check_status(env):
|
||||
# This function is used only for the testing (test_update_warnings).
|
||||
# Since we don't want to trigger the fetch_updates (which would interact
|
||||
# with real world resources), we'll only trigger this pseudo task
|
||||
# and check whether the STATUS_FILE is created or not.
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
status_file = Path(tempfile.gettempdir()) / STATUS_FILE
|
||||
status_file.touch()
|
||||
|
||||
|
||||
DAEMONIZED_TASKS = {
|
||||
'check_status': _check_status,
|
||||
'fetch_updates': _fetch_updates,
|
||||
}
|
||||
|
||||
|
||||
def _parse_options(args: List[str]) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('task_id')
|
||||
parser.add_argument('--daemon', action='store_true')
|
||||
return parser.parse_known_args(args)[0]
|
||||
|
||||
|
||||
def is_daemon_mode(args: List[str]) -> bool:
|
||||
return '--daemon' in args
|
||||
|
||||
|
||||
def run_daemon_task(env: Environment, args: List[str]) -> ExitStatus:
|
||||
options = _parse_options(args)
|
||||
|
||||
assert options.daemon
|
||||
assert options.task_id in DAEMONIZED_TASKS
|
||||
with redirect_stdout(env.devnull), redirect_stderr(env.devnull):
|
||||
with _get_suppress_context(env):
|
||||
DAEMONIZED_TASKS[options.task_id](env)
|
||||
|
||||
return ExitStatus.SUCCESS
|
121
httpie/internal/daemons.py
Normal file
121
httpie/internal/daemons.py
Normal file
@ -0,0 +1,121 @@
|
||||
"""
|
||||
This module provides an interface to spawn a detached task to be
|
||||
run with httpie.internal.daemon_runner on a separate process. It is
|
||||
based on DVC's daemon system.
|
||||
https://github.com/iterative/dvc/blob/main/dvc/daemon.py
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import httpie.__main__
|
||||
from contextlib import suppress
|
||||
from subprocess import Popen, DEVNULL
|
||||
from typing import Dict, List
|
||||
from httpie.compat import is_frozen, is_windows
|
||||
|
||||
|
||||
ProcessContext = Dict[str, str]
|
||||
|
||||
|
||||
def _start_process(cmd: List[str], **kwargs) -> Popen:
|
||||
prefix = [sys.executable]
|
||||
# If it is frozen, sys.executable points to the binary (http).
|
||||
# Otherwise it points to the python interpreter.
|
||||
if not is_frozen:
|
||||
main_entrypoint = httpie.__main__.__file__
|
||||
prefix += [main_entrypoint]
|
||||
return Popen(prefix + cmd, close_fds=True, shell=False, stdout=DEVNULL, stderr=DEVNULL, **kwargs)
|
||||
|
||||
|
||||
def _spawn_windows(cmd: List[str], process_context: ProcessContext) -> None:
|
||||
from subprocess import (
|
||||
CREATE_NEW_PROCESS_GROUP,
|
||||
CREATE_NO_WINDOW,
|
||||
STARTF_USESHOWWINDOW,
|
||||
STARTUPINFO,
|
||||
)
|
||||
|
||||
# https://stackoverflow.com/a/7006424
|
||||
# https://bugs.python.org/issue41619
|
||||
creationflags = CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW
|
||||
|
||||
startupinfo = STARTUPINFO()
|
||||
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
|
||||
|
||||
_start_process(
|
||||
cmd,
|
||||
env=process_context,
|
||||
creationflags=creationflags,
|
||||
startupinfo=startupinfo,
|
||||
)
|
||||
|
||||
|
||||
def _spawn_posix(args: List[str], process_context: ProcessContext) -> None:
|
||||
"""
|
||||
Perform a double fork procedure* to detach from the parent
|
||||
process so that we don't block the user even if their original
|
||||
command's execution is done but the release fetcher is not.
|
||||
|
||||
[1]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html#tag_11_01_03
|
||||
"""
|
||||
|
||||
from httpie.core import main
|
||||
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
return
|
||||
except OSError:
|
||||
os._exit(1)
|
||||
|
||||
os.setsid()
|
||||
|
||||
try:
|
||||
pid = os.fork()
|
||||
if pid > 0:
|
||||
os._exit(0)
|
||||
except OSError:
|
||||
os._exit(1)
|
||||
|
||||
# Close all standard inputs/outputs
|
||||
sys.stdin.close()
|
||||
sys.stdout.close()
|
||||
sys.stderr.close()
|
||||
|
||||
if platform.system() == 'Darwin':
|
||||
# Double-fork is not reliable on MacOS, so we'll use a subprocess
|
||||
# to ensure the task is isolated properly.
|
||||
process = _start_process(args, env=process_context)
|
||||
# Unlike windows, since we already completed the fork procedure
|
||||
# we can simply join the process and wait for it.
|
||||
process.communicate()
|
||||
else:
|
||||
os.environ.update(process_context)
|
||||
with suppress(BaseException):
|
||||
main(['http'] + args)
|
||||
|
||||
os._exit(0)
|
||||
|
||||
|
||||
def _spawn(args: List[str], process_context: ProcessContext) -> None:
|
||||
"""
|
||||
Spawn a new process to run the given command.
|
||||
"""
|
||||
if is_windows:
|
||||
_spawn_windows(args, process_context)
|
||||
else:
|
||||
_spawn_posix(args, process_context)
|
||||
|
||||
|
||||
def spawn_daemon(task: str) -> None:
|
||||
args = [task, '--daemon']
|
||||
process_context = os.environ.copy()
|
||||
if not is_frozen:
|
||||
file_path = os.path.abspath(inspect.stack()[0][1])
|
||||
process_context['PYTHONPATH'] = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(file_path))
|
||||
)
|
||||
|
||||
_spawn(args, process_context)
|
171
httpie/internal/update_warnings.py
Normal file
171
httpie/internal/update_warnings.py
Normal file
@ -0,0 +1,171 @@
|
||||
import json
|
||||
from contextlib import nullcontext, suppress
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional, Callable
|
||||
|
||||
import requests
|
||||
|
||||
import httpie
|
||||
from httpie.context import Environment, LogLevel
|
||||
from httpie.internal.__build_channel__ import BUILD_CHANNEL
|
||||
from httpie.internal.daemons import spawn_daemon
|
||||
from httpie.utils import is_version_greater, open_with_lockfile
|
||||
|
||||
# Automatically updated package version index.
|
||||
PACKAGE_INDEX_LINK = 'https://packages.httpie.io/latest.json'
|
||||
|
||||
FETCH_INTERVAL = timedelta(weeks=2)
|
||||
WARN_INTERVAL = timedelta(weeks=1)
|
||||
|
||||
UPDATE_MESSAGE_FORMAT = """\
|
||||
A new HTTPie release ({last_released_version}) is available.
|
||||
To see how you can update, please visit https://httpie.io/docs/cli/{installation_method}
|
||||
"""
|
||||
|
||||
ALREADY_UP_TO_DATE_MESSAGE = """\
|
||||
You are already up-to-date.
|
||||
"""
|
||||
|
||||
|
||||
def _read_data_error_free(file: Path) -> Any:
|
||||
# If the file is broken / non-existent, ignore it.
|
||||
try:
|
||||
with open(file) as stream:
|
||||
return json.load(stream)
|
||||
except (ValueError, OSError):
|
||||
return {}
|
||||
|
||||
|
||||
def _fetch_updates(env: Environment) -> str:
|
||||
file = env.config.version_info_file
|
||||
data = _read_data_error_free(file)
|
||||
|
||||
response = requests.get(PACKAGE_INDEX_LINK, verify=False)
|
||||
response.raise_for_status()
|
||||
|
||||
data.setdefault('last_warned_date', None)
|
||||
data['last_fetched_date'] = datetime.now().isoformat()
|
||||
data['last_released_versions'] = response.json()
|
||||
|
||||
with open_with_lockfile(file, 'w') as stream:
|
||||
json.dump(data, stream)
|
||||
|
||||
|
||||
def fetch_updates(env: Environment, lazy: bool = True):
|
||||
if lazy:
|
||||
spawn_daemon('fetch_updates')
|
||||
else:
|
||||
_fetch_updates(env)
|
||||
|
||||
|
||||
def maybe_fetch_updates(env: Environment) -> None:
|
||||
if env.config.get('disable_update_warnings'):
|
||||
return None
|
||||
|
||||
data = _read_data_error_free(env.config.version_info_file)
|
||||
|
||||
if data:
|
||||
current_date = datetime.now()
|
||||
last_fetched_date = datetime.fromisoformat(data['last_fetched_date'])
|
||||
earliest_fetch_date = last_fetched_date + FETCH_INTERVAL
|
||||
if current_date < earliest_fetch_date:
|
||||
return None
|
||||
|
||||
fetch_updates(env)
|
||||
|
||||
|
||||
def _get_suppress_context(env: Environment) -> Any:
|
||||
"""Return a context manager that suppress
|
||||
all possible errors.
|
||||
|
||||
Note: if you have set the developer_mode=True in
|
||||
your config, then it will show all errors for easier
|
||||
debugging."""
|
||||
if env.config.developer_mode:
|
||||
return nullcontext()
|
||||
else:
|
||||
return suppress(BaseException)
|
||||
|
||||
|
||||
def _update_checker(
|
||||
func: Callable[[Environment], None]
|
||||
) -> Callable[[Environment], None]:
|
||||
"""Control the execution of the update checker (suppress errors, trigger
|
||||
auto updates etc.)"""
|
||||
|
||||
def wrapper(env: Environment) -> None:
|
||||
with _get_suppress_context(env):
|
||||
func(env)
|
||||
|
||||
with _get_suppress_context(env):
|
||||
maybe_fetch_updates(env)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def _get_update_status(env: Environment) -> Optional[str]:
|
||||
"""If there is a new update available, return the warning text.
|
||||
Otherwise just return None."""
|
||||
file = env.config.version_info_file
|
||||
if not file.exists():
|
||||
return None
|
||||
|
||||
with _get_suppress_context(env):
|
||||
# If the user quickly spawns multiple httpie processes
|
||||
# we don't want to end in a race.
|
||||
with open_with_lockfile(file) as stream:
|
||||
version_info = json.load(stream)
|
||||
|
||||
available_channels = version_info['last_released_versions']
|
||||
if BUILD_CHANNEL not in available_channels:
|
||||
return None
|
||||
|
||||
current_version = httpie.__version__
|
||||
last_released_version = available_channels[BUILD_CHANNEL]
|
||||
if not is_version_greater(last_released_version, current_version):
|
||||
return None
|
||||
|
||||
text = UPDATE_MESSAGE_FORMAT.format(
|
||||
last_released_version=last_released_version,
|
||||
installation_method=BUILD_CHANNEL,
|
||||
)
|
||||
return text
|
||||
|
||||
|
||||
def get_update_status(env: Environment) -> str:
|
||||
return _get_update_status(env) or ALREADY_UP_TO_DATE_MESSAGE
|
||||
|
||||
|
||||
@_update_checker
|
||||
def check_updates(env: Environment) -> None:
|
||||
if env.config.get('disable_update_warnings'):
|
||||
return None
|
||||
|
||||
file = env.config.version_info_file
|
||||
update_status = _get_update_status(env)
|
||||
|
||||
if not update_status:
|
||||
return None
|
||||
|
||||
# If the user quickly spawns multiple httpie processes
|
||||
# we don't want to end in a race.
|
||||
with open_with_lockfile(file) as stream:
|
||||
version_info = json.load(stream)
|
||||
|
||||
# We don't want to spam the user with too many warnings,
|
||||
# so we'll only warn every once a while (WARN_INTERNAL).
|
||||
current_date = datetime.now()
|
||||
last_warned_date = version_info['last_warned_date']
|
||||
if last_warned_date is not None:
|
||||
earliest_warn_date = (
|
||||
datetime.fromisoformat(last_warned_date) + WARN_INTERVAL
|
||||
)
|
||||
if current_date < earliest_warn_date:
|
||||
return None
|
||||
|
||||
env.log_error(update_status, level=LogLevel.INFO)
|
||||
version_info['last_warned_date'] = current_date.isoformat()
|
||||
|
||||
with open_with_lockfile(file, 'w') as stream:
|
||||
json.dump(version_info, stream)
|
@ -20,9 +20,13 @@ COMMANDS = {
|
||||
{
|
||||
'flags': ['-f', '--format'],
|
||||
'choices': ['json'],
|
||||
'help': 'Format to export in.',
|
||||
'default': 'json'
|
||||
}
|
||||
],
|
||||
'check-updates': [
|
||||
'Check for updates'
|
||||
],
|
||||
'sessions': {
|
||||
'help': 'Manage HTTPie sessions',
|
||||
'upgrade': [
|
||||
@ -166,5 +170,12 @@ parser.add_argument(
|
||||
'''
|
||||
)
|
||||
|
||||
options = parser_to_parser_spec(parser)
|
||||
man_page_hint = '''
|
||||
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)
|
||||
|
@ -1,9 +1,11 @@
|
||||
from httpie.manager.tasks.sessions import cli_sessions
|
||||
from httpie.manager.tasks.export_args import cli_export_args
|
||||
from httpie.manager.tasks.plugins import cli_plugins
|
||||
from httpie.manager.tasks.check_updates import cli_check_updates
|
||||
|
||||
CLI_TASKS = {
|
||||
'sessions': cli_sessions,
|
||||
'export-args': cli_export_args,
|
||||
'plugins': cli_plugins,
|
||||
'check-updates': cli_check_updates
|
||||
}
|
||||
|
10
httpie/manager/tasks/check_updates.py
Normal file
10
httpie/manager/tasks/check_updates.py
Normal file
@ -0,0 +1,10 @@
|
||||
import argparse
|
||||
from httpie.context import Environment
|
||||
from httpie.status import ExitStatus
|
||||
from httpie.internal.update_warnings import fetch_updates, get_update_status
|
||||
|
||||
|
||||
def cli_check_updates(env: Environment, args: argparse.Namespace) -> ExitStatus:
|
||||
fetch_updates(env, lazy=False)
|
||||
env.stdout.write(get_update_status(env))
|
||||
return ExitStatus.SUCCESS
|
@ -1,11 +1,11 @@
|
||||
import argparse
|
||||
from typing import Tuple
|
||||
|
||||
from httpie.sessions import SESSIONS_DIR_NAME, get_httpie_session
|
||||
from httpie.status import ExitStatus
|
||||
from httpie.context import Environment
|
||||
from httpie.legacy import v3_1_0_session_cookie_format, v3_2_0_session_header_format
|
||||
from httpie.manager.cli import missing_subcommand, parser
|
||||
from httpie.utils import is_version_greater
|
||||
|
||||
|
||||
FIXERS_TO_VERSIONS = {
|
||||
@ -27,25 +27,6 @@ def cli_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus:
|
||||
raise ValueError(f'Unexpected action: {action}')
|
||||
|
||||
|
||||
def is_version_greater(version_1: str, version_2: str) -> bool:
|
||||
# In an ideal scenario, we would depend on `packaging` in order
|
||||
# to offer PEP 440 compatible parsing. But since it might not be
|
||||
# commonly available for outside packages, and since we are only
|
||||
# going to parse HTTPie's own version it should be fine to compare
|
||||
# this in a SemVer subset fashion.
|
||||
|
||||
def split_version(version: str) -> Tuple[int, ...]:
|
||||
parts = []
|
||||
for part in version.split('.')[:3]:
|
||||
try:
|
||||
parts.append(int(part))
|
||||
except ValueError:
|
||||
break
|
||||
return tuple(parts)
|
||||
|
||||
return split_version(version_1) > split_version(version_2)
|
||||
|
||||
|
||||
def upgrade_session(env: Environment, args: argparse.Namespace, hostname: str, session_name: str):
|
||||
session = get_httpie_session(
|
||||
env=env,
|
||||
|
@ -17,13 +17,15 @@ from pygments.util import ClassNotFound
|
||||
|
||||
from ..lexers.json import EnhancedJsonLexer
|
||||
from ..lexers.metadata import MetadataLexer
|
||||
from ..ui.palette import AUTO_STYLE, SHADE_NAMES, get_color
|
||||
from ..ui.palette import AUTO_STYLE, SHADE_TO_PIE_STYLE, PieColor, ColorString, get_color
|
||||
from ...context import Environment
|
||||
from ...plugins import FormatterPlugin
|
||||
|
||||
|
||||
DEFAULT_STYLE = AUTO_STYLE
|
||||
SOLARIZED_STYLE = 'solarized' # Bundled here
|
||||
PYGMENTS_BOLD = ColorString('bold')
|
||||
PYGMENTS_ITALIC = ColorString('italic')
|
||||
|
||||
BUNDLED_STYLES = {
|
||||
SOLARIZED_STYLE,
|
||||
@ -253,11 +255,11 @@ class Solarized256Style(pygments.style.Style):
|
||||
pygments.token.Comment.Preproc: GREEN,
|
||||
pygments.token.Comment.Special: GREEN,
|
||||
pygments.token.Generic.Deleted: CYAN,
|
||||
pygments.token.Generic.Emph: 'italic',
|
||||
pygments.token.Generic.Emph: PYGMENTS_ITALIC,
|
||||
pygments.token.Generic.Error: RED,
|
||||
pygments.token.Generic.Heading: ORANGE,
|
||||
pygments.token.Generic.Inserted: GREEN,
|
||||
pygments.token.Generic.Strong: 'bold',
|
||||
pygments.token.Generic.Strong: PYGMENTS_BOLD,
|
||||
pygments.token.Generic.Subheading: ORANGE,
|
||||
pygments.token.Token: BASE1,
|
||||
pygments.token.Token.Other: ORANGE,
|
||||
@ -266,86 +268,86 @@ class Solarized256Style(pygments.style.Style):
|
||||
|
||||
PIE_HEADER_STYLE = {
|
||||
# HTTP line / Headers / Etc.
|
||||
pygments.token.Name.Namespace: 'bold primary',
|
||||
pygments.token.Keyword.Reserved: 'bold grey',
|
||||
pygments.token.Operator: 'bold grey',
|
||||
pygments.token.Number: 'bold grey',
|
||||
pygments.token.Name.Function.Magic: 'bold green',
|
||||
pygments.token.Name.Exception: 'bold green',
|
||||
pygments.token.Name.Attribute: 'blue',
|
||||
pygments.token.String: 'primary',
|
||||
pygments.token.Name.Namespace: PYGMENTS_BOLD | PieColor.PRIMARY,
|
||||
pygments.token.Keyword.Reserved: PYGMENTS_BOLD | PieColor.GREY,
|
||||
pygments.token.Operator: PYGMENTS_BOLD | PieColor.GREY,
|
||||
pygments.token.Number: PYGMENTS_BOLD | PieColor.GREY,
|
||||
pygments.token.Name.Function.Magic: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Name.Exception: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Name.Attribute: PieColor.BLUE,
|
||||
pygments.token.String: PieColor.PRIMARY,
|
||||
|
||||
# HTTP Methods
|
||||
pygments.token.Name.Function: 'bold grey',
|
||||
pygments.token.Name.Function.HTTP.GET: 'bold green',
|
||||
pygments.token.Name.Function.HTTP.HEAD: 'bold green',
|
||||
pygments.token.Name.Function.HTTP.POST: 'bold yellow',
|
||||
pygments.token.Name.Function.HTTP.PUT: 'bold orange',
|
||||
pygments.token.Name.Function.HTTP.PATCH: 'bold orange',
|
||||
pygments.token.Name.Function.HTTP.DELETE: 'bold red',
|
||||
pygments.token.Name.Function: PYGMENTS_BOLD | PieColor.GREY,
|
||||
pygments.token.Name.Function.HTTP.GET: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Name.Function.HTTP.HEAD: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Name.Function.HTTP.POST: PYGMENTS_BOLD | PieColor.YELLOW,
|
||||
pygments.token.Name.Function.HTTP.PUT: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||
pygments.token.Name.Function.HTTP.PATCH: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||
pygments.token.Name.Function.HTTP.DELETE: PYGMENTS_BOLD | PieColor.RED,
|
||||
|
||||
# HTTP status codes
|
||||
pygments.token.Number.HTTP.INFO: 'bold aqua',
|
||||
pygments.token.Number.HTTP.OK: 'bold green',
|
||||
pygments.token.Number.HTTP.REDIRECT: 'bold yellow',
|
||||
pygments.token.Number.HTTP.CLIENT_ERR: 'bold orange',
|
||||
pygments.token.Number.HTTP.SERVER_ERR: 'bold red',
|
||||
pygments.token.Number.HTTP.INFO: PYGMENTS_BOLD | PieColor.AQUA,
|
||||
pygments.token.Number.HTTP.OK: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Number.HTTP.REDIRECT: PYGMENTS_BOLD | PieColor.YELLOW,
|
||||
pygments.token.Number.HTTP.CLIENT_ERR: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||
pygments.token.Number.HTTP.SERVER_ERR: PYGMENTS_BOLD | PieColor.RED,
|
||||
|
||||
# Metadata
|
||||
pygments.token.Name.Decorator: 'grey',
|
||||
pygments.token.Number.SPEED.FAST: 'bold green',
|
||||
pygments.token.Number.SPEED.AVG: 'bold yellow',
|
||||
pygments.token.Number.SPEED.SLOW: 'bold orange',
|
||||
pygments.token.Number.SPEED.VERY_SLOW: 'bold red',
|
||||
pygments.token.Name.Decorator: PieColor.GREY,
|
||||
pygments.token.Number.SPEED.FAST: PYGMENTS_BOLD | PieColor.GREEN,
|
||||
pygments.token.Number.SPEED.AVG: PYGMENTS_BOLD | PieColor.YELLOW,
|
||||
pygments.token.Number.SPEED.SLOW: PYGMENTS_BOLD | PieColor.ORANGE,
|
||||
pygments.token.Number.SPEED.VERY_SLOW: PYGMENTS_BOLD | PieColor.RED,
|
||||
}
|
||||
|
||||
PIE_BODY_STYLE = {
|
||||
# {}[]:
|
||||
pygments.token.Punctuation: 'grey',
|
||||
pygments.token.Punctuation: PieColor.GREY,
|
||||
|
||||
# Keys
|
||||
pygments.token.Name.Tag: 'pink',
|
||||
pygments.token.Name.Tag: PieColor.PINK,
|
||||
|
||||
# Values
|
||||
pygments.token.Literal.String: 'green',
|
||||
pygments.token.Literal.String.Double: 'green',
|
||||
pygments.token.Literal.Number: 'aqua',
|
||||
pygments.token.Keyword: 'orange',
|
||||
pygments.token.Literal.String: PieColor.GREEN,
|
||||
pygments.token.Literal.String.Double: PieColor.GREEN,
|
||||
pygments.token.Literal.Number: PieColor.AQUA,
|
||||
pygments.token.Keyword: PieColor.ORANGE,
|
||||
|
||||
# Other stuff
|
||||
pygments.token.Text: 'primary',
|
||||
pygments.token.Name.Attribute: 'primary',
|
||||
pygments.token.Name.Builtin: 'blue',
|
||||
pygments.token.Name.Builtin.Pseudo: 'blue',
|
||||
pygments.token.Name.Class: 'blue',
|
||||
pygments.token.Name.Constant: 'orange',
|
||||
pygments.token.Name.Decorator: 'blue',
|
||||
pygments.token.Name.Entity: 'orange',
|
||||
pygments.token.Name.Exception: 'yellow',
|
||||
pygments.token.Name.Function: 'blue',
|
||||
pygments.token.Name.Variable: 'blue',
|
||||
pygments.token.String: 'aqua',
|
||||
pygments.token.String.Backtick: 'secondary',
|
||||
pygments.token.String.Char: 'aqua',
|
||||
pygments.token.String.Doc: 'aqua',
|
||||
pygments.token.String.Escape: 'red',
|
||||
pygments.token.String.Heredoc: 'aqua',
|
||||
pygments.token.String.Regex: 'red',
|
||||
pygments.token.Number: 'aqua',
|
||||
pygments.token.Operator: 'primary',
|
||||
pygments.token.Operator.Word: 'green',
|
||||
pygments.token.Comment: 'secondary',
|
||||
pygments.token.Comment.Preproc: 'green',
|
||||
pygments.token.Comment.Special: 'green',
|
||||
pygments.token.Generic.Deleted: 'aqua',
|
||||
pygments.token.Generic.Emph: 'italic',
|
||||
pygments.token.Generic.Error: 'red',
|
||||
pygments.token.Generic.Heading: 'orange',
|
||||
pygments.token.Generic.Inserted: 'green',
|
||||
pygments.token.Generic.Strong: 'bold',
|
||||
pygments.token.Generic.Subheading: 'orange',
|
||||
pygments.token.Token: 'primary',
|
||||
pygments.token.Token.Other: 'orange',
|
||||
pygments.token.Text: PieColor.PRIMARY,
|
||||
pygments.token.Name.Attribute: PieColor.PRIMARY,
|
||||
pygments.token.Name.Builtin: PieColor.BLUE,
|
||||
pygments.token.Name.Builtin.Pseudo: PieColor.BLUE,
|
||||
pygments.token.Name.Class: PieColor.BLUE,
|
||||
pygments.token.Name.Constant: PieColor.ORANGE,
|
||||
pygments.token.Name.Decorator: PieColor.BLUE,
|
||||
pygments.token.Name.Entity: PieColor.ORANGE,
|
||||
pygments.token.Name.Exception: PieColor.YELLOW,
|
||||
pygments.token.Name.Function: PieColor.BLUE,
|
||||
pygments.token.Name.Variable: PieColor.BLUE,
|
||||
pygments.token.String: PieColor.AQUA,
|
||||
pygments.token.String.Backtick: PieColor.SECONDARY,
|
||||
pygments.token.String.Char: PieColor.AQUA,
|
||||
pygments.token.String.Doc: PieColor.AQUA,
|
||||
pygments.token.String.Escape: PieColor.RED,
|
||||
pygments.token.String.Heredoc: PieColor.AQUA,
|
||||
pygments.token.String.Regex: PieColor.RED,
|
||||
pygments.token.Number: PieColor.AQUA,
|
||||
pygments.token.Operator: PieColor.PRIMARY,
|
||||
pygments.token.Operator.Word: PieColor.GREEN,
|
||||
pygments.token.Comment: PieColor.SECONDARY,
|
||||
pygments.token.Comment.Preproc: PieColor.GREEN,
|
||||
pygments.token.Comment.Special: PieColor.GREEN,
|
||||
pygments.token.Generic.Deleted: PieColor.AQUA,
|
||||
pygments.token.Generic.Emph: PYGMENTS_ITALIC,
|
||||
pygments.token.Generic.Error: PieColor.RED,
|
||||
pygments.token.Generic.Heading: PieColor.ORANGE,
|
||||
pygments.token.Generic.Inserted: PieColor.GREEN,
|
||||
pygments.token.Generic.Strong: PYGMENTS_BOLD,
|
||||
pygments.token.Generic.Subheading: PieColor.ORANGE,
|
||||
pygments.token.Token: PieColor.PRIMARY,
|
||||
pygments.token.Token.Other: PieColor.ORANGE,
|
||||
}
|
||||
|
||||
|
||||
@ -369,7 +371,7 @@ def make_style(name, raw_styles, shade):
|
||||
def make_styles():
|
||||
styles = {}
|
||||
|
||||
for shade, name in SHADE_NAMES.items():
|
||||
for shade, name in SHADE_TO_PIE_STYLE.items():
|
||||
styles[name] = [
|
||||
make_style(name, style_map, shade)
|
||||
for style_name, style_map in [
|
||||
|
@ -7,6 +7,13 @@ from httpie.context import Environment
|
||||
MAN_COMMAND = 'man'
|
||||
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:
|
||||
"""Check whether HTTPie's man pages are available in this system."""
|
||||
@ -14,20 +21,27 @@ def is_available(program: str) -> bool:
|
||||
if NO_MAN_PAGES or os.system == 'nt':
|
||||
return False
|
||||
|
||||
process = subprocess.run(
|
||||
[MAN_COMMAND, program],
|
||||
shell=False,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
return process.returncode == 0
|
||||
try:
|
||||
process = subprocess.run(
|
||||
[MAN_COMMAND, MAN_PAGE_SECTION, program],
|
||||
shell=False,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
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:
|
||||
"""Display the man page for the given command (http/https)."""
|
||||
|
||||
subprocess.run(
|
||||
[MAN_COMMAND, program],
|
||||
[MAN_COMMAND, MAN_PAGE_SECTION, program],
|
||||
stdout=env.stdout,
|
||||
stderr=env.stderr
|
||||
)
|
||||
|
@ -1,16 +1,118 @@
|
||||
from typing import Dict, Optional
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum, auto
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
PYGMENTS_BRIGHT_BLACK = 'ansibrightblack'
|
||||
|
||||
AUTO_STYLE = 'auto' # Follows terminal ANSI color styles
|
||||
STYLE_PIE = 'pie'
|
||||
STYLE_PIE_DARK = 'pie-dark'
|
||||
STYLE_PIE_LIGHT = 'pie-light'
|
||||
|
||||
|
||||
class Styles(Enum):
|
||||
PIE = auto()
|
||||
ANSI = auto()
|
||||
|
||||
|
||||
class PieStyle(str, Enum):
|
||||
UNIVERSAL = 'pie'
|
||||
DARK = 'pie-dark'
|
||||
LIGHT = 'pie-light'
|
||||
|
||||
|
||||
PIE_STYLE_TO_SHADE = {
|
||||
PieStyle.DARK: '500',
|
||||
PieStyle.UNIVERSAL: '600',
|
||||
PieStyle.LIGHT: '700',
|
||||
}
|
||||
SHADE_TO_PIE_STYLE = {
|
||||
shade: style for style, shade in PIE_STYLE_TO_SHADE.items()
|
||||
}
|
||||
|
||||
|
||||
class ColorString(str):
|
||||
def __or__(self, other: str) -> 'ColorString':
|
||||
"""Combine a style with a property.
|
||||
|
||||
E.g: PieColor.BLUE | BOLD | ITALIC
|
||||
"""
|
||||
if isinstance(other, str):
|
||||
# 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):
|
||||
"""Styles that are available only in Pie themes."""
|
||||
|
||||
PRIMARY = 'primary'
|
||||
SECONDARY = 'secondary'
|
||||
|
||||
WHITE = 'white'
|
||||
BLACK = 'black'
|
||||
GREY = 'grey'
|
||||
AQUA = 'aqua'
|
||||
PURPLE = 'purple'
|
||||
ORANGE = 'orange'
|
||||
RED = 'red'
|
||||
BLUE = 'blue'
|
||||
PINK = 'pink'
|
||||
GREEN = 'green'
|
||||
YELLOW = 'yellow'
|
||||
|
||||
|
||||
class GenericColor(Enum):
|
||||
"""Generic colors that are safe to use everywhere."""
|
||||
|
||||
# <https://rich.readthedocs.io/en/stable/appendix/colors.html>
|
||||
|
||||
WHITE = {Styles.PIE: PieColor.WHITE, Styles.ANSI: 'white'}
|
||||
BLACK = {Styles.PIE: PieColor.BLACK, Styles.ANSI: 'black'}
|
||||
GREEN = {Styles.PIE: PieColor.GREEN, Styles.ANSI: 'green'}
|
||||
ORANGE = {Styles.PIE: PieColor.ORANGE, Styles.ANSI: 'yellow'}
|
||||
YELLOW = {Styles.PIE: PieColor.YELLOW, Styles.ANSI: 'bright_yellow'}
|
||||
BLUE = {Styles.PIE: PieColor.BLUE, Styles.ANSI: 'blue'}
|
||||
PINK = {Styles.PIE: PieColor.PINK, Styles.ANSI: 'bright_magenta'}
|
||||
PURPLE = {Styles.PIE: PieColor.PURPLE, Styles.ANSI: 'magenta'}
|
||||
RED = {Styles.PIE: PieColor.RED, Styles.ANSI: 'red'}
|
||||
AQUA = {Styles.PIE: PieColor.AQUA, Styles.ANSI: 'cyan'}
|
||||
GREY = {Styles.PIE: PieColor.GREY, Styles.ANSI: 'bright_black'}
|
||||
|
||||
def apply_style(
|
||||
self, style: Styles, *, style_name: Optional[str] = None
|
||||
) -> str:
|
||||
"""Apply the given style to a particular value."""
|
||||
exposed_color = self.value[style]
|
||||
if style is Styles.PIE:
|
||||
assert style_name is not None
|
||||
shade = PIE_STYLE_TO_SHADE[PieStyle(style_name)]
|
||||
return get_color(exposed_color, shade)
|
||||
else:
|
||||
return exposed_color
|
||||
|
||||
|
||||
@dataclass
|
||||
class _StyledGenericColor:
|
||||
color: 'GenericColor'
|
||||
styles: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
# noinspection PyDictCreation
|
||||
COLOR_PALETTE = {
|
||||
# Copy the brand palette
|
||||
'white': '#F5F5F0',
|
||||
'black': '#1C1818',
|
||||
'grey': {
|
||||
PieColor.WHITE: '#F5F5F0',
|
||||
PieColor.BLACK: '#1C1818',
|
||||
PieColor.GREY: {
|
||||
'50': '#F5F5F0',
|
||||
'100': '#EDEDEB',
|
||||
'200': '#D1D1CF',
|
||||
@ -23,7 +125,7 @@ COLOR_PALETTE = {
|
||||
'900': '#1C1818',
|
||||
'DEFAULT': '#7D7D7D',
|
||||
},
|
||||
'aqua': {
|
||||
PieColor.AQUA: {
|
||||
'50': '#E8F0F5',
|
||||
'100': '#D6E3ED',
|
||||
'200': '#C4D9E5',
|
||||
@ -36,7 +138,7 @@ COLOR_PALETTE = {
|
||||
'900': '#455966',
|
||||
'DEFAULT': '#8CB4CD',
|
||||
},
|
||||
'purple': {
|
||||
PieColor.PURPLE: {
|
||||
'50': '#F0E0FC',
|
||||
'100': '#E3C7FA',
|
||||
'200': '#D9ADF7',
|
||||
@ -49,7 +151,7 @@ COLOR_PALETTE = {
|
||||
'900': '#5C2982',
|
||||
'DEFAULT': '#B464F0',
|
||||
},
|
||||
'orange': {
|
||||
PieColor.ORANGE: {
|
||||
'50': '#FFEDDB',
|
||||
'100': '#FFDEBF',
|
||||
'200': '#FFCFA3',
|
||||
@ -62,7 +164,7 @@ COLOR_PALETTE = {
|
||||
'900': '#C75E0A',
|
||||
'DEFAULT': '#FFA24E',
|
||||
},
|
||||
'red': {
|
||||
PieColor.RED: {
|
||||
'50': '#FFE0DE',
|
||||
'100': '#FFC7C4',
|
||||
'200': '#FFB0AB',
|
||||
@ -75,7 +177,7 @@ COLOR_PALETTE = {
|
||||
'900': '#910A00',
|
||||
'DEFAULT': '#FF665B',
|
||||
},
|
||||
'blue': {
|
||||
PieColor.BLUE: {
|
||||
'50': '#DBE3FA',
|
||||
'100': '#BFCFF5',
|
||||
'200': '#A1B8F2',
|
||||
@ -88,7 +190,7 @@ COLOR_PALETTE = {
|
||||
'900': '#2B478F',
|
||||
'DEFAULT': '#4B78E6',
|
||||
},
|
||||
'pink': {
|
||||
PieColor.PINK: {
|
||||
'50': '#FFEBFF',
|
||||
'100': '#FCDBFC',
|
||||
'200': '#FCCCFC',
|
||||
@ -101,7 +203,7 @@ COLOR_PALETTE = {
|
||||
'900': '#8C3D8A',
|
||||
'DEFAULT': '#FA9BFA',
|
||||
},
|
||||
'green': {
|
||||
PieColor.GREEN: {
|
||||
'50': '#E3F7E8',
|
||||
'100': '#CCF2D6',
|
||||
'200': '#B5EDC4',
|
||||
@ -114,7 +216,7 @@ COLOR_PALETTE = {
|
||||
'900': '#307842',
|
||||
'DEFAULT': '#73DC8C',
|
||||
},
|
||||
'yellow': {
|
||||
PieColor.YELLOW: {
|
||||
'50': '#F7F7DB',
|
||||
'100': '#F2F2BF',
|
||||
'200': '#EDEDA6',
|
||||
@ -128,47 +230,38 @@ COLOR_PALETTE = {
|
||||
'DEFAULT': '#DBDE52',
|
||||
},
|
||||
}
|
||||
|
||||
# Grey is the same no matter shade for the colors
|
||||
COLOR_PALETTE['grey'] = {
|
||||
shade: COLOR_PALETTE['grey']['500'] for shade in COLOR_PALETTE['grey'].keys()
|
||||
}
|
||||
|
||||
COLOR_PALETTE['primary'] = {
|
||||
'700': COLOR_PALETTE['black'],
|
||||
'600': 'ansibrightblack',
|
||||
'500': COLOR_PALETTE['white'],
|
||||
}
|
||||
|
||||
COLOR_PALETTE['secondary'] = {'700': '#37523C', '600': '#6c6969', '500': '#6c6969'}
|
||||
COLOR_PALETTE.update(
|
||||
{
|
||||
# Terminal-specific palette customizations.
|
||||
PieColor.GREY: {
|
||||
# Grey is the same no matter shade for the colors
|
||||
shade: COLOR_PALETTE[PieColor.GREY]['500']
|
||||
for shade in COLOR_PALETTE[PieColor.GREY].keys()
|
||||
},
|
||||
PieColor.PRIMARY: {
|
||||
'700': COLOR_PALETTE[PieColor.BLACK],
|
||||
'600': PYGMENTS_BRIGHT_BLACK,
|
||||
'500': COLOR_PALETTE[PieColor.WHITE],
|
||||
},
|
||||
PieColor.SECONDARY: {
|
||||
'700': '#37523C',
|
||||
'600': '#6c6969',
|
||||
'500': '#6c6969',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
SHADE_NAMES = {
|
||||
'500': STYLE_PIE_DARK,
|
||||
'600': STYLE_PIE,
|
||||
'700': STYLE_PIE_LIGHT
|
||||
}
|
||||
|
||||
STYLE_SHADES = {
|
||||
style: shade
|
||||
for shade, style in SHADE_NAMES.items()
|
||||
}
|
||||
|
||||
SHADES = [
|
||||
'50',
|
||||
*map(str, range(100, 1000, 100))
|
||||
]
|
||||
def boldify(color: PieColor) -> str:
|
||||
return f'bold {color}'
|
||||
|
||||
|
||||
# noinspection PyDefaultArgument
|
||||
def get_color(
|
||||
color: str,
|
||||
shade: str,
|
||||
*,
|
||||
palette: Dict[str, Dict[str, str]] = COLOR_PALETTE
|
||||
color: PieColor, shade: str, *, palette=COLOR_PALETTE
|
||||
) -> Optional[str]:
|
||||
if color not in palette:
|
||||
return None
|
||||
|
||||
color_code = palette[color]
|
||||
if isinstance(color_code, dict) and shade in color_code:
|
||||
return color_code[shade]
|
||||
|
@ -10,20 +10,23 @@ from rich.text import Text
|
||||
|
||||
from httpie.cli.constants import SEPARATOR_GROUP_ALL_ITEMS
|
||||
from httpie.cli.options import Argument, ParserSpec, Qualifiers
|
||||
from httpie.output.ui.palette import GenericColor
|
||||
|
||||
SEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS))
|
||||
|
||||
STYLE_METAVAR = 'yellow'
|
||||
STYLE_SWITCH = 'green'
|
||||
STYLE_PROGRAM_NAME = 'bold green'
|
||||
STYLE_USAGE_OPTIONAL = 'grey46'
|
||||
STYLE_USAGE_REGULAR = 'white'
|
||||
STYLE_USAGE_ERROR = 'red'
|
||||
STYLE_USAGE_MISSING = 'yellow'
|
||||
STYLE_METAVAR = GenericColor.YELLOW
|
||||
STYLE_SWITCH = GenericColor.GREEN
|
||||
STYLE_PROGRAM_NAME = GenericColor.GREEN # .boldify()
|
||||
STYLE_USAGE_OPTIONAL = GenericColor.GREY
|
||||
STYLE_USAGE_REGULAR = GenericColor.WHITE
|
||||
STYLE_USAGE_ERROR = GenericColor.RED
|
||||
STYLE_USAGE_MISSING = GenericColor.YELLOW
|
||||
STYLE_BOLD = 'bold'
|
||||
|
||||
MAX_CHOICE_CHARS = 80
|
||||
|
||||
LEFT_PADDING_2 = (0, 0, 0, 2)
|
||||
LEFT_PADDING_3 = (0, 0, 0, 3)
|
||||
LEFT_PADDING_4 = (0, 0, 0, 4)
|
||||
LEFT_PADDING_5 = (0, 0, 0, 4)
|
||||
|
||||
@ -31,6 +34,12 @@ LEFT_INDENT_2 = (1, 0, 0, 2)
|
||||
LEFT_INDENT_3 = (1, 0, 0, 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):
|
||||
highlights = [
|
||||
@ -77,7 +86,7 @@ def to_usage(
|
||||
# shown first
|
||||
shown_arguments.sort(key=lambda argument: argument.aliases, reverse=True)
|
||||
|
||||
text = Text(program_name or spec.program, style='bold')
|
||||
text = Text(program_name or spec.program, style=STYLE_BOLD)
|
||||
for argument in shown_arguments:
|
||||
text.append(' ')
|
||||
|
||||
@ -211,6 +220,10 @@ def to_help_message(
|
||||
Text('More Information', style=STYLE_SWITCH),
|
||||
LEFT_INDENT_2,
|
||||
)
|
||||
yield Padding(
|
||||
MORE_INFO_COMMANDS.rstrip('\n'),
|
||||
LEFT_PADDING_3
|
||||
)
|
||||
yield Padding(
|
||||
spec.epilog.rstrip('\n'),
|
||||
LEFT_INDENT_BOTTOM_3,
|
||||
|
@ -1,23 +1,73 @@
|
||||
from httpie.output.ui.palette import * # noqa
|
||||
from collections import ChainMap
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from rich.theme import Theme
|
||||
|
||||
from httpie.output.ui.palette import GenericColor, PieStyle, Styles, ColorString, _StyledGenericColor # noqa
|
||||
|
||||
RICH_BOLD = ColorString('bold')
|
||||
|
||||
# Rich-specific color code declarations
|
||||
# https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py#L5
|
||||
# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>
|
||||
CUSTOM_STYLES = {
|
||||
'progress.description': 'white',
|
||||
'progress.data.speed': 'green',
|
||||
'progress.percentage': 'aqua',
|
||||
'progress.download': 'aqua',
|
||||
'progress.remaining': 'orange',
|
||||
'bar.complete': 'purple',
|
||||
'bar.finished': 'green',
|
||||
'bar.pulse': 'purple',
|
||||
'option': 'pink'
|
||||
'progress.description': RICH_BOLD | GenericColor.WHITE,
|
||||
'progress.data.speed': RICH_BOLD | GenericColor.GREEN,
|
||||
'progress.percentage': RICH_BOLD | GenericColor.AQUA,
|
||||
'progress.download': RICH_BOLD | GenericColor.AQUA,
|
||||
'progress.remaining': RICH_BOLD | GenericColor.ORANGE,
|
||||
'bar.complete': RICH_BOLD | GenericColor.PURPLE,
|
||||
'bar.finished': RICH_BOLD | GenericColor.GREEN,
|
||||
'bar.pulse': RICH_BOLD | GenericColor.PURPLE,
|
||||
'option': RICH_BOLD | GenericColor.PINK,
|
||||
}
|
||||
|
||||
RICH_THEME_PALETTE = COLOR_PALETTE.copy() # noqa
|
||||
RICH_THEME_PALETTE.update(
|
||||
{
|
||||
custom_style: RICH_THEME_PALETTE[color]
|
||||
for custom_style, color in CUSTOM_STYLES.items()
|
||||
}
|
||||
)
|
||||
|
||||
class _GenericColorCaster(dict):
|
||||
"""
|
||||
Translate GenericColor to a regular string on the attribute access
|
||||
phase.
|
||||
"""
|
||||
|
||||
def _translate(self, key: Any) -> Any:
|
||||
if isinstance(key, GenericColor):
|
||||
return key.name.lower()
|
||||
else:
|
||||
return key
|
||||
|
||||
def __getitem__(self, key: Any) -> Any:
|
||||
return super().__getitem__(self._translate(key))
|
||||
|
||||
def get(self, key: Any) -> Any:
|
||||
return super().get(self._translate(key))
|
||||
|
||||
|
||||
def _make_rich_color_theme(style_name: Optional[str] = None) -> 'Theme':
|
||||
from rich.style import Style
|
||||
from rich.theme import Theme
|
||||
|
||||
try:
|
||||
PieStyle(style_name)
|
||||
except ValueError:
|
||||
style = Styles.ANSI
|
||||
else:
|
||||
style = Styles.PIE
|
||||
|
||||
theme = Theme()
|
||||
for color, color_set in ChainMap(
|
||||
GenericColor.__members__, CUSTOM_STYLES
|
||||
).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(
|
||||
color=color_set.apply_style(style, style_name=style_name),
|
||||
**properties,
|
||||
)
|
||||
|
||||
# E.g translate GenericColor.BLUE into blue on key access
|
||||
theme.styles = _GenericColorCaster(theme.styles)
|
||||
return theme
|
||||
|
@ -28,10 +28,7 @@ class BaseDisplay:
|
||||
return self.env.rich_error_console
|
||||
|
||||
def _print_summary(
|
||||
self,
|
||||
is_finished: bool,
|
||||
observed_steps: int,
|
||||
time_spent: float
|
||||
self, is_finished: bool, observed_steps: int, time_spent: float
|
||||
):
|
||||
from rich import filesize
|
||||
|
||||
@ -50,7 +47,9 @@ class BaseDisplay:
|
||||
else:
|
||||
total_time = f'{minutes:02d}:{seconds:0.5f}'
|
||||
|
||||
self.console.print(f'[progress.description]{verb}. {total_size} in {total_time} ({avg_speed}/s)')
|
||||
self.console.print(
|
||||
f'[progress.description]{verb}. {total_size} in {total_time} ({avg_speed}/s)'
|
||||
)
|
||||
|
||||
|
||||
class DummyDisplay(BaseDisplay):
|
||||
@ -65,7 +64,9 @@ class StatusDisplay(BaseDisplay):
|
||||
self, *, total: Optional[float], at: float, description: str
|
||||
) -> None:
|
||||
self.observed = at
|
||||
self.description = f'[progress.description]{description}[/progress.description]'
|
||||
self.description = (
|
||||
f'[progress.description]{description}[/progress.description]'
|
||||
)
|
||||
|
||||
self.status = self.console.status(self.description, spinner='line')
|
||||
self.status.start()
|
||||
@ -75,8 +76,12 @@ class StatusDisplay(BaseDisplay):
|
||||
|
||||
self.observed += steps
|
||||
|
||||
observed_amount, observed_unit = filesize.decimal(self.observed).split()
|
||||
self.status.update(status=f'{self.description} [progress.download]{observed_amount}/? {observed_unit}[/progress.download]')
|
||||
observed_amount, observed_unit = filesize.decimal(
|
||||
self.observed
|
||||
).split()
|
||||
self.status.update(
|
||||
status=f'{self.description} [progress.download]{observed_amount}/? {observed_unit}[/progress.download]'
|
||||
)
|
||||
|
||||
def stop(self, time_spent: float) -> None:
|
||||
self.status.stop()
|
||||
@ -85,7 +90,7 @@ class StatusDisplay(BaseDisplay):
|
||||
self._print_summary(
|
||||
is_finished=True,
|
||||
observed_steps=self.observed,
|
||||
time_spent=time_spent
|
||||
time_spent=time_spent,
|
||||
)
|
||||
|
||||
|
||||
@ -114,7 +119,7 @@ class ProgressDisplay(BaseDisplay):
|
||||
TimeRemainingColumn(),
|
||||
TransferSpeedColumn(),
|
||||
console=self.console,
|
||||
transient=True
|
||||
transient=True,
|
||||
)
|
||||
self.progress_bar.start()
|
||||
self.transfer_task = self.progress_bar.add_task(
|
||||
@ -132,5 +137,5 @@ class ProgressDisplay(BaseDisplay):
|
||||
self._print_summary(
|
||||
is_finished=task.finished,
|
||||
observed_steps=task.completed,
|
||||
time_spent=time_spent
|
||||
time_spent=time_spent,
|
||||
)
|
||||
|
@ -6,16 +6,15 @@ from contextlib import contextmanager
|
||||
from rich.console import Console, RenderableType
|
||||
from rich.highlighter import Highlighter
|
||||
|
||||
from httpie.output.ui.rich_palette import _make_rich_color_theme
|
||||
|
||||
|
||||
def render_as_string(renderable: RenderableType) -> str:
|
||||
"""Render any `rich` object in a fake console and
|
||||
return a *style-less* version of it as a string."""
|
||||
|
||||
with open(os.devnull, "w") as null_stream:
|
||||
fake_console = Console(
|
||||
file=null_stream,
|
||||
record=True
|
||||
)
|
||||
with open(os.devnull, 'w') as null_stream:
|
||||
fake_console = Console(file=null_stream, record=True, theme=_make_rich_color_theme())
|
||||
fake_console.print(renderable)
|
||||
return fake_console.export_text()
|
||||
|
||||
@ -25,7 +24,7 @@ def enable_highlighter(
|
||||
console: Console,
|
||||
highlighter: Highlighter,
|
||||
) -> Iterator[Console]:
|
||||
"""Enable a higlighter temporarily."""
|
||||
"""Enable a highlighter temporarily."""
|
||||
|
||||
original_highlighter = console.highlighter
|
||||
try:
|
||||
|
@ -17,6 +17,7 @@ from .processing import Conversion, Formatting
|
||||
from .streams import (
|
||||
BaseStream, BufferedPrettyStream, EncodedStream, PrettyStream, RawStream,
|
||||
)
|
||||
from ..utils import parse_content_type_header
|
||||
|
||||
|
||||
MESSAGE_SEPARATOR = '\n\n'
|
||||
@ -163,7 +164,10 @@ def get_stream_type_and_kwargs(
|
||||
if not is_stream and message_type is HTTPResponse:
|
||||
# If this is a response, then check the headers for determining
|
||||
# auto-streaming.
|
||||
is_stream = headers.get('Content-Type') == 'text/event-stream'
|
||||
raw_content_type_header = headers.get('Content-Type', None)
|
||||
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:
|
||||
stream_class = RawStream
|
||||
|
@ -13,7 +13,7 @@ from typing import Any, Dict, List, Optional, Union
|
||||
from requests.auth import AuthBase
|
||||
from requests.cookies import RequestsCookieJar, remove_cookie_by_name
|
||||
|
||||
from .context import Environment, Levels
|
||||
from .context import Environment, LogLevel
|
||||
from .cookies import HTTPieCookiePolicy
|
||||
from .cli.dicts import HTTPHeadersDict
|
||||
from .config import BaseConfigDict, DEFAULT_CONFIG_DIR
|
||||
@ -313,7 +313,7 @@ class Session(BaseConfigDict):
|
||||
|
||||
self.env.log_error(
|
||||
warning,
|
||||
level=Levels.WARNING
|
||||
level=LogLevel.WARNING
|
||||
)
|
||||
|
||||
# We don't want to spam multiple warnings on each usage,
|
||||
|
@ -1,16 +1,20 @@
|
||||
import os
|
||||
import base64
|
||||
import json
|
||||
import mimetypes
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import tempfile
|
||||
import sysconfig
|
||||
|
||||
from collections import OrderedDict
|
||||
from contextlib import contextmanager
|
||||
from http.cookiejar import parse_ns_headers
|
||||
from pathlib import Path
|
||||
from pprint import pformat
|
||||
from urllib.parse import urlsplit
|
||||
from typing import Any, List, Optional, Tuple, Callable, Iterable, TypeVar
|
||||
from typing import Any, List, Optional, Tuple, Generator, Callable, Iterable, IO, TypeVar
|
||||
|
||||
import requests.auth
|
||||
|
||||
@ -261,3 +265,45 @@ def unwrap_context(exc: Exception) -> Optional[Exception]:
|
||||
|
||||
def url_as_host(url: str) -> str:
|
||||
return urlsplit(url).netloc.split('@')[-1]
|
||||
|
||||
|
||||
class LockFileError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def open_with_lockfile(file: Path, *args, **kwargs) -> Generator[IO[Any], None, None]:
|
||||
file_id = base64.b64encode(os.fsencode(file)).decode()
|
||||
target_file = Path(tempfile.gettempdir()) / file_id
|
||||
|
||||
# Have an atomic-like touch here, so we'll tighten the possibility of
|
||||
# a race occurring between multiple processes accessing the same file.
|
||||
try:
|
||||
target_file.touch(exist_ok=False)
|
||||
except FileExistsError as exc:
|
||||
raise LockFileError("Can't modify a locked file.") from exc
|
||||
|
||||
try:
|
||||
with open(file, *args, **kwargs) as stream:
|
||||
yield stream
|
||||
finally:
|
||||
target_file.unlink()
|
||||
|
||||
|
||||
def is_version_greater(version_1: str, version_2: str) -> bool:
|
||||
# In an ideal scenario, we would depend on `packaging` in order
|
||||
# to offer PEP 440 compatible parsing. But since it might not be
|
||||
# commonly available for outside packages, and since we are only
|
||||
# going to parse HTTPie's own version it should be fine to compare
|
||||
# this in a SemVer subset fashion.
|
||||
|
||||
def split_version(version: str) -> Tuple[int, ...]:
|
||||
parts = []
|
||||
for part in version.split('.')[:3]:
|
||||
try:
|
||||
parts.append(int(part))
|
||||
except ValueError:
|
||||
break
|
||||
return tuple(parts)
|
||||
|
||||
return split_version(version_1) > split_version(version_2)
|
||||
|
2
setup.py
2
setup.py
@ -13,6 +13,7 @@ tests_require = [
|
||||
'pytest-httpbin>=0.0.6',
|
||||
'pytest-lazy-fixture>=0.0.6',
|
||||
'responses',
|
||||
'pytest-mock',
|
||||
'werkzeug<2.1.0'
|
||||
]
|
||||
dev_require = [
|
||||
@ -116,5 +117,6 @@ setup(
|
||||
data_files=[
|
||||
('share/man/man1', ['extras/man/http.1']),
|
||||
('share/man/man1', ['extras/man/https.1']),
|
||||
('share/man/man1', ['extras/man/httpie.1']),
|
||||
]
|
||||
)
|
||||
|
@ -1,4 +1,3 @@
|
||||
import os
|
||||
import socket
|
||||
|
||||
import pytest
|
||||
@ -8,6 +7,7 @@ from .utils import ( # noqa
|
||||
HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN,
|
||||
HTTPBIN_WITH_CHUNKED_SUPPORT,
|
||||
REMOTE_HTTPBIN_DOMAIN,
|
||||
IS_PYOPENSSL,
|
||||
mock_env
|
||||
)
|
||||
from .utils.plugins_cli import ( # noqa
|
||||
@ -81,7 +81,7 @@ def pyopenssl_inject():
|
||||
Injects `pyOpenSSL` module to make sure `requests` will use it.
|
||||
<https://github.com/psf/requests/pull/5443#issuecomment-645740394>
|
||||
"""
|
||||
if os.getenv('HTTPIE_TEST_WITH_PYOPENSSL', '0') == '1':
|
||||
if IS_PYOPENSSL:
|
||||
try:
|
||||
import urllib3.contrib.pyopenssl
|
||||
urllib3.contrib.pyopenssl.inject_into_urllib3()
|
||||
|
@ -1,4 +1,3 @@
|
||||
import os
|
||||
import ssl
|
||||
|
||||
import pytest
|
||||
@ -11,7 +10,7 @@ from unittest import mock
|
||||
from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS
|
||||
from httpie.status import ExitStatus
|
||||
|
||||
from .utils import HTTP_OK, TESTS_ROOT, http
|
||||
from .utils import HTTP_OK, TESTS_ROOT, IS_PYOPENSSL, http
|
||||
|
||||
|
||||
try:
|
||||
@ -152,6 +151,7 @@ def test_ciphers(httpbin_secure):
|
||||
assert HTTP_OK in r
|
||||
|
||||
|
||||
@pytest.mark.skipif(IS_PYOPENSSL, reason='pyOpenSSL uses a different message format.')
|
||||
def test_ciphers_none_can_be_selected(httpbin_secure):
|
||||
r = http(
|
||||
httpbin_secure.url + '/get',
|
||||
@ -169,8 +169,7 @@ def test_ciphers_none_can_be_selected(httpbin_secure):
|
||||
|
||||
|
||||
def test_pyopenssl_presence():
|
||||
using_pyopenssl = os.getenv('HTTPIE_TEST_WITH_PYOPENSSL', '0')
|
||||
if using_pyopenssl == '0':
|
||||
if not IS_PYOPENSSL:
|
||||
assert not urllib3.util.ssl_.IS_PYOPENSSL
|
||||
assert not urllib3.util.IS_PYOPENSSL
|
||||
else:
|
||||
|
@ -124,6 +124,10 @@ def test_redirected_stream(httpbin):
|
||||
['Accept:text/event-stream'],
|
||||
3
|
||||
),
|
||||
(
|
||||
['Accept:text/event-stream; charset=utf-8'],
|
||||
3
|
||||
),
|
||||
(
|
||||
['Accept:text/plain'],
|
||||
1
|
||||
|
237
tests/test_update_warnings.py
Normal file
237
tests/test_update_warnings.py
Normal file
@ -0,0 +1,237 @@
|
||||
import json
|
||||
import tempfile
|
||||
import time
|
||||
from contextlib import suppress
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from httpie.internal.daemon_runner import STATUS_FILE
|
||||
from httpie.internal.daemons import spawn_daemon
|
||||
from httpie.status import ExitStatus
|
||||
|
||||
from .utils import PersistentMockEnvironment, http, httpie
|
||||
|
||||
BUILD_CHANNEL = 'test'
|
||||
BUILD_CHANNEL_2 = 'test2'
|
||||
UNKNOWN_BUILD_CHANNEL = 'test3'
|
||||
|
||||
HIGHEST_VERSION = '999.999.999'
|
||||
LOWEST_VERSION = '1.1.1'
|
||||
|
||||
FIXED_DATE = datetime(1970, 1, 1).isoformat()
|
||||
|
||||
MAX_ATTEMPT = 40
|
||||
MAX_TIMEOUT = 2.0
|
||||
|
||||
|
||||
def check_update_warnings(text):
|
||||
return 'A new HTTPie release' in text
|
||||
|
||||
|
||||
@pytest.mark.requires_external_processes
|
||||
def test_daemon_runner():
|
||||
# We have a pseudo daemon task called 'check_status'
|
||||
# which creates a temp file called STATUS_FILE under
|
||||
# user's temp directory. This test simply ensures that
|
||||
# we create a daemon that successfully performs the
|
||||
# external task.
|
||||
|
||||
status_file = Path(tempfile.gettempdir()) / STATUS_FILE
|
||||
with suppress(FileNotFoundError):
|
||||
status_file.unlink()
|
||||
|
||||
spawn_daemon('check_status')
|
||||
|
||||
for attempt in range(MAX_ATTEMPT):
|
||||
time.sleep(MAX_TIMEOUT / MAX_ATTEMPT)
|
||||
if status_file.exists():
|
||||
break
|
||||
else:
|
||||
pytest.fail(
|
||||
'Maximum number of attempts failed for daemon status check.'
|
||||
)
|
||||
|
||||
assert status_file.exists()
|
||||
|
||||
|
||||
def test_fetch(static_fetch_data, without_warnings):
|
||||
http('fetch_updates', '--daemon', env=without_warnings)
|
||||
|
||||
with open(without_warnings.config.version_info_file) as stream:
|
||||
version_data = json.load(stream)
|
||||
|
||||
assert version_data['last_warned_date'] is None
|
||||
assert version_data['last_fetched_date'] is not None
|
||||
assert (
|
||||
version_data['last_released_versions'][BUILD_CHANNEL]
|
||||
== HIGHEST_VERSION
|
||||
)
|
||||
assert (
|
||||
version_data['last_released_versions'][BUILD_CHANNEL_2]
|
||||
== LOWEST_VERSION
|
||||
)
|
||||
|
||||
|
||||
def test_fetch_dont_override_existing_layout(
|
||||
static_fetch_data, without_warnings
|
||||
):
|
||||
with open(without_warnings.config.version_info_file, 'w') as stream:
|
||||
existing_layout = {
|
||||
'last_warned_date': FIXED_DATE,
|
||||
'last_fetched_date': FIXED_DATE,
|
||||
'last_released_versions': {BUILD_CHANNEL: LOWEST_VERSION},
|
||||
}
|
||||
json.dump(existing_layout, stream)
|
||||
|
||||
http('fetch_updates', '--daemon', env=without_warnings)
|
||||
|
||||
with open(without_warnings.config.version_info_file) as stream:
|
||||
version_data = json.load(stream)
|
||||
|
||||
# The "last updated at" field should not be modified, but the
|
||||
# rest need to be updated.
|
||||
assert version_data['last_warned_date'] == FIXED_DATE
|
||||
assert version_data['last_fetched_date'] != FIXED_DATE
|
||||
assert (
|
||||
version_data['last_released_versions'][BUILD_CHANNEL]
|
||||
== HIGHEST_VERSION
|
||||
)
|
||||
|
||||
|
||||
def test_fetch_broken_json(static_fetch_data, without_warnings):
|
||||
with open(without_warnings.config.version_info_file, 'w') as stream:
|
||||
stream.write('$$broken$$')
|
||||
|
||||
http('fetch_updates', '--daemon', env=without_warnings)
|
||||
|
||||
with open(without_warnings.config.version_info_file) as stream:
|
||||
version_data = json.load(stream)
|
||||
|
||||
assert (
|
||||
version_data['last_released_versions'][BUILD_CHANNEL]
|
||||
== HIGHEST_VERSION
|
||||
)
|
||||
|
||||
|
||||
def test_check_updates_disable_warnings(
|
||||
without_warnings, httpbin, fetch_update_mock
|
||||
):
|
||||
r = http(httpbin + '/get', env=without_warnings)
|
||||
assert not fetch_update_mock.called
|
||||
assert not check_update_warnings(r.stderr)
|
||||
|
||||
|
||||
def test_check_updates_first_invocation(
|
||||
with_warnings, httpbin, fetch_update_mock
|
||||
):
|
||||
r = http(httpbin + '/get', env=with_warnings)
|
||||
assert fetch_update_mock.called
|
||||
assert not check_update_warnings(r.stderr)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'should_issue_warning, build_channel',
|
||||
[
|
||||
(False, pytest.lazy_fixture('lower_build_channel')),
|
||||
(True, pytest.lazy_fixture('higher_build_channel')),
|
||||
],
|
||||
)
|
||||
def test_check_updates_first_time_after_data_fetch(
|
||||
with_warnings,
|
||||
httpbin,
|
||||
fetch_update_mock,
|
||||
static_fetch_data,
|
||||
should_issue_warning,
|
||||
build_channel,
|
||||
):
|
||||
http('fetch_updates', '--daemon', env=with_warnings)
|
||||
r = http(httpbin + '/get', env=with_warnings)
|
||||
|
||||
assert not fetch_update_mock.called
|
||||
assert (not should_issue_warning) or check_update_warnings(r.stderr)
|
||||
|
||||
|
||||
def test_check_updates_first_time_after_data_fetch_unknown_build_channel(
|
||||
with_warnings,
|
||||
httpbin,
|
||||
fetch_update_mock,
|
||||
static_fetch_data,
|
||||
unknown_build_channel,
|
||||
):
|
||||
http('fetch_updates', '--daemon', env=with_warnings)
|
||||
r = http(httpbin + '/get', env=with_warnings)
|
||||
|
||||
assert not fetch_update_mock.called
|
||||
assert not check_update_warnings(r.stderr)
|
||||
|
||||
|
||||
def test_cli_check_updates(
|
||||
static_fetch_data, higher_build_channel
|
||||
):
|
||||
r = httpie('cli', 'check-updates')
|
||||
assert r.exit_status == ExitStatus.SUCCESS
|
||||
assert check_update_warnings(r)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"build_channel", [
|
||||
pytest.lazy_fixture("lower_build_channel"),
|
||||
pytest.lazy_fixture("unknown_build_channel")
|
||||
]
|
||||
)
|
||||
def test_cli_check_updates_not_shown(
|
||||
static_fetch_data, build_channel
|
||||
):
|
||||
r = httpie('cli', 'check-updates')
|
||||
assert r.exit_status == ExitStatus.SUCCESS
|
||||
assert not check_update_warnings(r)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def with_warnings(tmp_path):
|
||||
env = PersistentMockEnvironment()
|
||||
env.config['version_info_file'] = tmp_path / 'version.json'
|
||||
env.config['disable_update_warnings'] = False
|
||||
return env
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def without_warnings(tmp_path):
|
||||
env = PersistentMockEnvironment()
|
||||
env.config['version_info_file'] = tmp_path / 'version.json'
|
||||
env.config['disable_update_warnings'] = True
|
||||
return env
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fetch_update_mock(mocker):
|
||||
mock_fetch = mocker.patch('httpie.internal.update_warnings.fetch_updates')
|
||||
return mock_fetch
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def static_fetch_data(mocker):
|
||||
mock_get = mocker.patch('requests.get')
|
||||
mock_get.return_value.status_code = 200
|
||||
mock_get.return_value.json.return_value = {
|
||||
BUILD_CHANNEL: HIGHEST_VERSION,
|
||||
BUILD_CHANNEL_2: LOWEST_VERSION,
|
||||
}
|
||||
return mock_get
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def unknown_build_channel(mocker):
|
||||
mocker.patch('httpie.internal.update_warnings.BUILD_CHANNEL', UNKNOWN_BUILD_CHANNEL)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def higher_build_channel(mocker):
|
||||
mocker.patch('httpie.internal.update_warnings.BUILD_CHANNEL', BUILD_CHANNEL)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lower_build_channel(mocker):
|
||||
mocker.patch('httpie.internal.update_warnings.BUILD_CHANNEL', BUILD_CHANNEL_2)
|
@ -18,7 +18,7 @@ from .utils import (
|
||||
)
|
||||
from .fixtures import FILE_PATH_ARG, FILE_PATH, FILE_CONTENT
|
||||
|
||||
MAX_RESPONSE_WAIT_TIME = 2
|
||||
MAX_RESPONSE_WAIT_TIME = 5
|
||||
|
||||
|
||||
def test_chunked_json(httpbin_with_chunked_support):
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Utilities for HTTPie test suite."""
|
||||
import re
|
||||
import shlex
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
@ -30,6 +31,7 @@ REMOTE_HTTPBIN_DOMAIN = 'pie.dev'
|
||||
HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN = 'pie.dev'
|
||||
HTTPBIN_WITH_CHUNKED_SUPPORT = 'http://' + HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN
|
||||
|
||||
IS_PYOPENSSL = os.getenv('HTTPIE_TEST_WITH_PYOPENSSL', '0') == '1'
|
||||
|
||||
TESTS_ROOT = Path(__file__).parent.parent
|
||||
CRLF = '\r\n'
|
||||
@ -47,6 +49,10 @@ HTTP_OK_COLOR = (
|
||||
DUMMY_URL = 'http://this-should.never-resolve' # Note: URL never fetched
|
||||
DUMMY_HOST = url_as_host(DUMMY_URL)
|
||||
|
||||
# We don't want hundreds of subprocesses trying to access GitHub API
|
||||
# during the tests.
|
||||
Config.DEFAULTS['disable_update_warnings'] = True
|
||||
|
||||
|
||||
def strip_colors(colorized_msg: str) -> str:
|
||||
return COLOR_RE.sub('', colorized_msg)
|
||||
@ -161,6 +167,7 @@ class MockEnvironment(Environment):
|
||||
self._delete_config_dir = True
|
||||
|
||||
def cleanup(self):
|
||||
self.devnull.close()
|
||||
self.stdout.close()
|
||||
self.stderr.close()
|
||||
warnings.resetwarnings()
|
||||
@ -177,6 +184,11 @@ class MockEnvironment(Environment):
|
||||
pass
|
||||
|
||||
|
||||
class PersistentMockEnvironment(MockEnvironment):
|
||||
def cleanup(self):
|
||||
pass
|
||||
|
||||
|
||||
class BaseCLIResponse:
|
||||
"""
|
||||
Represents the result of simulated `$ http' invocation via `http()`.
|
||||
@ -440,7 +452,4 @@ def http(
|
||||
return r
|
||||
|
||||
finally:
|
||||
devnull.close()
|
||||
stdout.close()
|
||||
stderr.close()
|
||||
env.cleanup()
|
||||
|
Loading…
Reference in New Issue
Block a user