mirror of
https://github.com/httpie/cli.git
synced 2025-08-13 07:37:22 +02:00
Compare commits
205 Commits
Author | SHA1 | Date | |
---|---|---|---|
5b604c37c6 | |||
2843b874c6 | |||
2105caa49b | |||
8560d1196d | |||
2ef4a57d8c | |||
ff742581f4 | |||
fd30c4ef62 | |||
cee82c825e | |||
9eb8699873 | |||
3037327410 | |||
50e1564600 | |||
f4cf43ecdd | |||
7f03c52d22 | |||
5c068f8102 | |||
10b7d317d0 | |||
3de7c82077 | |||
db16bbee96 | |||
3524ccf0ba | |||
8ac44b57ce | |||
2db28ef692 | |||
7a234d60da | |||
a842a932cc | |||
b934eec7fc | |||
9e8e3691c8 | |||
e52a60e67c | |||
8aa654d1ef | |||
011402152c | |||
30a6f73ec8 | |||
ec4fb84254 | |||
c8c135ffff | |||
2da955fb06 | |||
c2677eeccf | |||
5325a9bc07 | |||
2e3272b5ba | |||
5dc30bc438 | |||
442aa673ac | |||
3e290e5dba | |||
2a9cd226aa | |||
3b58a4a4a2 | |||
7512ca7e47 | |||
cc697db730 | |||
cbe53ed79a | |||
3664644722 | |||
29de4ce115 | |||
879fedc10a | |||
18bb49b268 | |||
fcd3f7ece6 | |||
8e56e9fc64 | |||
44d3cff03f | |||
d021b94b5d | |||
4e29a6d561 | |||
1ae4152e1e | |||
47e9b99ba1 | |||
265841f866 | |||
b16392fbb9 | |||
e73c3e6c24 | |||
f0563deb7f | |||
4894b4c0fc | |||
3a123c4125 | |||
621042a048 | |||
0689b55e1d | |||
a7321d8ac4 | |||
d9a73cd8eb | |||
930cd9081a | |||
3549ee8342 | |||
810bb1c77b | |||
767f3c3a19 | |||
1236793272 | |||
c3a2f87dd2 | |||
1121d695a8 | |||
5794a070e1 | |||
4736a16698 | |||
3ad408add7 | |||
91cdb22a4b | |||
c995fd9b24 | |||
418b12bbd6 | |||
ecff53f2d5 | |||
41da87f7c8 | |||
4f172a61b4 | |||
542a2d35de | |||
d9e1dc08c9 | |||
3b734fb0bc | |||
8abe47969e | |||
8173cb0337 | |||
7fd34fc8ce | |||
80ae644464 | |||
69fe5dbfd1 | |||
f09e7564e7 | |||
dc5274e491 | |||
ad2b86ccf4 | |||
11b2af0f59 | |||
b54239b525 | |||
b0b0f3dc53 | |||
9f7612cdeb | |||
5e76ebc5e1 | |||
343a521673 | |||
2142ae60c3 | |||
0b6a9b23c2 | |||
9e1c0b98c7 | |||
003f2095d4 | |||
f9b5c2f696 | |||
76495cbdec | |||
c4d7d05f3b | |||
7a4fb5d966 | |||
f7c1bb269e | |||
0f9fd76852 | |||
af1d6b1853 | |||
419cc2c34a | |||
79a8ecd84b | |||
d262181bed | |||
732878f1b4 | |||
83803db14d | |||
dd2c9513f3 | |||
278dfc487d | |||
ff6f1887b0 | |||
86f4bf4d0a | |||
e6d0bfec7c | |||
9f1ec6d5cc | |||
85ba9ad8ea | |||
d03e3f4e14 | |||
c157948531 | |||
33ea977b64 | |||
d1596dde12 | |||
af2ffb6999 | |||
0632c4d614 | |||
6787a2bd29 | |||
9d2864b966 | |||
a5288f0cd6 | |||
8efa7cb04d | |||
baec1b2202 | |||
266c6375c6 | |||
77af4c7a5c | |||
7509dd4e6c | |||
f08c1bee17 | |||
59d9e928f8 | |||
0a873172c9 | |||
614866eeb2 | |||
395914fb4d | |||
65ab7d5caa | |||
b5623ccc87 | |||
ec203b1fac | |||
350abe3033 | |||
9241a09360 | |||
15013fd609 | |||
98688b2f2d | |||
5ac05e9514 | |||
5c98253377 | |||
b0f5b8ab26 | |||
55087a901e | |||
c901e70463 | |||
25bd817bb2 | |||
6f77e144e4 | |||
6bf39e469f | |||
30cd862fc0 | |||
ad613f29d2 | |||
225dccb218 | |||
cafa11665b | |||
0a9d3d3c54 | |||
e306667436 | |||
384d3869f6 | |||
5fd48e3137 | |||
37ef670876 | |||
46e782bf75 | |||
42edb1eb76 | |||
d45f413f12 | |||
f1ea486025 | |||
7abddfe350 | |||
86ba995ad8 | |||
c03f081a7e | |||
a7d8187b21 | |||
fc383e9b78 | |||
770df02291 | |||
f756cad58d | |||
fde64d578d | |||
c8404493e5 | |||
559134de0a | |||
813e8864a1 | |||
45fcd746d7 | |||
d5e3611e85 | |||
378a1f513e | |||
df6843b15a | |||
640901146f | |||
6b5d96da72 | |||
97bd9c2a89 | |||
708608e1d4 | |||
d56a1f216e | |||
738a6bea57 | |||
ec521c461b | |||
212000199e | |||
700dbeddb0 | |||
30a4d29f77 | |||
aedcad7e2a | |||
202f59e04a | |||
ba0c1ab258 | |||
217cf8ddae | |||
859e442083 | |||
4e59bbfae6 | |||
caa8fb9058 | |||
2797b7244c | |||
3b441fa57e | |||
c815e21ef9 | |||
8a03b7a824 | |||
b3f29c8d1e | |||
a88e44c284 | |||
c97fe64a37 |
8
.github/workflows/benchmark.yml
vendored
8
.github/workflows/benchmark.yml
vendored
@ -13,8 +13,8 @@ jobs:
|
|||||||
if: github.event.label.name == 'benchmark'
|
if: github.event.label.name == 'benchmark'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.9"
|
python-version: "3.9"
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ jobs:
|
|||||||
echo "::set-output name=body::$body"
|
echo "::set-output name=body::$body"
|
||||||
|
|
||||||
- name: Find Comment
|
- name: Find Comment
|
||||||
uses: peter-evans/find-comment@v1
|
uses: peter-evans/find-comment@v2
|
||||||
id: fc
|
id: fc
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
@ -38,7 +38,7 @@ jobs:
|
|||||||
body-includes: '# Benchmarks'
|
body-includes: '# Benchmarks'
|
||||||
|
|
||||||
- name: Create or update comment
|
- name: Create or update comment
|
||||||
uses: peter-evans/create-or-update-comment@v1
|
uses: peter-evans/create-or-update-comment@v2
|
||||||
with:
|
with:
|
||||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
6
.github/workflows/code-style.yml
vendored
6
.github/workflows/code-style.yml
vendored
@ -1,3 +1,5 @@
|
|||||||
|
name: Code Style Check
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
@ -11,8 +13,8 @@ jobs:
|
|||||||
code-style:
|
code-style:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
- run: make venv
|
- run: make venv
|
||||||
|
22
.github/workflows/content.yml
vendored
Normal file
22
.github/workflows/content.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
name: Update Generated Content
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
update-content:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: 3.9
|
||||||
|
- run: make content
|
||||||
|
- name: Create Pull Request
|
||||||
|
id: cpr
|
||||||
|
uses: peter-evans/create-pull-request@v4
|
||||||
|
with:
|
||||||
|
commit-message: "[automated] Update generated content"
|
||||||
|
title: "[automated] Update generated content"
|
||||||
|
delete-branch: true
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
6
.github/workflows/coverage.yml
vendored
6
.github/workflows/coverage.yml
vendored
@ -1,3 +1,5 @@
|
|||||||
|
name: Coverage
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
@ -10,8 +12,8 @@ jobs:
|
|||||||
coverage:
|
coverage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
- run: make install
|
- run: make install
|
||||||
|
4
.github/workflows/docs-check-markdown.yml
vendored
4
.github/workflows/docs-check-markdown.yml
vendored
@ -1,3 +1,5 @@
|
|||||||
|
name: Check Markdown Style
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
@ -8,7 +10,7 @@ jobs:
|
|||||||
doc:
|
doc:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
|
4
.github/workflows/docs-deploy.yml
vendored
4
.github/workflows/docs-deploy.yml
vendored
@ -1,3 +1,5 @@
|
|||||||
|
name: Deploy Documentation
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@ -15,6 +17,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install HTTPie
|
- name: Install HTTPie
|
||||||
run: sudo snap install --edge httpie
|
run: sudo pip install httpie
|
||||||
- name: Trigger new documentation build
|
- name: Trigger new documentation build
|
||||||
run: http --ignore-stdin POST ${{ secrets.DOCS_UPDATE_VERCEL_HOOK }}
|
run: http --ignore-stdin POST ${{ secrets.DOCS_UPDATE_VERCEL_HOOK }}
|
||||||
|
31
.github/workflows/docs-update-install.yml
vendored
31
.github/workflows/docs-update-install.yml
vendored
@ -1,31 +0,0 @@
|
|||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
paths:
|
|
||||||
- .github/workflows/docs-update-install.yml
|
|
||||||
- docs/installation/*
|
|
||||||
|
|
||||||
# Allow to call the workflow manually
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
doc:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
- run: make install
|
|
||||||
- run: make doc-update-install
|
|
||||||
- uses: Automattic/action-commit-to-branch@master
|
|
||||||
with:
|
|
||||||
branch: master
|
|
||||||
commit_message: |
|
|
||||||
Auto-update install docs
|
|
||||||
|
|
||||||
Via .github/workflows/docs-update-install.yml
|
|
||||||
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
26
.github/workflows/release-brew.yml
vendored
Normal file
26
.github/workflows/release-brew.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: Release on Homebrew
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
branch:
|
||||||
|
description: "The branch, tag or SHA to release from"
|
||||||
|
required: true
|
||||||
|
default: "master"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
brew-release:
|
||||||
|
name: Release the Homebrew Package
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.branch }}
|
||||||
|
|
||||||
|
- uses: mislav/bump-homebrew-formula-action@v2
|
||||||
|
with:
|
||||||
|
formula-name: httpie
|
||||||
|
tag-name: ${{ github.events.inputs.branch }}
|
||||||
|
env:
|
||||||
|
COMMITTER_TOKEN: ${{ secrets.BREW_UPDATE_TOKEN }}
|
61
.github/workflows/release-choco.yml
vendored
Normal file
61
.github/workflows/release-choco.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
name: Release on Chocolatey
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
branch:
|
||||||
|
description: "The branch, tag or SHA to release from"
|
||||||
|
required: true
|
||||||
|
default: "master"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
brew-release:
|
||||||
|
name: Release the Chocolatey
|
||||||
|
runs-on: windows-2019
|
||||||
|
env:
|
||||||
|
package-dir: docs\packaging\windows-chocolatey
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.branch }}
|
||||||
|
|
||||||
|
# Chocolatey comes already installed on the Windows GHA image
|
||||||
|
- name: Build the Choco package
|
||||||
|
shell: cmd
|
||||||
|
run: choco pack -v
|
||||||
|
working-directory: ${{ env.package-dir }}
|
||||||
|
|
||||||
|
- name: Check the Choco package
|
||||||
|
run: choco info httpie -s .
|
||||||
|
working-directory: ${{ env.package-dir }}
|
||||||
|
|
||||||
|
- 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
|
||||||
|
choco uninstall -y httpie
|
||||||
|
working-directory: ${{ env.package-dir }}
|
||||||
|
|
||||||
|
- name: Publish on Chocolatey
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }}
|
||||||
|
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 }}
|
77
.github/workflows/release-linux-standalone.yml
vendored
Normal file
77
.github/workflows/release-linux-standalone.yml
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
name: Release as Standalone Linux Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
branch:
|
||||||
|
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]
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
binary-build-and-release:
|
||||||
|
name: Build and Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.branch }}
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: 3.9
|
||||||
|
|
||||||
|
- name: Build Artifacts
|
||||||
|
run: |
|
||||||
|
cd extras/packaging/linux
|
||||||
|
./get_release_artifacts.sh
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: http
|
||||||
|
path: extras/packaging/linux/artifacts/dist/http
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: httpie.deb
|
||||||
|
path: extras/packaging/linux/artifacts/dist/*.deb
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
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/cli/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
|
||||||
|
uses: actions/upload-release-asset@v1.0.2
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
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
|
||||||
|
uses: actions/upload-release-asset@v1.0.2
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
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
|
30
.github/workflows/release-pypi.yml
vendored
Normal file
30
.github/workflows/release-pypi.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: Release on PyPI
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
branch:
|
||||||
|
description: "The branch, tag or SHA to release from"
|
||||||
|
required: true
|
||||||
|
default: "master"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pypi-build-and-release:
|
||||||
|
name: Build and Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.branch }}
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: 3.9
|
||||||
|
|
||||||
|
- name: Build a binary wheel and a source tarball
|
||||||
|
run: make install && make build
|
||||||
|
|
||||||
|
- name: Release on PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@master
|
||||||
|
with:
|
||||||
|
password: ${{ secrets.PYPI_TOKEN }}
|
27
.github/workflows/release-snap.yml
vendored
27
.github/workflows/release-snap.yml
vendored
@ -1,3 +1,5 @@
|
|||||||
|
name: Release on Snap
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
@ -7,16 +9,33 @@ on:
|
|||||||
default: "master"
|
default: "master"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
snap:
|
snap-build-and-release:
|
||||||
|
name: Build & Release the Snap Package
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
# If any of the stages fail, then we'll stop the action
|
||||||
|
# to give release manager time to investigate the underlying
|
||||||
|
# issue.
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
level: [edge, beta, candidate, stable]
|
||||||
|
|
||||||
|
# Set the concurrency level for this version, so
|
||||||
|
# that we'll release one by one.
|
||||||
|
concurrency: ${{ github.event.inputs.branch }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.inputs.branch }}
|
ref: ${{ github.event.inputs.branch }}
|
||||||
|
|
||||||
- uses: snapcore/action-build@v1
|
- uses: snapcore/action-build@v1
|
||||||
id: build
|
id: build
|
||||||
|
|
||||||
- uses: snapcore/action-publish@v1
|
- uses: snapcore/action-publish@v1
|
||||||
|
env:
|
||||||
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
|
||||||
with:
|
with:
|
||||||
store_login: ${{ secrets.SNAP_STORE_LOGIN }}
|
|
||||||
snap: ${{ steps.build.outputs.snap }}
|
snap: ${{ steps.build.outputs.snap }}
|
||||||
release: edge
|
release: ${{ matrix.level }}
|
||||||
|
31
.github/workflows/release.yml
vendored
31
.github/workflows/release.yml
vendored
@ -1,31 +0,0 @@
|
|||||||
on:
|
|
||||||
# Add a "Trigger" button to manually start the workflow.
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
branch:
|
|
||||||
description: "The branch, tag or SHA to release from"
|
|
||||||
required: true
|
|
||||||
default: "master"
|
|
||||||
# It could be fully automated by uncommenting following lines.
|
|
||||||
# Let's see later if we are confident enough to try it :)
|
|
||||||
# release:
|
|
||||||
# types:
|
|
||||||
# - published
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
new-release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.inputs.branch }}
|
|
||||||
- name: PyPi configuration
|
|
||||||
run: |
|
|
||||||
echo "[distutils]\nindex-servers=\n httpie\n\n[httpie]\nrepository = https://upload.pypi.org/legacy/\n" > $HOME/.pypirc
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.9
|
|
||||||
- run: make publish
|
|
||||||
env:
|
|
||||||
TWINE_USERNAME: __token__
|
|
||||||
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v4
|
- uses: actions/stale@v8
|
||||||
with:
|
with:
|
||||||
close-pr-message: 'Thanks for the pull request, but since it was stale for more than a 30 days we are closing it. If you want to work back on it, feel free to re-open it or create a new one.'
|
close-pr-message: 'Thanks for the pull request, but since it was stale for more than a 30 days we are closing it. If you want to work back on it, feel free to re-open it or create a new one.'
|
||||||
stale-pr-label: 'stale'
|
stale-pr-label: 'stale'
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
name: Test Snap Package (Linux)
|
||||||
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
@ -9,7 +12,7 @@ jobs:
|
|||||||
snap:
|
snap:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: snapcore/action-build@v1
|
uses: snapcore/action-build@v1
|
||||||
id: snapcraft
|
id: snapcraft
|
||||||
|
4
.github/workflows/test-package-mac-brew.yml
vendored
4
.github/workflows/test-package-mac-brew.yml
vendored
@ -1,3 +1,5 @@
|
|||||||
|
name: Test Brew Package (MacOS)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
@ -9,7 +11,7 @@ jobs:
|
|||||||
brew:
|
brew:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- name: Setup brew
|
- name: Setup brew
|
||||||
run: |
|
run: |
|
||||||
brew developer on
|
brew developer on
|
||||||
|
19
.github/workflows/tests.yml
vendored
19
.github/workflows/tests.yml
vendored
@ -1,3 +1,8 @@
|
|||||||
|
name: Tests
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
@ -19,13 +24,19 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest, macos-13, windows-latest]
|
||||||
python-version: [3.7, 3.8, 3.9, "3.10"]
|
python-version:
|
||||||
|
- '3.12'
|
||||||
|
- '3.11'
|
||||||
|
- '3.10'
|
||||||
|
- '3.9'
|
||||||
|
- '3.8'
|
||||||
|
- '3.7'
|
||||||
pyopenssl: [0, 1]
|
pyopenssl: [0, 1]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
- name: Windows setup
|
- name: Windows setup
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -43,8 +43,8 @@ MANIFEST
|
|||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# Usually these files are written by a python script from a template
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
*.manifest
|
|
||||||
*.spec
|
*.spec
|
||||||
|
*.manifest
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
@ -151,3 +151,5 @@ dmypy.json
|
|||||||
|
|
||||||
# Windows Chocolatey
|
# Windows Chocolatey
|
||||||
*.nupkg
|
*.nupkg
|
||||||
|
|
||||||
|
artifacts/
|
||||||
|
12
.packit.yaml
12
.packit.yaml
@ -3,16 +3,10 @@
|
|||||||
specfile_path: httpie.spec
|
specfile_path: httpie.spec
|
||||||
actions:
|
actions:
|
||||||
# get the current Fedora Rawhide specfile:
|
# get the current Fedora Rawhide specfile:
|
||||||
# post-upstream-clone: "wget https://src.fedoraproject.org/rpms/httpie/raw/rawhide/f/httpie.spec -O httpie.spec"
|
post-upstream-clone: "wget https://src.fedoraproject.org/rpms/httpie/raw/rawhide/f/httpie.spec -O httpie.spec"
|
||||||
post-upstream-clone: "cp docs/packaging/linux-fedora/httpie.spec.txt httpie.spec"
|
# 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:
|
jobs:
|
||||||
- job: copr_build
|
|
||||||
trigger: pull_request
|
|
||||||
metadata:
|
|
||||||
targets:
|
|
||||||
- fedora-all
|
|
||||||
additional_repos:
|
|
||||||
- "https://kojipkgs.fedoraproject.org/repos/f$releasever-build/latest/$basearch/"
|
|
||||||
- job: propose_downstream
|
- job: propose_downstream
|
||||||
trigger: release
|
trigger: release
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Patches, features, ideas
|
## Patches, features, ideas
|
||||||
|
|
||||||
[Complete list of contributors on GitHub](https://github.com/httpie/httpie/graphs/contributors)
|
[Complete list of contributors on GitHub](https://github.com/httpie/cli/graphs/contributors)
|
||||||
|
|
||||||
- [Cláudia T. Delgado](https://github.com/claudiatd)
|
- [Cláudia T. Delgado](https://github.com/claudiatd)
|
||||||
- [Hank Gay](https://github.com/gthank)
|
- [Hank Gay](https://github.com/gthank)
|
||||||
|
250
CHANGELOG.md
250
CHANGELOG.md
@ -3,98 +3,156 @@
|
|||||||
This document records all notable changes to [HTTPie](https://httpie.io).
|
This document records all notable changes to [HTTPie](https://httpie.io).
|
||||||
This project adheres to [Semantic Versioning](https://semver.org/).
|
This project adheres to [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
## [3.0.0](https://github.com/httpie/httpie/compare/2.6.0...3.0.0) (2022-01-21)
|
## [3.2.4](https://github.com/httpie/cli/compare/3.2.3...3.2.4) (2024-11-01)
|
||||||
|
|
||||||
- Dropped support for Python 3.6. ([#1177](https://github.com/httpie/httpie/issues/1177))
|
- Fix default certs loading and unpin `requests`. ([#1596](https://github.com/httpie/cli/issues/1596))
|
||||||
- Improved startup time by 40%. ([#1211](https://github.com/httpie/httpie/pull/1211))
|
|
||||||
- Added support for nested JSON syntax. ([#1169](https://github.com/httpie/httpie/issues/1169))
|
|
||||||
- Added `httpie plugins` interface for plugin management. ([#566](https://github.com/httpie/httpie/issues/566))
|
|
||||||
- Added support for Bearer authentication via `--auth-type=bearer` ([#1215](https://github.com/httpie/httpie/issues/1215)).
|
|
||||||
- Added support for quick conversions of pasted URLs into HTTPie calls by adding a space after the protocol name (`$ https ://pie.dev` → `https://pie.dev`). ([#1195](https://github.com/httpie/httpie/issues/1195))
|
|
||||||
- Added support for _sending_ multiple HTTP header lines with the same name. ([#130](https://github.com/httpie/httpie/issues/130))
|
|
||||||
- Added support for _receiving_ multiple HTTP headers lines with the same name. ([#1207](https://github.com/httpie/httpie/issues/1207))
|
|
||||||
- Added support for basic JSON types on `--form`/`--multipart` when using JSON only operators (`:=`/`:=@`). ([#1212](https://github.com/httpie/httpie/issues/1212))
|
|
||||||
- Added support for automatically enabling `--stream` when `Content-Type` is `text/event-stream`. ([#376](https://github.com/httpie/httpie/issues/376))
|
|
||||||
- Added support for displaying the total elapsed time through `--meta`/`-vv` or `--print=m`. ([#243](https://github.com/httpie/httpie/issues/243))
|
|
||||||
- Added new `pie-dark`/`pie-light` (and `pie`) styles that match with [HTTPie for Web and Desktop](https://httpie.io/product). ([#1237](https://github.com/httpie/httpie/issues/1237))
|
|
||||||
- Added support for better error handling on DNS failures. ([#1248](https://github.com/httpie/httpie/issues/1248))
|
|
||||||
- Added support for storing prompted passwords in the local sessions. ([#1098](https://github.com/httpie/httpie/issues/1098))
|
|
||||||
- Added warnings about the `--ignore-stdin`, when there is no incoming data from stdin. ([#1255](https://github.com/httpie/httpie/issues/1255))
|
|
||||||
- Fixed crashing due to broken plugins. ([#1204](https://github.com/httpie/httpie/issues/1204))
|
|
||||||
- Fixed auto addition of XML declaration to every formatted XML response. ([#1156](https://github.com/httpie/httpie/issues/1156))
|
|
||||||
- Fixed highlighting when `Content-Type` specifies `charset`. ([#1242](https://github.com/httpie/httpie/issues/1242))
|
|
||||||
- Fixed an unexpected crash when `--raw` is used with `--chunked`. ([#1253](https://github.com/httpie/httpie/issues/1253))
|
|
||||||
- Changed the default Windows theme from `fruity` to `auto`. ([#1266](https://github.com/httpie/httpie/issues/1266))
|
|
||||||
|
|
||||||
## [2.6.0](https://github.com/httpie/httpie/compare/2.5.0...2.6.0) (2021-10-14)
|
## [3.2.3](https://github.com/httpie/cli/compare/3.2.2...3.2.3) (2024-07-10)
|
||||||
|
|
||||||
[What’s new in HTTPie 2.6.0 →](https://httpie.io/blog/httpie-2.6.0)
|
- Fix SSL connections by pinning the `requests` version to `2.31.0`. (#1583, #1581)
|
||||||
|
- Make it possible to [unset](https://httpie.io/docs/cli/default-request-headers) the `User-Agent` and `Accept-Encoding` request headers. ([#1502](https://github.com/httpie/cli/issues/1502))
|
||||||
|
|
||||||
- Added support for formatting & coloring of JSON bodies preceded by non-JSON data (e.g., an XXSI prefix). ([#1130](https://github.com/httpie/httpie/issues/1130))
|
## [3.2.2](https://github.com/httpie/cli/compare/3.2.1...3.2.2) (2023-05-19)
|
||||||
- Added charset auto-detection when `Content-Type` doesn’t include it. ([#1110](https://github.com/httpie/httpie/issues/1110), [#1168](https://github.com/httpie/httpie/issues/1168))
|
|
||||||
- Added `--response-charset` to allow overriding the response encoding for terminal display purposes. ([#1168](https://github.com/httpie/httpie/issues/1168))
|
|
||||||
- Added `--response-mime` to allow overriding the response mime type for coloring and formatting for the terminal. ([#1168](https://github.com/httpie/httpie/issues/1168))
|
|
||||||
- Added the ability to silence warnings through using `-q` or `--quiet` twice (e.g. `-qq`) ([#1175](https://github.com/httpie/httpie/issues/1175))
|
|
||||||
- Added installed plugin list to `--debug` output. ([#1165](https://github.com/httpie/httpie/issues/1165))
|
|
||||||
- Fixed duplicate keys preservation in JSON data. ([#1163](https://github.com/httpie/httpie/issues/1163))
|
|
||||||
|
|
||||||
## [2.5.0](https://github.com/httpie/httpie/compare/2.4.0...2.5.0) (2021-09-06)
|
- Fixed compatibility with urllib3 2.0.0. ([#1499](https://github.com/httpie/cli/issues/1499))
|
||||||
|
|
||||||
[What’s new in HTTPie 2.5.0 →](https://httpie.io/blog/httpie-2.5.0)
|
## [3.2.1](https://github.com/httpie/cli/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/cli/pull/1383))
|
||||||
|
- Fixed the display of the crash happening in the secondary process for update checks. ([#1388](https://github.com/httpie/cli/issues/1388))
|
||||||
|
|
||||||
|
## [3.2.0](https://github.com/httpie/cli/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/cli/pull/1336))
|
||||||
|
- Added support for single binary executables. ([#1330](https://github.com/httpie/cli/pull/1330))
|
||||||
|
- Added support for man pages (and auto generation of them from the parser declaration). ([#1317](https://github.com/httpie/cli/pull/1317))
|
||||||
|
- Added `http --manual` for man pages & regular manual with pager. ([#1343](https://github.com/httpie/cli/pull/1343))
|
||||||
|
- Added support for session persistence of repeated headers with the same name. ([#1335](https://github.com/httpie/cli/pull/1335))
|
||||||
|
- Added support for sending `Secure` cookies to the `localhost` (and `.local` suffixed domains). ([#1308](https://github.com/httpie/cli/issues/1308))
|
||||||
|
- Improved UI for the progress bars. ([#1324](https://github.com/httpie/cli/pull/1324))
|
||||||
|
- Fixed redundant creation of `Content-Length` header on `OPTIONS` requests. ([#1310](https://github.com/httpie/cli/issues/1310))
|
||||||
|
- Fixed blocking of warning thread on some use cases. ([#1349](https://github.com/httpie/cli/issues/1349))
|
||||||
|
- Changed `httpie plugins` to the new `httpie cli` namespace as `httpie cli plugins` (`httpie plugins` continues to work as a hidden alias). ([#1320](https://github.com/httpie/cli/issues/1320))
|
||||||
|
- Soft deprecated the `--history-print`. ([#1380](https://github.com/httpie/cli/pull/1380))
|
||||||
|
|
||||||
|
## [3.1.0](https://github.com/httpie/cli/compare/3.0.2...3.1.0) (2022-03-08)
|
||||||
|
|
||||||
|
- **SECURITY** Fixed the [vulnerability](https://github.com/httpie/cli/security/advisories/GHSA-9w4w-cpc8-h2fq) that caused exposure of cookies on redirects to third party hosts. ([#1312](https://github.com/httpie/cli/pull/1312))
|
||||||
|
- Fixed escaping of integer indexes with multiple backslashes in the nested JSON builder. ([#1285](https://github.com/httpie/cli/issues/1285))
|
||||||
|
- Fixed displaying of status code without a status message on non-`auto` themes. ([#1300](https://github.com/httpie/cli/issues/1300))
|
||||||
|
- Fixed redundant issuance of stdin detection warnings on some rare cases due to underlying implementation. ([#1303](https://github.com/httpie/cli/pull/1303))
|
||||||
|
- Fixed double `--quiet` so that it will now suppress all python level warnings. ([#1271](https://github.com/httpie/cli/issues/1271))
|
||||||
|
- Added support for specifying certificate private key passphrases through `--cert-key-pass` and prompts. ([#946](https://github.com/httpie/cli/issues/946))
|
||||||
|
- Added `httpie cli export-args` command for exposing the parser specification for the `http`/`https` commands. ([#1293](https://github.com/httpie/cli/pull/1293))
|
||||||
|
- Improved regulation of top-level arrays. ([#1292](https://github.com/httpie/cli/commit/225dccb2186f14f871695b6c4e0bfbcdb2e3aa28))
|
||||||
|
- Improved UI layout for standalone invocations. ([#1296](https://github.com/httpie/cli/pull/1296))
|
||||||
|
|
||||||
|
## [3.0.2](https://github.com/httpie/cli/compare/3.0.1...3.0.2) (2022-01-24)
|
||||||
|
|
||||||
|
[What’s new in HTTPie for Terminal 3.0 →](https://httpie.io/blog/httpie-3.0.0)
|
||||||
|
|
||||||
|
- Fixed usage of `httpie` when there is a presence of a config with `default_options`. ([#1280](https://github.com/httpie/cli/pull/1280))
|
||||||
|
|
||||||
|
## [3.0.1](https://github.com/httpie/cli/compare/3.0.0...3.0.1) (2022-01-23)
|
||||||
|
|
||||||
|
[What’s new in HTTPie for Terminal 3.0 →](https://httpie.io/blog/httpie-3.0.0)
|
||||||
|
|
||||||
|
- Changed the value shown as time elapsed from time-to-read-headers to total exchange time. ([#1277](https://github.com/httpie/cli/issues/1277))
|
||||||
|
|
||||||
|
## [3.0.0](https://github.com/httpie/cli/compare/2.6.0...3.0.0) (2022-01-21)
|
||||||
|
|
||||||
|
[What’s new in HTTPie for Terminal 3.0 →](https://httpie.io/blog/httpie-3.0.0)
|
||||||
|
|
||||||
|
- Dropped support for Python 3.6. ([#1177](https://github.com/httpie/cli/issues/1177))
|
||||||
|
- Improved startup time by 40%. ([#1211](https://github.com/httpie/cli/pull/1211))
|
||||||
|
- Added support for nested JSON syntax. ([#1169](https://github.com/httpie/cli/issues/1169))
|
||||||
|
- Added `httpie plugins` interface for plugin management. ([#566](https://github.com/httpie/cli/issues/566))
|
||||||
|
- Added support for Bearer authentication via `--auth-type=bearer` ([#1215](https://github.com/httpie/cli/issues/1215)).
|
||||||
|
- Added support for quick conversions of pasted URLs into HTTPie calls by adding a space after the protocol name (`$ https ://pie.dev` → `https://pie.dev`). ([#1195](https://github.com/httpie/cli/issues/1195))
|
||||||
|
- Added support for _sending_ multiple HTTP header lines with the same name. ([#130](https://github.com/httpie/cli/issues/130))
|
||||||
|
- Added support for _receiving_ multiple HTTP headers lines with the same name. ([#1207](https://github.com/httpie/cli/issues/1207))
|
||||||
|
- Added support for basic JSON types on `--form`/`--multipart` when using JSON only operators (`:=`/`:=@`). ([#1212](https://github.com/httpie/cli/issues/1212))
|
||||||
|
- Added support for automatically enabling `--stream` when `Content-Type` is `text/event-stream`. ([#376](https://github.com/httpie/cli/issues/376))
|
||||||
|
- Added support for displaying the total elapsed time through `--meta`/`-vv` or `--print=m`. ([#243](https://github.com/httpie/cli/issues/243))
|
||||||
|
- Added new `pie-dark`/`pie-light` (and `pie`) styles that match with [HTTPie for Web and Desktop](https://httpie.io/product). ([#1237](https://github.com/httpie/cli/issues/1237))
|
||||||
|
- Added support for better error handling on DNS failures. ([#1248](https://github.com/httpie/cli/issues/1248))
|
||||||
|
- Added support for storing prompted passwords in the local sessions. ([#1098](https://github.com/httpie/cli/issues/1098))
|
||||||
|
- Added warnings about the `--ignore-stdin`, when there is no incoming data from stdin. ([#1255](https://github.com/httpie/cli/issues/1255))
|
||||||
|
- Fixed crashing due to broken plugins. ([#1204](https://github.com/httpie/cli/issues/1204))
|
||||||
|
- Fixed auto addition of XML declaration to every formatted XML response. ([#1156](https://github.com/httpie/cli/issues/1156))
|
||||||
|
- Fixed highlighting when `Content-Type` specifies `charset`. ([#1242](https://github.com/httpie/cli/issues/1242))
|
||||||
|
- Fixed an unexpected crash when `--raw` is used with `--chunked`. ([#1253](https://github.com/httpie/cli/issues/1253))
|
||||||
|
- Changed the default Windows theme from `fruity` to `auto`. ([#1266](https://github.com/httpie/cli/issues/1266))
|
||||||
|
|
||||||
|
## [2.6.0](https://github.com/httpie/cli/compare/2.5.0...2.6.0) (2021-10-14)
|
||||||
|
|
||||||
|
[What’s new in HTTPie for Terminal 2.6.0 →](https://httpie.io/blog/httpie-2.6.0)
|
||||||
|
|
||||||
|
- Added support for formatting & coloring of JSON bodies preceded by non-JSON data (e.g., an XXSI prefix). ([#1130](https://github.com/httpie/cli/issues/1130))
|
||||||
|
- Added charset auto-detection when `Content-Type` doesn’t include it. ([#1110](https://github.com/httpie/cli/issues/1110), [#1168](https://github.com/httpie/cli/issues/1168))
|
||||||
|
- Added `--response-charset` to allow overriding the response encoding for terminal display purposes. ([#1168](https://github.com/httpie/cli/issues/1168))
|
||||||
|
- Added `--response-mime` to allow overriding the response mime type for coloring and formatting for the terminal. ([#1168](https://github.com/httpie/cli/issues/1168))
|
||||||
|
- Added the ability to silence warnings through using `-q` or `--quiet` twice (e.g. `-qq`) ([#1175](https://github.com/httpie/cli/issues/1175))
|
||||||
|
- Added installed plugin list to `--debug` output. ([#1165](https://github.com/httpie/cli/issues/1165))
|
||||||
|
- Fixed duplicate keys preservation in JSON data. ([#1163](https://github.com/httpie/cli/issues/1163))
|
||||||
|
|
||||||
|
## [2.5.0](https://github.com/httpie/cli/compare/2.4.0...2.5.0) (2021-09-06)
|
||||||
|
|
||||||
|
[What’s new in HTTPie for Terminal 2.5.0 →](https://httpie.io/blog/httpie-2.5.0)
|
||||||
|
|
||||||
- Added `--raw` to allow specifying the raw request body without extra processing as
|
- Added `--raw` to allow specifying the raw request body without extra processing as
|
||||||
an alternative to `stdin`. ([#534](https://github.com/httpie/httpie/issues/534))
|
an alternative to `stdin`. ([#534](https://github.com/httpie/cli/issues/534))
|
||||||
- Added support for XML formatting. ([#1129](https://github.com/httpie/httpie/issues/1129))
|
- Added support for XML formatting. ([#1129](https://github.com/httpie/cli/issues/1129))
|
||||||
- Added internal support for file-like object responses to improve adapter plugin support. ([#1094](https://github.com/httpie/httpie/issues/1094))
|
- Added internal support for file-like object responses to improve adapter plugin support. ([#1094](https://github.com/httpie/cli/issues/1094))
|
||||||
- Fixed `--continue --download` with a single byte to be downloaded left. ([#1032](https://github.com/httpie/httpie/issues/1032))
|
- Fixed `--continue --download` with a single byte to be downloaded left. ([#1032](https://github.com/httpie/cli/issues/1032))
|
||||||
- Fixed `--verbose` HTTP 307 redirects with streamed request body. ([#1088](https://github.com/httpie/httpie/issues/1088))
|
- Fixed `--verbose` HTTP 307 redirects with streamed request body. ([#1088](https://github.com/httpie/cli/issues/1088))
|
||||||
- Fixed handling of session files with `Cookie:` followed by other headers. ([#1126](https://github.com/httpie/httpie/issues/1126))
|
- Fixed handling of session files with `Cookie:` followed by other headers. ([#1126](https://github.com/httpie/cli/issues/1126))
|
||||||
|
|
||||||
## [2.4.0](https://github.com/httpie/httpie/compare/2.3.0...2.4.0) (2021-02-06)
|
## [2.4.0](https://github.com/httpie/cli/compare/2.3.0...2.4.0) (2021-02-06)
|
||||||
|
|
||||||
- Added support for `--session` cookie expiration based on `Set-Cookie: max-age=<n>`. ([#1029](https://github.com/httpie/httpie/issues/1029))
|
- Added support for `--session` cookie expiration based on `Set-Cookie: max-age=<n>`. ([#1029](https://github.com/httpie/cli/issues/1029))
|
||||||
- Show a `--check-status` warning with `--quiet` as well, not only when the output is redirected. ([#1026](https://github.com/httpie/httpie/issues/1026))
|
- Show a `--check-status` warning with `--quiet` as well, not only when the output is redirected. ([#1026](https://github.com/httpie/cli/issues/1026))
|
||||||
- Fixed upload with `--session` ([#1020](https://github.com/httpie/httpie/issues/1020)).
|
- Fixed upload with `--session` ([#1020](https://github.com/httpie/cli/issues/1020)).
|
||||||
- Fixed a missing blank line between request and response ([#1006](https://github.com/httpie/httpie/issues/1006)).
|
- Fixed a missing blank line between request and response ([#1006](https://github.com/httpie/cli/issues/1006)).
|
||||||
|
|
||||||
## [2.3.0](https://github.com/httpie/httpie/compare/2.2.0...2.3.0) (2020-10-25)
|
## [2.3.0](https://github.com/httpie/cli/compare/2.2.0...2.3.0) (2020-10-25)
|
||||||
|
|
||||||
- Added support for streamed uploads ([#201](https://github.com/httpie/httpie/issues/201)).
|
- Added support for streamed uploads ([#201](https://github.com/httpie/cli/issues/201)).
|
||||||
- Added support for multipart upload streaming ([#684](https://github.com/httpie/httpie/issues/684)).
|
- Added support for multipart upload streaming ([#684](https://github.com/httpie/cli/issues/684)).
|
||||||
- Added support for body-from-file upload streaming (`http pie.dev/post @file`).
|
- Added support for body-from-file upload streaming (`http pie.dev/post @file`).
|
||||||
- Added `--chunked` to enable chunked transfer encoding ([#753](https://github.com/httpie/httpie/issues/753)).
|
- Added `--chunked` to enable chunked transfer encoding ([#753](https://github.com/httpie/cli/issues/753)).
|
||||||
- Added `--multipart` to allow `multipart/form-data` encoding for non-file `--form` requests as well.
|
- Added `--multipart` to allow `multipart/form-data` encoding for non-file `--form` requests as well.
|
||||||
- Added support for preserving field order in multipart requests ([#903](https://github.com/httpie/httpie/issues/903)).
|
- Added support for preserving field order in multipart requests ([#903](https://github.com/httpie/cli/issues/903)).
|
||||||
- Added `--boundary` to allow a custom boundary string for `multipart/form-data` requests.
|
- Added `--boundary` to allow a custom boundary string for `multipart/form-data` requests.
|
||||||
- Added support for combining cookies specified on the CLI and in a session file ([#932](https://github.com/httpie/httpie/issues/932)).
|
- Added support for combining cookies specified on the CLI and in a session file ([#932](https://github.com/httpie/cli/issues/932)).
|
||||||
- Added out of the box SOCKS support with no extra installation ([#904](https://github.com/httpie/httpie/issues/904)).
|
- Added out of the box SOCKS support with no extra installation ([#904](https://github.com/httpie/cli/issues/904)).
|
||||||
- Added `--quiet, -q` flag to enforce silent behaviour.
|
- Added `--quiet, -q` flag to enforce silent behaviour.
|
||||||
- Fixed the handling of invalid `expires` dates in `Set-Cookie` headers ([#963](https://github.com/httpie/httpie/issues/963)).
|
- Fixed the handling of invalid `expires` dates in `Set-Cookie` headers ([#963](https://github.com/httpie/cli/issues/963)).
|
||||||
- Removed Tox testing entirely ([#943](https://github.com/httpie/httpie/issues/943)).
|
- Removed Tox testing entirely ([#943](https://github.com/httpie/cli/issues/943)).
|
||||||
|
|
||||||
## [2.2.0](https://github.com/httpie/httpie/compare/2.1.0...2.2.0) (2020-06-18)
|
## [2.2.0](https://github.com/httpie/cli/compare/2.1.0...2.2.0) (2020-06-18)
|
||||||
|
|
||||||
- Added support for custom content types for uploaded files ([#668](https://github.com/httpie/httpie/issues/668)).
|
- Added support for custom content types for uploaded files ([#668](https://github.com/httpie/cli/issues/668)).
|
||||||
- Added support for `$XDG_CONFIG_HOME` ([#920](https://github.com/httpie/httpie/issues/920)).
|
- Added support for `$XDG_CONFIG_HOME` ([#920](https://github.com/httpie/cli/issues/920)).
|
||||||
- Added support for `Set-Cookie`-triggered cookie expiration ([#853](https://github.com/httpie/httpie/issues/853)).
|
- Added support for `Set-Cookie`-triggered cookie expiration ([#853](https://github.com/httpie/cli/issues/853)).
|
||||||
- Added `--format-options` to allow disabling sorting, etc. ([#128](https://github.com/httpie/httpie/issues/128))
|
- Added `--format-options` to allow disabling sorting, etc. ([#128](https://github.com/httpie/cli/issues/128))
|
||||||
- Added `--sorted` and `--unsorted` shortcuts for (un)setting all sorting-related `--format-options`. ([#128](https://github.com/httpie/httpie/issues/128))
|
- Added `--sorted` and `--unsorted` shortcuts for (un)setting all sorting-related `--format-options`. ([#128](https://github.com/httpie/cli/issues/128))
|
||||||
- Added `--ciphers` to allow configuring OpenSSL ciphers ([#870](https://github.com/httpie/httpie/issues/870)).
|
- Added `--ciphers` to allow configuring OpenSSL ciphers ([#870](https://github.com/httpie/cli/issues/870)).
|
||||||
- Added `netrc` support for auth plugins. Enabled for `--auth-type=basic`
|
- Added `netrc` support for auth plugins. Enabled for `--auth-type=basic`
|
||||||
and `digest`, 3rd parties may opt in ([#718](https://github.com/httpie/httpie/issues/718), [#719](https://github.com/httpie/httpie/issues/719), [#852](https://github.com/httpie/httpie/issues/852), [#934](https://github.com/httpie/httpie/issues/934)).
|
and `digest`, 3rd parties may opt in ([#718](https://github.com/httpie/cli/issues/718), [#719](https://github.com/httpie/cli/issues/719), [#852](https://github.com/httpie/cli/issues/852), [#934](https://github.com/httpie/cli/issues/934)).
|
||||||
- Fixed built-in plugins-related circular imports ([#925](https://github.com/httpie/httpie/issues/925)).
|
- Fixed built-in plugins-related circular imports ([#925](https://github.com/httpie/cli/issues/925)).
|
||||||
|
|
||||||
## [2.1.0](https://github.com/httpie/httpie/compare/2.0.0...2.1.0) (2020-04-18)
|
## [2.1.0](https://github.com/httpie/cli/compare/2.0.0...2.1.0) (2020-04-18)
|
||||||
|
|
||||||
- Added `--path-as-is` to bypass dot segment (`/../` or `/./`)
|
- Added `--path-as-is` to bypass dot segment (`/../` or `/./`)
|
||||||
URL squashing ([#895](https://github.com/httpie/httpie/issues/895)).
|
URL squashing ([#895](https://github.com/httpie/cli/issues/895)).
|
||||||
- Changed the default `Accept` header value for JSON requests from
|
- Changed the default `Accept` header value for JSON requests from
|
||||||
`application/json, */*` to `application/json, */*;q=0.5`
|
`application/json, */*` to `application/json, */*;q=0.5`
|
||||||
to clearly indicate preference ([#488](https://github.com/httpie/httpie/issues/488)).
|
to clearly indicate preference ([#488](https://github.com/httpie/cli/issues/488)).
|
||||||
- Fixed `--form` file upload mixed with redirected `stdin` error handling
|
- Fixed `--form` file upload mixed with redirected `stdin` error handling
|
||||||
([#840](https://github.com/httpie/httpie/issues/840)).
|
([#840](https://github.com/httpie/cli/issues/840)).
|
||||||
|
|
||||||
## [2.0.0](https://github.com/httpie/httpie/compare/1.0.3...2.0.0) (2020-01-12)
|
## [2.0.0](https://github.com/httpie/cli/compare/1.0.3...2.0.0) (2020-01-12)
|
||||||
|
|
||||||
- Removed Python 2.7 support ([EOL Jan 2020](https://www.python.org/doc/sunset-python-2/).
|
- Removed Python 2.7 support ([EOL Jan 2020](https://www.python.org/doc/sunset-python-2/).
|
||||||
- Added `--offline` to allow building an HTTP request and printing it but not
|
- Added `--offline` to allow building an HTTP request and printing it but not
|
||||||
@ -117,7 +175,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Fixed an error when `stdin` was a closed fd.
|
- Fixed an error when `stdin` was a closed fd.
|
||||||
- Improved `--debug` output formatting.
|
- Improved `--debug` output formatting.
|
||||||
|
|
||||||
## [1.0.3](https://github.com/httpie/httpie/compare/1.0.2...1.0.3) (2019-08-26)
|
## [1.0.3](https://github.com/httpie/cli/compare/1.0.2...1.0.3) (2019-08-26)
|
||||||
|
|
||||||
- Fixed CVE-2019-10751 — the way the output filename is generated for
|
- Fixed CVE-2019-10751 — the way the output filename is generated for
|
||||||
`--download` requests without `--output` resulting in a redirect has
|
`--download` requests without `--output` resulting in a redirect has
|
||||||
@ -143,15 +201,15 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
|
|
||||||
Reported by Raul Onitza and Giulio Comi.
|
Reported by Raul Onitza and Giulio Comi.
|
||||||
|
|
||||||
## [1.0.2](https://github.com/httpie/httpie/compare/1.0.1...1.0.2) (2018-11-14)
|
## [1.0.2](https://github.com/httpie/cli/compare/1.0.1...1.0.2) (2018-11-14)
|
||||||
|
|
||||||
- Fixed tests for installation with pyOpenSSL.
|
- Fixed tests for installation with pyOpenSSL.
|
||||||
|
|
||||||
## [1.0.1](https://github.com/httpie/httpie/compare/1.0.0...1.0.1) (2018-11-14)
|
## [1.0.1](https://github.com/httpie/cli/compare/1.0.0...1.0.1) (2018-11-14)
|
||||||
|
|
||||||
- Removed external URL calls from tests.
|
- Removed external URL calls from tests.
|
||||||
|
|
||||||
## [1.0.0](https://github.com/httpie/httpie/compare/0.9.9...1.0.0) (2018-11-02)
|
## [1.0.0](https://github.com/httpie/cli/compare/0.9.9...1.0.0) (2018-11-02)
|
||||||
|
|
||||||
- Added `--style=auto` which follows the terminal ANSI color styles.
|
- Added `--style=auto` which follows the terminal ANSI color styles.
|
||||||
- Added support for selecting TLS 1.3 via `--ssl=tls1.3`
|
- Added support for selecting TLS 1.3 via `--ssl=tls1.3`
|
||||||
@ -162,11 +220,11 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Fixed default headers being incorrectly case-sensitive.
|
- Fixed default headers being incorrectly case-sensitive.
|
||||||
- Removed Python 2.6 support.
|
- Removed Python 2.6 support.
|
||||||
|
|
||||||
## [0.9.9](https://github.com/httpie/httpie/compare/0.9.8...0.9.9) (2016-12-08)
|
## [0.9.9](https://github.com/httpie/cli/compare/0.9.8...0.9.9) (2016-12-08)
|
||||||
|
|
||||||
- Fixed README.
|
- Fixed README.
|
||||||
|
|
||||||
## [0.9.8](https://github.com/httpie/httpie/compare/0.9.6...0.9.8) (2016-12-08)
|
## [0.9.8](https://github.com/httpie/cli/compare/0.9.6...0.9.8) (2016-12-08)
|
||||||
|
|
||||||
- Extended auth plugin API.
|
- Extended auth plugin API.
|
||||||
- Added exit status code `7` for plugin errors.
|
- Added exit status code `7` for plugin errors.
|
||||||
@ -175,7 +233,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Improved `CTRL-C` interrupt handling.
|
- Improved `CTRL-C` interrupt handling.
|
||||||
- Added the standard exit status code `130` for keyboard interrupts.
|
- Added the standard exit status code `130` for keyboard interrupts.
|
||||||
|
|
||||||
## [0.9.6](https://github.com/httpie/httpie/compare/0.9.4...0.9.6) (2016-08-13)
|
## [0.9.6](https://github.com/httpie/cli/compare/0.9.4...0.9.6) (2016-08-13)
|
||||||
|
|
||||||
- Added Python 3 as a dependency for Homebrew installations
|
- Added Python 3 as a dependency for Homebrew installations
|
||||||
to ensure some of the newer HTTP features work out of the box
|
to ensure some of the newer HTTP features work out of the box
|
||||||
@ -194,7 +252,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Changed the pre-processing of request HTTP headers so that any leading
|
- Changed the pre-processing of request HTTP headers so that any leading
|
||||||
and trailing whitespace is removed.
|
and trailing whitespace is removed.
|
||||||
|
|
||||||
## [0.9.4](https://github.com/httpie/httpie/compare/0.9.3...0.9.4) (2016-07-01)
|
## [0.9.4](https://github.com/httpie/cli/compare/0.9.3...0.9.4) (2016-07-01)
|
||||||
|
|
||||||
- Added `Content-Type` of files uploaded in `multipart/form-data` requests
|
- Added `Content-Type` of files uploaded in `multipart/form-data` requests
|
||||||
- Added `--ssl=<PROTOCOL>` to specify the desired SSL/TLS protocol version
|
- Added `--ssl=<PROTOCOL>` to specify the desired SSL/TLS protocol version
|
||||||
@ -218,7 +276,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Fixed the handling of `Content-Type` with multiple `+subtype` parts
|
- Fixed the handling of `Content-Type` with multiple `+subtype` parts
|
||||||
- Removed the XML formatter as the implementation suffered from multiple issues
|
- Removed the XML formatter as the implementation suffered from multiple issues
|
||||||
|
|
||||||
## [0.9.3](https://github.com/httpie/httpie/compare/0.9.2...0.9.3) (2016-01-01)
|
## [0.9.3](https://github.com/httpie/cli/compare/0.9.2...0.9.3) (2016-01-01)
|
||||||
|
|
||||||
- Changed the default color `--style` from `solarized` to `monokai`
|
- Changed the default color `--style` from `solarized` to `monokai`
|
||||||
- Added basic Bash autocomplete support (need to be installed manually)
|
- Added basic Bash autocomplete support (need to be installed manually)
|
||||||
@ -228,19 +286,19 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Fixed colors and formatting on Windows
|
- Fixed colors and formatting on Windows
|
||||||
- Fixed `--auth` prompt on Windows
|
- Fixed `--auth` prompt on Windows
|
||||||
|
|
||||||
## [0.9.2](https://github.com/httpie/httpie/compare/0.9.1...0.9.2) (2015-02-24)
|
## [0.9.2](https://github.com/httpie/cli/compare/0.9.1...0.9.2) (2015-02-24)
|
||||||
|
|
||||||
- Fixed compatibility with Requests 2.5.1
|
- Fixed compatibility with Requests 2.5.1
|
||||||
- Changed the default JSON `Content-Type` to `application/json` as UTF-8
|
- Changed the default JSON `Content-Type` to `application/json` as UTF-8
|
||||||
is the default JSON encoding
|
is the default JSON encoding
|
||||||
|
|
||||||
## [0.9.1](https://github.com/httpie/httpie/compare/0.9.0...0.9.1) (2015-02-07)
|
## [0.9.1](https://github.com/httpie/cli/compare/0.9.0...0.9.1) (2015-02-07)
|
||||||
|
|
||||||
- Added support for Requests transport adapter plugins
|
- Added support for Requests transport adapter plugins
|
||||||
(see [httpie-unixsocket](https://github.com/httpie/httpie-unixsocket)
|
(see [httpie-unixsocket](https://github.com/httpie/httpie-unixsocket)
|
||||||
and [httpie-http2](https://github.com/httpie/httpie-http2))
|
and [httpie-http2](https://github.com/httpie/httpie-http2))
|
||||||
|
|
||||||
## [0.9.0](https://github.com/httpie/httpie/compare/0.8.0...0.9.0) (2015-01-31)
|
## [0.9.0](https://github.com/httpie/cli/compare/0.8.0...0.9.0) (2015-01-31)
|
||||||
|
|
||||||
- Added `--cert` and `--cert-key` parameters to specify a client side
|
- Added `--cert` and `--cert-key` parameters to specify a client side
|
||||||
certificate and private key for SSL
|
certificate and private key for SSL
|
||||||
@ -259,7 +317,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Fixed `--output=/dev/null` on Linux
|
- Fixed `--output=/dev/null` on Linux
|
||||||
- Miscellaneous bugfixes
|
- Miscellaneous bugfixes
|
||||||
|
|
||||||
## [0.8.0](https://github.com/httpie/httpie/compare/0.7.1...0.8.0) (2014-01-25)
|
## [0.8.0](https://github.com/httpie/cli/compare/0.7.1...0.8.0) (2014-01-25)
|
||||||
|
|
||||||
- Added `field=@file.txt` and `field:=@file.json` for embedding
|
- Added `field=@file.txt` and `field:=@file.json` for embedding
|
||||||
the contents of text and JSON files into request data
|
the contents of text and JSON files into request data
|
||||||
@ -267,7 +325,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Fixed request `Host` header value output so that it doesn't contain
|
- Fixed request `Host` header value output so that it doesn't contain
|
||||||
credentials, if included in the URL
|
credentials, if included in the URL
|
||||||
|
|
||||||
## [0.7.1](https://github.com/httpie/httpie/compare/0.6.0...0.7.1) (2013-09-24)
|
## [0.7.1](https://github.com/httpie/cli/compare/0.6.0...0.7.1) (2013-09-24)
|
||||||
|
|
||||||
- Added `--ignore-stdin`
|
- Added `--ignore-stdin`
|
||||||
- Added support for auth plugins
|
- Added support for auth plugins
|
||||||
@ -275,27 +333,27 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Improved `Content-Disposition` parsing for `--download` mode
|
- Improved `Content-Disposition` parsing for `--download` mode
|
||||||
- Update to Requests 2.0.0
|
- Update to Requests 2.0.0
|
||||||
|
|
||||||
## [0.6.0](https://github.com/httpie/httpie/compare/0.5.1...0.6.0) (2013-06-03)
|
## [0.6.0](https://github.com/httpie/cli/compare/0.5.1...0.6.0) (2013-06-03)
|
||||||
|
|
||||||
- XML data is now formatted
|
- XML data is now formatted
|
||||||
- `--session` and `--session-read-only` now also accept paths to
|
- `--session` and `--session-read-only` now also accept paths to
|
||||||
session files (eg. `http --session=/tmp/session.json example.org`)
|
session files (eg. `http --session=/tmp/session.json example.org`)
|
||||||
|
|
||||||
## [0.5.1](https://github.com/httpie/httpie/compare/0.5.0...0.5.1) (2013-05-13)
|
## [0.5.1](https://github.com/httpie/cli/compare/0.5.0...0.5.1) (2013-05-13)
|
||||||
|
|
||||||
- `Content-*` and `If-*` request headers are not stored in sessions
|
- `Content-*` and `If-*` request headers are not stored in sessions
|
||||||
anymore as they are request-specific
|
anymore as they are request-specific
|
||||||
|
|
||||||
## [0.5.0](https://github.com/httpie/httpie/compare/0.4.1...0.5.0) (2013-04-27)
|
## [0.5.0](https://github.com/httpie/cli/compare/0.4.1...0.5.0) (2013-04-27)
|
||||||
|
|
||||||
- Added a download mode via `--download`
|
- Added a download mode via `--download`
|
||||||
- Fixes miscellaneous bugs
|
- Fixes miscellaneous bugs
|
||||||
|
|
||||||
## [0.4.1](https://github.com/httpie/httpie/compare/0.4.0...0.4.1) (2013-02-26)
|
## [0.4.1](https://github.com/httpie/cli/compare/0.4.0...0.4.1) (2013-02-26)
|
||||||
|
|
||||||
- Fixed `setup.py`
|
- Fixed `setup.py`
|
||||||
|
|
||||||
## [0.4.0](https://github.com/httpie/httpie/compare/0.3.0...0.4.0) (2013-02-22)
|
## [0.4.0](https://github.com/httpie/cli/compare/0.3.0...0.4.0) (2013-02-22)
|
||||||
|
|
||||||
- Added Python 3.3 compatibility
|
- Added Python 3.3 compatibility
|
||||||
- Added Requests >= v1.0.4 compatibility
|
- Added Requests >= v1.0.4 compatibility
|
||||||
@ -304,7 +362,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Mutually exclusive arguments can be specified multiple times. The
|
- Mutually exclusive arguments can be specified multiple times. The
|
||||||
last value is used
|
last value is used
|
||||||
|
|
||||||
## [0.3.0](https://github.com/httpie/httpie/compare/0.2.7...0.3.0) (2012-09-21)
|
## [0.3.0](https://github.com/httpie/cli/compare/0.2.7...0.3.0) (2012-09-21)
|
||||||
|
|
||||||
- Allow output redirection on Windows
|
- Allow output redirection on Windows
|
||||||
- Added configuration file
|
- Added configuration file
|
||||||
@ -319,7 +377,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
(`--pretty=all`, `--pretty=colors` and `--pretty=format`)
|
(`--pretty=all`, `--pretty=colors` and `--pretty=format`)
|
||||||
`--ugly` has bee removed in favor of `--pretty=none`
|
`--ugly` has bee removed in favor of `--pretty=none`
|
||||||
|
|
||||||
## [0.2.7](https://github.com/httpie/httpie/compare/0.2.5...0.2.7) (2012-08-07)
|
## [0.2.7](https://github.com/httpie/cli/compare/0.2.5...0.2.7) (2012-08-07)
|
||||||
|
|
||||||
- Added compatibility with Requests 0.13.6
|
- Added compatibility with Requests 0.13.6
|
||||||
- Added streamed terminal output. `--stream, -S` can be used to enable
|
- Added streamed terminal output. `--stream, -S` can be used to enable
|
||||||
@ -336,7 +394,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Fixed printing of `multipart/form-data` requests
|
- Fixed printing of `multipart/form-data` requests
|
||||||
- Renamed `--traceback` to `--debug`
|
- Renamed `--traceback` to `--debug`
|
||||||
|
|
||||||
## [0.2.6](https://github.com/httpie/httpie/compare/0.2.5...0.2.6) (2012-07-26)
|
## [0.2.6](https://github.com/httpie/cli/compare/0.2.5...0.2.6) (2012-07-26)
|
||||||
|
|
||||||
- The short option for `--headers` is now `-h` (`-t` has been
|
- The short option for `--headers` is now `-h` (`-t` has been
|
||||||
removed, for usage use `--help`)
|
removed, for usage use `--help`)
|
||||||
@ -351,7 +409,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Added query string parameters (`param==value`)
|
- Added query string parameters (`param==value`)
|
||||||
- Added support for terminal colors under Windows
|
- Added support for terminal colors under Windows
|
||||||
|
|
||||||
## [0.2.5](https://github.com/httpie/httpie/compare/0.2.2...0.2.5) (2012-07-17)
|
## [0.2.5](https://github.com/httpie/cli/compare/0.2.2...0.2.5) (2012-07-17)
|
||||||
|
|
||||||
- Unicode characters in prettified JSON now don't get escaped for
|
- Unicode characters in prettified JSON now don't get escaped for
|
||||||
improved readability
|
improved readability
|
||||||
@ -362,20 +420,20 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
`--verbose`
|
`--verbose`
|
||||||
- Fixed Content-Type for requests with no data
|
- Fixed Content-Type for requests with no data
|
||||||
|
|
||||||
## [0.2.2](https://github.com/httpie/httpie/compare/0.2.1...0.2.2) (2012-06-24)
|
## [0.2.2](https://github.com/httpie/cli/compare/0.2.1...0.2.2) (2012-06-24)
|
||||||
|
|
||||||
- The `METHOD` positional argument can now be omitted (defaults to
|
- The `METHOD` positional argument can now be omitted (defaults to
|
||||||
`GET`, or to `POST` with data)
|
`GET`, or to `POST` with data)
|
||||||
- Fixed --verbose --form
|
- Fixed --verbose --form
|
||||||
- Added support for Tox
|
- Added support for Tox
|
||||||
|
|
||||||
## [0.2.1](https://github.com/httpie/httpie/compare/0.2.0...0.2.1) (2012-06-13)
|
## [0.2.1](https://github.com/httpie/cli/compare/0.2.0...0.2.1) (2012-06-13)
|
||||||
|
|
||||||
- Added compatibility with `requests-0.12.1`
|
- Added compatibility with `requests-0.12.1`
|
||||||
- Dropped custom JSON and HTTP lexers in favor of the ones newly included
|
- Dropped custom JSON and HTTP lexers in favor of the ones newly included
|
||||||
in `pygments-1.5`
|
in `pygments-1.5`
|
||||||
|
|
||||||
## [0.2.0](https://github.com/httpie/httpie/compare/0.1.6...0.2.0) (2012-04-25)
|
## [0.2.0](https://github.com/httpie/cli/compare/0.1.6...0.2.0) (2012-04-25)
|
||||||
|
|
||||||
- Added Python 3 support
|
- Added Python 3 support
|
||||||
- Added the ability to print the HTTP request as well as the response
|
- Added the ability to print the HTTP request as well as the response
|
||||||
@ -387,18 +445,18 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|||||||
- Added support for field name escaping
|
- Added support for field name escaping
|
||||||
- Many bug fixes
|
- Many bug fixes
|
||||||
|
|
||||||
## [0.1.6](https://github.com/httpie/httpie/compare/0.1.5...0.1.6) (2012-03-04)
|
## [0.1.6](https://github.com/httpie/cli/compare/0.1.5...0.1.6) (2012-03-04)
|
||||||
|
|
||||||
- Fixed `setup.py`
|
- Fixed `setup.py`
|
||||||
|
|
||||||
## [0.1.5](https://github.com/httpie/httpie/compare/0.1.4...0.1.5) (2012-03-04)
|
## [0.1.5](https://github.com/httpie/cli/compare/0.1.4...0.1.5) (2012-03-04)
|
||||||
|
|
||||||
- Many improvements and bug fixes
|
- Many improvements and bug fixes
|
||||||
|
|
||||||
## [0.1.4](https://github.com/httpie/httpie/compare/b966efa...0.1.4) (2012-02-28)
|
## [0.1.4](https://github.com/httpie/cli/compare/b966efa...0.1.4) (2012-02-28)
|
||||||
|
|
||||||
- Many improvements and bug fixes
|
- Many improvements and bug fixes
|
||||||
|
|
||||||
## [0.1.0](https://github.com/httpie/httpie/commit/b966efa) (2012-02-25)
|
## [0.1.0](https://github.com/httpie/cli/commit/b966efa) (2012-02-25)
|
||||||
|
|
||||||
- Initial public release
|
- Initial public release
|
||||||
|
@ -19,7 +19,7 @@ $ http --debug <COMPLETE ARGUMENT LIST THAT TRIGGERS THE ERROR>
|
|||||||
|
|
||||||
## 2. Contributing Code and Docs
|
## 2. Contributing Code and Docs
|
||||||
|
|
||||||
Before working on a new feature or a bug, please browse [existing issues](https://github.com/httpie/httpie/issues)
|
Before working on a new feature or a bug, please browse [existing issues](https://github.com/httpie/cli/issues)
|
||||||
to see whether it has previously been discussed.
|
to see whether it has previously been discussed.
|
||||||
|
|
||||||
If your change alters HTTPie’s behaviour or interface, it's a good idea to
|
If your change alters HTTPie’s behaviour or interface, it's a good idea to
|
||||||
@ -38,13 +38,13 @@ for existing-yet-previously-untested behavior will very likely be merged.
|
|||||||
Therefore, docs and tests improvements are a great candidate for your first
|
Therefore, docs and tests improvements are a great candidate for your first
|
||||||
contribution.
|
contribution.
|
||||||
|
|
||||||
Consider also adding a [CHANGELOG](https://github.com/httpie/httpie/blob/master/CHANGELOG.md) entry for your changes.
|
Consider also adding a [CHANGELOG](https://github.com/httpie/cli/blob/master/CHANGELOG.md) entry for your changes.
|
||||||
|
|
||||||
### Development Environment
|
### Development Environment
|
||||||
|
|
||||||
#### Getting the code
|
#### Getting the code
|
||||||
|
|
||||||
Go to <https://github.com/httpie/httpie> and fork the project repository.
|
Go to <https://github.com/httpie/cli> and fork the project repository.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone your fork
|
# Clone your fork
|
||||||
@ -59,8 +59,10 @@ $ git checkout -b my_topical_branch
|
|||||||
|
|
||||||
#### Setup
|
#### Setup
|
||||||
|
|
||||||
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started. Just run
|
The [Makefile](https://github.com/httpie/cli/blob/master/Makefile) contains a bunch of tasks to get you started.
|
||||||
the following command, which:
|
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`
|
- Creates an isolated Python virtual environment inside `./venv`
|
||||||
(via the standard library [venv](https://docs.python.org/3/library/venv.html) tool);
|
(via the standard library [venv](https://docs.python.org/3/library/venv.html) tool);
|
||||||
@ -70,7 +72,7 @@ the following command, which:
|
|||||||
- and runs tests (It is the same as running `make install test`).
|
- and runs tests (It is the same as running `make install test`).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ make
|
$ make all
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Python virtual environment
|
#### Python virtual environment
|
||||||
@ -110,7 +112,7 @@ and that `make pycodestyle` passes.
|
|||||||
|
|
||||||
Please add tests for any new features and bug fixes.
|
Please add tests for any new features and bug fixes.
|
||||||
|
|
||||||
When you open a Pull Request, [GitHub Actions](https://github.com/httpie/httpie/actions) will automatically run HTTPie’s [test suite](https://github.com/httpie/httpie/tree/master/tests) against your code, so please make sure all checks pass.
|
When you open a Pull Request, [GitHub Actions](https://github.com/httpie/cli/actions) will automatically run HTTPie’s [test suite](https://github.com/httpie/cli/tree/master/tests) against your code, so please make sure all checks pass.
|
||||||
|
|
||||||
#### Running tests locally
|
#### Running tests locally
|
||||||
|
|
||||||
@ -142,7 +144,7 @@ $ python -m pytest tests/test_uploads.py::TestMultipartFormDataFileUpload
|
|||||||
$ python -m pytest tests/test_uploads.py::TestMultipartFormDataFileUpload::test_upload_ok
|
$ python -m pytest tests/test_uploads.py::TestMultipartFormDataFileUpload::test_upload_ok
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) for additional development utilities.
|
See [Makefile](https://github.com/httpie/cli/blob/master/Makefile) for additional development utilities.
|
||||||
|
|
||||||
#### Running benchmarks
|
#### Running benchmarks
|
||||||
|
|
||||||
@ -152,7 +154,7 @@ with the master branch of your repository (or a fresh checkout of HTTPie master,
|
|||||||
`--fresh`) and report the results back.
|
`--fresh`) and report the results back.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ python extras/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
|
The benchmarks can also be run on the CI. Since it is a long process, it requires manual
|
||||||
@ -207,4 +209,4 @@ $ python -m pytest
|
|||||||
|
|
||||||
______________________________________________________________________
|
______________________________________________________________________
|
||||||
|
|
||||||
Finally, feel free to add yourself to [AUTHORS](https://github.com/httpie/httpie/blob/master/AUTHORS.md)!
|
Finally, feel free to add yourself to [AUTHORS](https://github.com/httpie/cli/blob/master/AUTHORS.md)!
|
||||||
|
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
|||||||
Copyright © 2012-2021 Jakub Roztocil <jakub@roztocil.co>
|
Copyright © 2012-2022 Jakub Roztocil <jakub@roztocil.co>
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are met:
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
@ -4,5 +4,5 @@ include CHANGELOG.md
|
|||||||
include AUTHORS.md
|
include AUTHORS.md
|
||||||
include docs/README.md
|
include docs/README.md
|
||||||
|
|
||||||
# <https://github.com/httpie/httpie/issues/182>
|
# <https://github.com/httpie/cli/issues/182>
|
||||||
recursive-include tests/ *
|
recursive-include tests/ *
|
||||||
|
62
Makefile
62
Makefile
@ -22,6 +22,26 @@ VENV_PYTHON=$(VENV_BIN)/python
|
|||||||
export PATH := $(VENV_BIN):$(PATH)
|
export PATH := $(VENV_BIN):$(PATH)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
default: list-tasks
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Default task to get a list of tasks when `make' is run without args.
|
||||||
|
# <https://stackoverflow.com/questions/4219255>
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
list-tasks:
|
||||||
|
@echo Available tasks:
|
||||||
|
@echo ----------------
|
||||||
|
@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | grep -E -v -e '^[^[:alnum:]]' -e '^$@$$'
|
||||||
|
@echo
|
||||||
|
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Installation
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
all: uninstall-httpie install test
|
all: uninstall-httpie install test
|
||||||
|
|
||||||
|
|
||||||
@ -30,10 +50,10 @@ install: venv install-reqs
|
|||||||
|
|
||||||
install-reqs:
|
install-reqs:
|
||||||
@echo $(H1)Updating package tools$(H1END)
|
@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)
|
@echo $(H1)Installing dev requirements$(H1END)
|
||||||
$(VENV_PIP) install --upgrade --editable '.[dev]'
|
$(VENV_PIP) install --upgrade '.[dev]' '.[test]'
|
||||||
|
|
||||||
@echo $(H1)Installing HTTPie$(H1END)
|
@echo $(H1)Installing HTTPie$(H1END)
|
||||||
$(VENV_PIP) install --upgrade --editable .
|
$(VENV_PIP) install --upgrade --editable .
|
||||||
@ -104,7 +124,8 @@ test-dist: test-sdist test-bdist-wheel
|
|||||||
|
|
||||||
test-sdist: clean venv
|
test-sdist: clean venv
|
||||||
@echo $(H1)Testing sdist build an installation$(H1END)
|
@echo $(H1)Testing sdist build an installation$(H1END)
|
||||||
$(VENV_PYTHON) setup.py sdist
|
$(VENV_PIP) install build
|
||||||
|
$(VENV_PYTHON) -m build --sdist
|
||||||
$(VENV_PIP) install --force-reinstall --upgrade dist/*.gz
|
$(VENV_PIP) install --force-reinstall --upgrade dist/*.gz
|
||||||
$(VENV_BIN)/http --version
|
$(VENV_BIN)/http --version
|
||||||
@echo
|
@echo
|
||||||
@ -112,8 +133,8 @@ test-sdist: clean venv
|
|||||||
|
|
||||||
test-bdist-wheel: clean venv
|
test-bdist-wheel: clean venv
|
||||||
@echo $(H1)Testing wheel build an installation$(H1END)
|
@echo $(H1)Testing wheel build an installation$(H1END)
|
||||||
$(VENV_PIP) install wheel
|
$(VENV_PIP) install build
|
||||||
$(VENV_PYTHON) setup.py bdist_wheel
|
$(VENV_PYTHON) -m build --wheel
|
||||||
$(VENV_PIP) install --force-reinstall --upgrade dist/*.whl
|
$(VENV_PIP) install --force-reinstall --upgrade dist/*.whl
|
||||||
$(VENV_BIN)/http --version
|
$(VENV_BIN)/http --version
|
||||||
@echo
|
@echo
|
||||||
@ -147,19 +168,17 @@ doc-check:
|
|||||||
mdl --git-recurse --style docs/markdownlint.rb .
|
mdl --git-recurse --style docs/markdownlint.rb .
|
||||||
|
|
||||||
|
|
||||||
doc-update-install:
|
|
||||||
@echo $(H1)Updating installation instructions in the docs$(H1END)
|
|
||||||
$(VENV_PYTHON) docs/installation/generate.py
|
|
||||||
|
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Publishing to PyPi
|
# Publishing to PyPi
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
rm -rf build/
|
rm -rf build/ dist/
|
||||||
$(VENV_PYTHON) setup.py sdist bdist_wheel
|
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
|
publish: test-all publish-no-test
|
||||||
@ -203,10 +222,25 @@ brew-test:
|
|||||||
- brew uninstall httpie
|
- brew uninstall httpie
|
||||||
|
|
||||||
@echo $(H1)Building from source…$(H1END)
|
@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)
|
@echo $(H1)Verifying…$(H1END)
|
||||||
brew test httpie
|
http --version
|
||||||
|
https --version
|
||||||
|
|
||||||
@echo $(H1)Auditing…$(H1END)
|
@echo $(H1)Auditing…$(H1END)
|
||||||
brew audit --strict httpie
|
brew audit --strict httpie
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Generated content
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
content: man installation-docs
|
||||||
|
|
||||||
|
man: install
|
||||||
|
@echo $(H1)Regenerate man pages$(H1END)
|
||||||
|
$(VENV_PYTHON) extras/scripts/generate_man_pages.py
|
||||||
|
|
||||||
|
installation-docs:
|
||||||
|
@echo $(H1)Updating installation instructions in the docs$(H1END)
|
||||||
|
$(VENV_PYTHON) docs/installation/generate.py
|
||||||
|
73
README.md
73
README.md
@ -1,10 +1,31 @@
|
|||||||
<br/>
|
<h2 align="center">
|
||||||
<a href="https://httpie.io" target="blank_">
|
<a href="https://httpie.io" target="blank_">
|
||||||
<img height="100" alt="HTTPie" src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-logo.svg" />
|
<img height="100" alt="HTTPie" src="https://raw.githubusercontent.com/httpie/cli/master/docs/httpie-logo.svg" />
|
||||||
</a>
|
</a>
|
||||||
<br/>
|
<br>
|
||||||
|
HTTPie CLI: human-friendly HTTP client for the API era
|
||||||
|
</h2>
|
||||||
|
|
||||||
# HTTPie: human-friendly CLI HTTP client for the API era
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://httpie.io/product)
|
||||||
|
[](https://httpie.io/app)
|
||||||
|
[](https://httpie.io/cli)
|
||||||
|
[](https://twitter.com/httpie)
|
||||||
|
[](https://httpie.io/discord)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://httpie.org/docs/cli)
|
||||||
|
[](https://pypi.python.org/pypi/httpie)
|
||||||
|
[](https://github.com/httpie/cli/actions)
|
||||||
|
[](https://codecov.io/gh/httpie/cli)
|
||||||
|
[](https://www.pepy.tech/projects/httpie)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
HTTPie (pronounced _aitch-tee-tee-pie_) is a command-line HTTP client.
|
HTTPie (pronounced _aitch-tee-tee-pie_) is a command-line HTTP client.
|
||||||
Its goal is to make CLI interaction with web services as human-friendly as possible.
|
Its goal is to make CLI interaction with web services as human-friendly as possible.
|
||||||
@ -12,14 +33,22 @@ HTTPie is designed for testing, debugging, and generally interacting with APIs &
|
|||||||
The `http` & `https` commands allow for creating and sending arbitrary HTTP requests.
|
The `http` & `https` commands allow for creating and sending arbitrary HTTP requests.
|
||||||
They use simple and natural syntax and provide formatted and colorized output.
|
They use simple and natural syntax and provide formatted and colorized output.
|
||||||
|
|
||||||
[](https://httpie.org/docs)
|
<div align="center">
|
||||||
[](https://pypi.python.org/pypi/httpie)
|
|
||||||
[](https://github.com/httpie/httpie/actions)
|
<img src="https://raw.githubusercontent.com/httpie/cli/master/docs/httpie-animation.gif" alt="HTTPie in action" width="100%"/>
|
||||||
[](https://codecov.io/gh/httpie/httpie)
|
|
||||||
[](https://twitter.com/httpie)
|
|
||||||
[](https://httpie.io/discord)
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## We lost 54k GitHub stars
|
||||||
|
|
||||||
|
Please note we recently accidentally made this repo private for a moment, and GitHub deleted our community that took a decade to build. Read the full story here: https://httpie.io/blog/stardust
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-animation.gif" alt="HTTPie in action" width="100%"/>
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
@ -45,25 +74,25 @@ They use simple and natural syntax and provide formatted and colorized output.
|
|||||||
Hello World:
|
Hello World:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ https httpie.io/hello
|
https httpie.io/hello
|
||||||
```
|
```
|
||||||
|
|
||||||
Custom [HTTP method](https://httpie.io/docs#http-method), [HTTP headers](https://httpie.io/docs#http-headers) and [JSON](https://httpie.io/docs#json) data:
|
Custom [HTTP method](https://httpie.io/docs#http-method), [HTTP headers](https://httpie.io/docs#http-headers) and [JSON](https://httpie.io/docs#json) data:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ http PUT pie.dev/put X-API-Token:123 name=John
|
http PUT pie.dev/put X-API-Token:123 name=John
|
||||||
```
|
```
|
||||||
|
|
||||||
Build and print a request without sending it using [offline mode](https://httpie.io/docs#offline-mode):
|
Build and print a request without sending it using [offline mode](https://httpie.io/docs/cli/offline-mode):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ http --offline pie.dev/post hello=offline
|
http --offline pie.dev/post hello=offline
|
||||||
```
|
```
|
||||||
|
|
||||||
Use [GitHub API](https://developer.github.com/v3/issues/comments/#create-a-comment) to post a comment on an [Issue](https://github.com/httpie/httpie/issues/83) with [authentication](https://httpie.io/docs#authentication):
|
Use [GitHub API](https://developer.github.com/v3/issues/comments/#create-a-comment) to post a comment on an [Issue](https://github.com/httpie/cli/issues/83) with [authentication](https://httpie.io/docs#authentication):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ http -a USERNAME POST https://api.github.com/repos/httpie/httpie/issues/83/comments body='HTTPie is awesome! :heart:'
|
http -a USERNAME POST https://api.github.com/repos/httpie/cli/issues/83/comments body='HTTPie is awesome! :heart:'
|
||||||
```
|
```
|
||||||
|
|
||||||
[See more examples →](https://httpie.io/docs#examples)
|
[See more examples →](https://httpie.io/docs#examples)
|
||||||
@ -74,11 +103,11 @@ $ http -a USERNAME POST https://api.github.com/repos/httpie/httpie/issues/83/com
|
|||||||
- Join our [Discord server](https://httpie.io/discord) is to ask questions, discuss features, and for general API chat.
|
- Join our [Discord server](https://httpie.io/discord) is to ask questions, discuss features, and for general API chat.
|
||||||
- Tweet at [@httpie](https://twitter.com/httpie) on Twitter.
|
- Tweet at [@httpie](https://twitter.com/httpie) on Twitter.
|
||||||
- Use [StackOverflow](https://stackoverflow.com/questions/tagged/httpie) to ask questions and include a `httpie` tag.
|
- Use [StackOverflow](https://stackoverflow.com/questions/tagged/httpie) to ask questions and include a `httpie` tag.
|
||||||
- Create [GitHub Issues](https://github.com/httpie/httpie/issues) for bug reports and feature requests.
|
- Create [GitHub Issues](https://github.com/httpie/cli/issues) for bug reports and feature requests.
|
||||||
- Subscribe to the [HTTPie newsletter](https://httpie.io) for occasional updates.
|
- Subscribe to the [HTTPie newsletter](https://httpie.io) for occasional updates.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Have a look through existing [Issues](https://github.com/httpie/httpie/issues) and [Pull Requests](https://github.com/httpie/httpie/pulls) that you could help with. If you'd like to request a feature or report a bug, please [create a GitHub Issue](https://github.com/httpie/httpie/issues) using one of the templates provided.
|
Have a look through existing [Issues](https://github.com/httpie/cli/issues) and [Pull Requests](https://github.com/httpie/cli/pulls) that you could help with. If you'd like to request a feature or report a bug, please [create a GitHub Issue](https://github.com/httpie/cli/issues) using one of the templates provided.
|
||||||
|
|
||||||
[See contribution guide →](https://github.com/httpie/httpie/blob/master/CONTRIBUTING.md)
|
[See contribution guide →](https://github.com/httpie/cli/blob/master/CONTRIBUTING.md)
|
||||||
|
14
SECURITY.md
Normal file
14
SECURITY.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Security policy
|
||||||
|
|
||||||
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
When you identify a vulnerability in HTTPie, please report it privately using one of the following channels:
|
||||||
|
|
||||||
|
- Email to [`security@httpie.io`](mailto:security@httpie.io)
|
||||||
|
- Report on [huntr.dev](https://huntr.dev/)
|
||||||
|
|
||||||
|
In addition to the description of the vulnerability, include the following information:
|
||||||
|
|
||||||
|
- A short reproducer to verify it (it can be a small HTTP server, shell script, docker image, etc.)
|
||||||
|
- Your deemed severity level of the vulnerability (`LOW`/`MEDIUM`/`HIGH`/`CRITICAL`)
|
||||||
|
- [CWE](https://cwe.mitre.org/) ID, if available.
|
657
docs/README.md
657
docs/README.md
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"website": {
|
"website": {
|
||||||
"master_and_released_docs_differ_after": "d40f06687f8cbbd22bf7dba05bee93aea11a169f"
|
"master_and_released_docs_differ_after": null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
Here we maintain a database of contributors, from which we generate credits on release blog posts and social medias.
|
Here we maintain a database of contributors, from which we generate credits on release blog posts and social media.
|
||||||
|
|
||||||
For the HTTPie blog see: <https://httpie.io/blog>.
|
For the HTTPie blog see: <https://httpie.io/blog>.
|
||||||
|
@ -252,6 +252,7 @@ def fetch_missing_users_details(people: People) -> None:
|
|||||||
def save_awesome_people(people: People) -> None:
|
def save_awesome_people(people: People) -> None:
|
||||||
with DB_FILE.open(mode='w', encoding='utf-8') as fh:
|
with DB_FILE.open(mode='w', encoding='utf-8') as fh:
|
||||||
json.dump(people, fh, indent=4, sort_keys=True)
|
json.dump(people, fh, indent=4, sort_keys=True)
|
||||||
|
fh.write("\n")
|
||||||
|
|
||||||
|
|
||||||
def debug(*args: Any) -> None:
|
def debug(*args: Any) -> None:
|
||||||
|
@ -8,19 +8,27 @@ from jinja2 import Template
|
|||||||
from fetch import HERE, load_awesome_people
|
from fetch import HERE, load_awesome_people
|
||||||
|
|
||||||
TPL_FILE = HERE / 'snippet.jinja2'
|
TPL_FILE = HERE / 'snippet.jinja2'
|
||||||
|
|
||||||
HTTPIE_TEAM = {
|
HTTPIE_TEAM = {
|
||||||
'claudiatd',
|
'claudiatd',
|
||||||
'jakubroztocil',
|
'jakubroztocil',
|
||||||
'jkbr',
|
'jkbr',
|
||||||
|
'isidentical'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOT_ACCOUNTS = {
|
||||||
|
'dependabot-sr'
|
||||||
|
}
|
||||||
|
|
||||||
|
IGNORE_ACCOUNTS = HTTPIE_TEAM | BOT_ACCOUNTS
|
||||||
|
|
||||||
|
|
||||||
def generate_snippets(release: str) -> str:
|
def generate_snippets(release: str) -> str:
|
||||||
people = load_awesome_people()
|
people = load_awesome_people()
|
||||||
contributors = {
|
contributors = {
|
||||||
name: details
|
name: details
|
||||||
for name, details in people.items()
|
for name, details in people.items()
|
||||||
if details['github'] not in HTTPIE_TEAM
|
if details['github'] not in IGNORE_ACCOUNTS
|
||||||
and (release in details['committed'] or release in details['reported'])
|
and (release in details['committed'] or release in details['reported'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,45 @@
|
|||||||
{
|
{
|
||||||
|
"Aaron Miller": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "aaronhmiller",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": "aaronmiller8"
|
||||||
|
},
|
||||||
|
"Alexander Bogdanov": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "ab-kily",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Almad": {
|
"Almad": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.5.0"
|
"2.5.0"
|
||||||
],
|
],
|
||||||
"github": "Almad",
|
"github": "Almad",
|
||||||
"reported": [
|
"reported": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"twitter": "almadcz"
|
"twitter": "almadcz"
|
||||||
},
|
},
|
||||||
|
"Andr\u00e1s Czig\u00e1ny": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "andrascz",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Annette Wilson": {
|
"Annette Wilson": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "annettejanewilson",
|
"github": "annettejanewilson",
|
||||||
"reported": [
|
"reported": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
@ -25,6 +51,34 @@
|
|||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"Batuhan Taskaya": {
|
||||||
|
"committed": [
|
||||||
|
"3.0.0",
|
||||||
|
"3.2.0"
|
||||||
|
],
|
||||||
|
"github": "isidentical",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0",
|
||||||
|
"3.2.0"
|
||||||
|
],
|
||||||
|
"twitter": "isidentical"
|
||||||
|
},
|
||||||
|
"Brad Crittenden": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "bac",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"Chad": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "cythrawll",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"D8ger": {
|
"D8ger": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "caofanCPU",
|
"github": "caofanCPU",
|
||||||
@ -35,7 +89,8 @@
|
|||||||
},
|
},
|
||||||
"Dave": {
|
"Dave": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"github": "davecheney",
|
"github": "davecheney",
|
||||||
"reported": [],
|
"reported": [],
|
||||||
@ -49,6 +104,14 @@
|
|||||||
],
|
],
|
||||||
"twitter": "DawidFerenczy"
|
"twitter": "DawidFerenczy"
|
||||||
},
|
},
|
||||||
|
"Ed Rooth": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "sym3tri",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Elena Lape": {
|
"Elena Lape": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.5.0"
|
"2.5.0"
|
||||||
@ -57,11 +120,20 @@
|
|||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": "elena_lape"
|
"twitter": "elena_lape"
|
||||||
},
|
},
|
||||||
|
"Ethan Mills": {
|
||||||
|
"committed": [
|
||||||
|
"3.2.0"
|
||||||
|
],
|
||||||
|
"github": "ethanmills",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Fabio Peruzzo": {
|
"Fabio Peruzzo": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "peruzzof",
|
"github": "peruzzof",
|
||||||
"reported": [
|
"reported": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
@ -73,6 +145,22 @@
|
|||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"Gabriel Cruz": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "gmelodie",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": "gmelodiecruz"
|
||||||
|
},
|
||||||
|
"Gaurav": {
|
||||||
|
"committed": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"github": "gkcs",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Giampaolo Rodola": {
|
"Giampaolo Rodola": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "giampaolo",
|
"github": "giampaolo",
|
||||||
@ -81,6 +169,14 @@
|
|||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"Greg Myers": {
|
||||||
|
"committed": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"github": "myersg86",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Hugh Williams": {
|
"Hugh Williams": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "hughpv",
|
"github": "hughpv",
|
||||||
@ -102,21 +198,35 @@
|
|||||||
"Jakub Roztocil": {
|
"Jakub Roztocil": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.5.0",
|
"2.5.0",
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0",
|
||||||
|
"3.2.0"
|
||||||
],
|
],
|
||||||
"github": "jakubroztocil",
|
"github": "jakubroztocil",
|
||||||
"reported": [
|
"reported": [
|
||||||
"2.5.0",
|
"2.5.0",
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"twitter": "jakubroztocil"
|
"twitter": "jakubroztocil"
|
||||||
},
|
},
|
||||||
|
"Jan Bra\u0161na": {
|
||||||
|
"committed": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"github": "janbrasna",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": "janbrasna"
|
||||||
|
},
|
||||||
"Jan Verbeek": {
|
"Jan Verbeek": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.5.0"
|
"2.5.0"
|
||||||
],
|
],
|
||||||
"github": "blyxxyz",
|
"github": "blyxxyz",
|
||||||
"reported": [],
|
"reported": [
|
||||||
|
"3.0.0",
|
||||||
|
"3.2.0"
|
||||||
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
"Jannik Vieten": {
|
"Jannik Vieten": {
|
||||||
@ -127,6 +237,22 @@
|
|||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"Jesper Holmberg": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "strindberg",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"Kirill Krasnov": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "Kirill",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Marcel St\u00f6r": {
|
"Marcel St\u00f6r": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.5.0"
|
"2.5.0"
|
||||||
@ -135,6 +261,14 @@
|
|||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": "frightanic"
|
"twitter": "frightanic"
|
||||||
},
|
},
|
||||||
|
"Marco Seguri": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "seguri",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Mariano Ruiz": {
|
"Mariano Ruiz": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "mrsarm",
|
"github": "mrsarm",
|
||||||
@ -143,22 +277,41 @@
|
|||||||
],
|
],
|
||||||
"twitter": "mrsarm82"
|
"twitter": "mrsarm82"
|
||||||
},
|
},
|
||||||
|
"Mark Rosenbaum": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "markrosenbaum",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Micka\u00ebl Schoentgen": {
|
"Micka\u00ebl Schoentgen": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.5.0",
|
"2.5.0",
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"github": "BoboTiG",
|
"github": "BoboTiG",
|
||||||
"reported": [
|
"reported": [
|
||||||
"2.5.0",
|
"2.5.0",
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"twitter": "__tiger222__"
|
"twitter": "__tiger222__"
|
||||||
},
|
},
|
||||||
|
"Mike DePalatis": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "mivade",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Miro Hron\u010dok": {
|
"Miro Hron\u010dok": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.5.0",
|
"2.5.0",
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"github": "hroncok",
|
"github": "hroncok",
|
||||||
"reported": [],
|
"reported": [],
|
||||||
@ -168,20 +321,63 @@
|
|||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "ducaale",
|
"github": "ducaale",
|
||||||
"reported": [
|
"reported": [
|
||||||
"2.5.0"
|
"2.5.0",
|
||||||
|
"3.2.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"Nanashi.": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "sevenc-nanashi",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"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
|
"twitter": null
|
||||||
},
|
},
|
||||||
"Omer Akram": {
|
"Omer Akram": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"github": "om26er",
|
"github": "om26er",
|
||||||
"reported": [
|
"reported": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"twitter": "om26er"
|
"twitter": "om26er"
|
||||||
},
|
},
|
||||||
|
"Patrick Taylor": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "pmeister",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"Paul Laffitte": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "paullaffitte",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": "plaffitt"
|
||||||
|
},
|
||||||
"Pavel Alexeev aka Pahan-Hubbitus": {
|
"Pavel Alexeev aka Pahan-Hubbitus": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "Hubbitus",
|
"github": "Hubbitus",
|
||||||
@ -190,6 +386,22 @@
|
|||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"Roberto L\u00f3pez L\u00f3pez": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "robertolopezlopez",
|
||||||
|
"reported": [
|
||||||
|
"3.2.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"Russell Shurts": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "rshurts",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"Samuel Marks": {
|
"Samuel Marks": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "SamuelMarks",
|
"github": "SamuelMarks",
|
||||||
@ -198,6 +410,14 @@
|
|||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"Sebastian Czech": {
|
||||||
|
"committed": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"github": "sebastianczech",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": "sebaczech"
|
||||||
|
},
|
||||||
"Sullivan SENECHAL": {
|
"Sullivan SENECHAL": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "soullivaneuh",
|
"github": "soullivaneuh",
|
||||||
@ -218,7 +438,32 @@
|
|||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "vovtz",
|
"github": "vovtz",
|
||||||
"reported": [
|
"reported": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"Vivaan Verma": {
|
||||||
|
"committed": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"github": "doublevcodes",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": "doublevcodes"
|
||||||
|
},
|
||||||
|
"Vladimir Berkutov": {
|
||||||
|
"committed": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"github": "dair-targ",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"Will Rogers": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "wjrogers",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
@ -238,6 +483,14 @@
|
|||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"arloan": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "arloan",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"bl-ue": {
|
"bl-ue": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.5.0"
|
"2.5.0"
|
||||||
@ -246,22 +499,56 @@
|
|||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"blueray453": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "blueray453",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"claudiatd": {
|
"claudiatd": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"github": "claudiatd",
|
"github": "claudiatd",
|
||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"coldcoff": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "coldcoff",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"dependabot[bot]": {
|
||||||
|
"committed": [
|
||||||
|
"3.2.0"
|
||||||
|
],
|
||||||
|
"github": "dependabot-sr",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"dkreeft": {
|
"dkreeft": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"github": "dkreeft",
|
"github": "dkreeft",
|
||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"greg": {
|
||||||
|
"committed": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"github": "gregkh",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"henryhu712": {
|
"henryhu712": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.5.0"
|
"2.5.0"
|
||||||
@ -270,14 +557,31 @@
|
|||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"hosseingt": {
|
||||||
|
"committed": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"github": "hosseingt",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"jakubroztocil": {
|
"jakubroztocil": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"github": "jkbr",
|
"github": "jkbr",
|
||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"josephworks": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "josephworks",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"jungle-boogie": {
|
"jungle-boogie": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "jungle-boogie",
|
"github": "jungle-boogie",
|
||||||
@ -286,6 +590,22 @@
|
|||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"luisuimi": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "luisuimi",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"luzpaz": {
|
||||||
|
"committed": [
|
||||||
|
"3.2.0"
|
||||||
|
],
|
||||||
|
"github": "luzpaz",
|
||||||
|
"reported": [],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"nixbytes": {
|
"nixbytes": {
|
||||||
"committed": [
|
"committed": [
|
||||||
"2.5.0"
|
"2.5.0"
|
||||||
@ -294,6 +614,14 @@
|
|||||||
"reported": [],
|
"reported": [],
|
||||||
"twitter": "linuxbyte3"
|
"twitter": "linuxbyte3"
|
||||||
},
|
},
|
||||||
|
"peterpt": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "peterpt",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"qiulang": {
|
"qiulang": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "qiulang",
|
"github": "qiulang",
|
||||||
@ -302,6 +630,38 @@
|
|||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
|
"stonebig": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "stonebig",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"whodidthis": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "whodidthis",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"zhaohanqing95": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "zhaohanqing95",
|
||||||
|
"reported": [
|
||||||
|
"3.2.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
|
"zoulja": {
|
||||||
|
"committed": [],
|
||||||
|
"github": "zoulja",
|
||||||
|
"reported": [
|
||||||
|
"3.0.0"
|
||||||
|
],
|
||||||
|
"twitter": null
|
||||||
|
},
|
||||||
"zwx00": {
|
"zwx00": {
|
||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "zwx00",
|
"github": "zwx00",
|
||||||
@ -314,7 +674,8 @@
|
|||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "rogerdehe",
|
"github": "rogerdehe",
|
||||||
"reported": [
|
"reported": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
},
|
},
|
||||||
@ -322,7 +683,8 @@
|
|||||||
"committed": [],
|
"committed": [],
|
||||||
"github": "hh-in-zhuzhou",
|
"github": "hh-in-zhuzhou",
|
||||||
"reported": [
|
"reported": [
|
||||||
"2.6.0"
|
"2.6.0",
|
||||||
|
"3.0.0"
|
||||||
],
|
],
|
||||||
"twitter": null
|
"twitter": null
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
## Community contributions
|
## Community contributions
|
||||||
|
|
||||||
We’d like to thank these amazing people for their contributions to this release: {% for name, details in contributors.items() -%}
|
We’d like to thank these amazing people for their contributions to this release:
|
||||||
[{{ name }}](https://github.com/{{ details.github }}){{ '' if loop.last else ', ' }}
|
{% for name, details in contributors.items() -%}
|
||||||
{%- endfor %}.
|
- [{{ name }}](https://github.com/{{ details.github }}){{ '' if loop.last else '\n' }}
|
||||||
|
{%- endfor %}
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Twitter -->
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ def build_docs_structure(database: Database):
|
|||||||
tree = database[KEY_DOC_STRUCTURE]
|
tree = database[KEY_DOC_STRUCTURE]
|
||||||
structure = []
|
structure = []
|
||||||
for platform, tools_ids in tree.items():
|
for platform, tools_ids in tree.items():
|
||||||
assert platform.isalnum(), f'{platform=} must be alpha-numeric for generated links to work'
|
assert platform.isalnum(), f'{platform=} must be alphanumeric for generated links to work'
|
||||||
platform_tools = [tools[tool_id] for tool_id in tools_ids]
|
platform_tools = [tools[tool_id] for tool_id in tools_ids]
|
||||||
structure.append((platform, platform_tools))
|
structure.append((platform, platform_tools))
|
||||||
return structure
|
return structure
|
||||||
|
@ -17,11 +17,12 @@ docs-structure:
|
|||||||
Windows:
|
Windows:
|
||||||
- chocolatey
|
- chocolatey
|
||||||
Linux:
|
Linux:
|
||||||
- snap-linux
|
|
||||||
- brew-linux
|
|
||||||
- apt
|
- apt
|
||||||
- dnf
|
- dnf
|
||||||
- yum
|
- yum
|
||||||
|
- single-binary
|
||||||
|
- snap-linux
|
||||||
|
- brew-linux
|
||||||
- pacman
|
- pacman
|
||||||
FreeBSD:
|
FreeBSD:
|
||||||
- pkg
|
- pkg
|
||||||
@ -36,11 +37,13 @@ tools:
|
|||||||
package: https://packages.debian.org/sid/web/httpie
|
package: https://packages.debian.org/sid/web/httpie
|
||||||
commands:
|
commands:
|
||||||
install:
|
install:
|
||||||
- apt update
|
- curl -SsL https://packages.httpie.io/deb/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/httpie.gpg
|
||||||
- apt install httpie
|
# - curl -SsL -o /etc/apt/sources.list.d/httpie.list https://packages.httpie.io/deb/httpie.list
|
||||||
|
- echo "deb [arch=amd64 signed-by=/usr/share/keyrings/httpie.gpg] https://packages.httpie.io/deb ./" | sudo tee /etc/apt/sources.list.d/httpie.list > /dev/null
|
||||||
|
- sudo apt update
|
||||||
|
- sudo apt install httpie
|
||||||
upgrade:
|
upgrade:
|
||||||
- apt update
|
- sudo apt update && sudo apt upgrade httpie
|
||||||
- apt upgrade httpie
|
|
||||||
|
|
||||||
brew-mac:
|
brew-mac:
|
||||||
title: Homebrew
|
title: Homebrew
|
||||||
@ -106,9 +109,9 @@ tools:
|
|||||||
package: https://archlinux.org/packages/community/any/httpie/
|
package: https://archlinux.org/packages/community/any/httpie/
|
||||||
commands:
|
commands:
|
||||||
install:
|
install:
|
||||||
- pacman -Sy httpie
|
|
||||||
upgrade:
|
|
||||||
- pacman -Syu httpie
|
- pacman -Syu httpie
|
||||||
|
upgrade:
|
||||||
|
- pacman -Syu
|
||||||
|
|
||||||
pkg:
|
pkg:
|
||||||
title: FreshPorts
|
title: FreshPorts
|
||||||
@ -179,3 +182,16 @@ tools:
|
|||||||
- yum install httpie
|
- yum install httpie
|
||||||
upgrade:
|
upgrade:
|
||||||
- yum upgrade httpie
|
- yum upgrade httpie
|
||||||
|
|
||||||
|
single-binary:
|
||||||
|
title: Single binary executables
|
||||||
|
name: Single binary executables
|
||||||
|
note: Get the standalone HTTPie Linux executables when you don't want to go through the full installation process.
|
||||||
|
links:
|
||||||
|
commands:
|
||||||
|
install:
|
||||||
|
- https --download packages.httpie.io/binaries/linux/http-latest -o http
|
||||||
|
- ln -ls ./http ./https
|
||||||
|
- chmod +x ./http ./https
|
||||||
|
upgrade:
|
||||||
|
- https --download packages.httpie.io/binaries/linux/http-latest -o http
|
||||||
|
@ -11,6 +11,9 @@ all
|
|||||||
# Because we use HTML to hide them on the website.
|
# Because we use HTML to hide them on the website.
|
||||||
exclude_rule 'MD002'
|
exclude_rule 'MD002'
|
||||||
|
|
||||||
|
# MD007 Allow unordered list indentation
|
||||||
|
exclude_rule 'MD007'
|
||||||
|
|
||||||
# MD013 Line length
|
# MD013 Line length
|
||||||
exclude_rule 'MD013'
|
exclude_rule 'MD013'
|
||||||
|
|
||||||
@ -20,6 +23,9 @@ exclude_rule 'MD014'
|
|||||||
# MD028 Blank line inside blockquote
|
# MD028 Blank line inside blockquote
|
||||||
exclude_rule 'MD028'
|
exclude_rule 'MD028'
|
||||||
|
|
||||||
|
# MD012 Multiple consecutive blank lines
|
||||||
|
exclude_rule 'MD012'
|
||||||
|
|
||||||
# Tell the linter to use ordered lists:
|
# Tell the linter to use ordered lists:
|
||||||
# 1. Foo
|
# 1. Foo
|
||||||
# 2. Bar
|
# 2. Bar
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
Welcome on the documentation part of the **HTTPie release process**.
|
Welcome on the documentation part of the **HTTPie release process**.
|
||||||
|
|
||||||
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
|
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
|
||||||
- If you are looking for HTTPie installation or upgrade instructions, then you can find all you need for your OS on [that page](https://httpie.io/docs#installation). In the case you do not find your OS, [let us know](https://github.com/httpie/httpie/issues/).
|
- If you are looking for HTTPie installation or upgrade instructions, then you can find all you need for your OS on [that page](https://httpie.io/docs#installation). In the case you do not find your OS, [let us know](https://github.com/httpie/cli/issues/).
|
||||||
- If you are looking for technical information about the HTTPie packaging, then you are at the good place.
|
- If you are looking for technical information about the HTTPie packaging, then you are at the good place.
|
||||||
|
|
||||||
## About
|
## About
|
||||||
@ -12,18 +12,20 @@ You are looking at the HTTPie packaging documentation, where you will find valua
|
|||||||
|
|
||||||
The overall release process starts simple:
|
The overall release process starts simple:
|
||||||
|
|
||||||
1. Do the [PyPI](https://pypi.org/project/httpie/) publication.
|
1. Bump the version identifiers in the following places:
|
||||||
2. Then, handle company-related tasks.
|
- `httpie/__init__.py`
|
||||||
3. Finally, follow OS-specific steps, described in documents below, to send patches downstream.
|
- `docs/packaging/windows-chocolatey/httpie.nuspec`
|
||||||
|
- `CHANGELOG.md`
|
||||||
|
2. Commit your changes and make a PR against the `master`.
|
||||||
|
3. Merge the PR, and tag the last commit with your version identifier.
|
||||||
|
4. Make a GitHub release (by copying the text in `CHANGELOG.md`)
|
||||||
|
5. Push that release to PyPI (dispatch the `Release PyPI` GitHub action).
|
||||||
|
6. Once PyPI is ready, push the release to the Snap, Homebrew and Chocolatey with their respective actions.
|
||||||
|
7. Go to the [`httpie/debian.httpie.io`](https://github.com/httpie/debian.httpie.io) repo and trigger the package index workflow.
|
||||||
|
|
||||||
## First, PyPI
|
## Company-specific tasks
|
||||||
|
|
||||||
Let's do the release on [PyPi](https://pypi.org/project/httpie/).
|
- Blank the `master_and_released_docs_differ_after` value in [config.json](https://github.com/httpie/cli/blob/master/docs/config.json).
|
||||||
That is done quite easily by manually triggering the [release workflow](https://github.com/httpie/httpie/actions/workflows/release.yml).
|
|
||||||
|
|
||||||
## Then, company-specific tasks
|
|
||||||
|
|
||||||
- Blank the `master_and_released_docs_differ_after` value in [config.json](https://github.com/httpie/httpie/blob/master/docs/config.json).
|
|
||||||
- Update the [contributors list](../contributors).
|
- Update the [contributors list](../contributors).
|
||||||
- Update the HTTPie version bundled into [Termible](https://termible.io/) ([example](https://github.com/httpie/termible/pull/1)).
|
- Update the HTTPie version bundled into [Termible](https://termible.io/) ([example](https://github.com/httpie/termible/pull/1)).
|
||||||
|
|
||||||
@ -36,11 +38,10 @@ A more complete state of deployment can be found on [repology](https://repology.
|
|||||||
| -------------------------------------------: | -------------- |
|
| -------------------------------------------: | -------------- |
|
||||||
| [Arch Linux, and derived](linux-arch/) | trusted person |
|
| [Arch Linux, and derived](linux-arch/) | trusted person |
|
||||||
| [CentOS, RHEL, and derived](linux-centos/) | trusted person |
|
| [CentOS, RHEL, and derived](linux-centos/) | trusted person |
|
||||||
| [Debian, Ubuntu, and derived](linux-debian/) | trusted person |
|
|
||||||
| [Fedora](linux-fedora/) | trusted person |
|
| [Fedora](linux-fedora/) | trusted person |
|
||||||
| :construction: [Homebrew, Linuxbrew](brew/) | **HTTPie** |
|
| [Debian, Ubuntu, and derived](linux-debian/) | **HTTPie** |
|
||||||
| :construction: [MacPorts](mac-ports/) | **HTTPie** |
|
| [Homebrew, Linuxbrew](brew/) | **HTTPie** |
|
||||||
| [Snapcraft](snapcraft/) | **HTTPie** |
|
| [Snapcraft](snapcraft/) | **HTTPie** |
|
||||||
| [Windows — Chocolatey](windows-chocolatey/) | **HTTPie** |
|
| [Windows — Chocolatey](windows-chocolatey/) | **HTTPie** |
|
||||||
|
|
||||||
:new: You do not find your system or you would like to see HTTPie supported on another OS? Then [let us know](https://github.com/httpie/httpie/issues/).
|
:new: You do not find your system or you would like to see HTTPie supported on another OS? Then [let us know](https://github.com/httpie/cli/issues/).
|
||||||
|
@ -13,21 +13,19 @@ We will discuss setting up the environment, installing development tools, instal
|
|||||||
|
|
||||||
## Overall process
|
## Overall process
|
||||||
|
|
||||||
:construction: Work in progress.
|
The brew deployment is completely automated, and only requires a trigger to [`Release on Homebrew`](https://github.com/httpie/cli/actions/workflows/release-brew.yml) action
|
||||||
|
from the release manager.
|
||||||
|
|
||||||
First, update the current Formula:
|
If it is needed to be done manually, the following command can be used:
|
||||||
|
|
||||||
```bash
|
```console
|
||||||
make brew-deps
|
$ brew bump-formula-pr httpie --version={TARGET_VERSION}
|
||||||
# Copy-paste content into downstream/mac/brew/httpie.rb
|
|
||||||
git add downstream/mac/brew/httpie.rb
|
|
||||||
git commit -s -m 'Update brew formula to XXX'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
That [GitHub workflow](https://github.com/httpie/httpie/actions/workflows/test-package-mac-brew.yml) will test the formula when `downstream/mac/brew/httpie.rb` is changed in a pull request.
|
which will bump the formula, and create a PR against the package index.
|
||||||
|
|
||||||
Then, open a pull request with those changes to the [downstream file](https://github.com/Homebrew/homebrew-core/blob/master/Formula/httpie.rb).
|
|
||||||
|
|
||||||
## Hacking
|
## Hacking
|
||||||
|
|
||||||
:construction: Work in progress.
|
Make your changes, test the formula through the [`Test Brew Package`](https://github.com/httpie/cli/actions/workflows/test-package-mac-brew.yml) action
|
||||||
|
and then finally submit your patch to [`homebrew-core`](https://github.com/Homebrew/homebrew-core`)
|
||||||
|
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Generate Ruby code with URLs and file hashes for packages from PyPi
|
|
||||||
(i.e., httpie itself as well as its dependencies) to be included
|
|
||||||
in the Homebrew formula after a new release of HTTPie has been published
|
|
||||||
on PyPi.
|
|
||||||
|
|
||||||
<https://github.com/Homebrew/homebrew-core/blob/master/Formula/httpie.rb>
|
|
||||||
|
|
||||||
"""
|
|
||||||
import hashlib
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
VERSIONS = {
|
|
||||||
# By default, we use the latest packages. But sometimes Requests has a maximum supported versions.
|
|
||||||
# Take a look here before making a release: <https://github.com/psf/requests/blob/master/setup.py>
|
|
||||||
'idna': '3.2',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Note: Keep that list sorted.
|
|
||||||
PACKAGES = [
|
|
||||||
'certifi',
|
|
||||||
'charset-normalizer',
|
|
||||||
'defusedxml',
|
|
||||||
'httpie',
|
|
||||||
'idna',
|
|
||||||
'Pygments',
|
|
||||||
'PySocks',
|
|
||||||
'requests',
|
|
||||||
'requests-toolbelt',
|
|
||||||
'urllib3',
|
|
||||||
'multidict',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def get_package_meta(package_name):
|
|
||||||
api_url = f'https://pypi.org/pypi/{package_name}/json'
|
|
||||||
resp = requests.get(api_url).json()
|
|
||||||
hasher = hashlib.sha256()
|
|
||||||
version = VERSIONS.get(package_name)
|
|
||||||
if package_name not in VERSIONS:
|
|
||||||
# Latest version
|
|
||||||
release_bundle = resp['urls']
|
|
||||||
else:
|
|
||||||
release_bundle = resp['releases'][version]
|
|
||||||
|
|
||||||
for release in release_bundle:
|
|
||||||
download_url = release['url']
|
|
||||||
if download_url.endswith('.tar.gz'):
|
|
||||||
hasher.update(requests.get(download_url).content)
|
|
||||||
return {
|
|
||||||
'name': package_name,
|
|
||||||
'url': download_url,
|
|
||||||
'sha256': hasher.hexdigest(),
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f'{package_name}: download not found: {resp}')
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
package_meta_map = {
|
|
||||||
package_name: get_package_meta(package_name)
|
|
||||||
for package_name in PACKAGES
|
|
||||||
}
|
|
||||||
httpie_meta = package_meta_map.pop('httpie')
|
|
||||||
print()
|
|
||||||
print(' url "{url}"'.format(url=httpie_meta['url']))
|
|
||||||
print(' sha256 "{sha256}"'.format(sha256=httpie_meta['sha256']))
|
|
||||||
print()
|
|
||||||
for dep_meta in package_meta_map.values():
|
|
||||||
print(' resource "{name}" do'.format(name=dep_meta['name']))
|
|
||||||
print(' url "{url}"'.format(url=dep_meta['url']))
|
|
||||||
print(' sha256 "{sha256}"'.format(sha256=dep_meta['sha256']))
|
|
||||||
print(' end')
|
|
||||||
print('')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -3,19 +3,18 @@ class Httpie < Formula
|
|||||||
|
|
||||||
desc "User-friendly cURL replacement (command-line HTTP client)"
|
desc "User-friendly cURL replacement (command-line HTTP client)"
|
||||||
homepage "https://httpie.io/"
|
homepage "https://httpie.io/"
|
||||||
url "https://files.pythonhosted.org/packages/53/96/cbcfec73c186f076e4443faf3d91cbbc868f18f6323703afd348b1aba46d/httpie-2.6.0.tar.gz"
|
url "https://files.pythonhosted.org/packages/32/85/bb095699be20cc98731261cb80884e9458178f8fef2a38273530ce77c0a5/httpie-3.1.0.tar.gz"
|
||||||
sha256 "ef929317b239bbf0a5bb7159b4c5d2edbfc55f8a0bcf9cd24ce597daec2afca5"
|
sha256 "2e4a2040b84a912e65c01fb34f7aafe88cad2a3af2da8c685ca65080f376feda"
|
||||||
license "BSD-3-Clause"
|
license "BSD-3-Clause"
|
||||||
head "https://github.com/httpie/httpie.git", branch: "master"
|
head "https://github.com/httpie/cli.git", branch: "master"
|
||||||
|
|
||||||
bottle do
|
bottle do
|
||||||
sha256 cellar: :any_skip_relocation, arm64_monterey: "83aab05ffbcd4c3baa6de6158d57ebdaa67c148bef8c872527d90bdaebff0504"
|
sha256 cellar: :any_skip_relocation, arm64_monterey: "9bb6e8c1ef5ba8b019ddedd7e908dd2174da695351aa9a238dfb28b0f57ef005"
|
||||||
sha256 cellar: :any_skip_relocation, arm64_big_sur: "3c3a5c2458d0658e14b663495e115297c573aa3466d292f12d02c3ec13a24bdf"
|
sha256 cellar: :any_skip_relocation, arm64_big_sur: "47ffccd3241155d863e1b4f6259d538a34d42a0cdeed8152bda257ee607b51be"
|
||||||
sha256 cellar: :any_skip_relocation, monterey: "f860e7d3b77dca4928a2c5e10c4cbd50d792330dfb99f7d736ca0da9fb9dd0d0"
|
sha256 cellar: :any_skip_relocation, monterey: "dc4a04cb05a9cd1bfa6a632a0e4a21975905954af54ece41f9050c52474267be"
|
||||||
sha256 cellar: :any_skip_relocation, big_sur: "377b0643aa1f6d310ba4cfc70d66a94cc458213db8d134940d3b10a32defacf1"
|
sha256 cellar: :any_skip_relocation, big_sur: "ae469e37864e967e0fd99fba15a78e719dcb351b462f98f3843c78ed1473df6d"
|
||||||
sha256 cellar: :any_skip_relocation, catalina: "6d306c30f6f1d7a551d88415efe12b7c3f25d0602f3579dc632771a463f78fa5"
|
sha256 cellar: :any_skip_relocation, catalina: "291a3eaecb2a2cc845c1652686a9a14b21053d7e3a7d0115245b2150ca2e199e"
|
||||||
sha256 cellar: :any_skip_relocation, mojave: "f66b8cdff9cb7b44a84197c3e3d81d810f7ff8f2188998b977ccadfc7e2ec893"
|
sha256 cellar: :any_skip_relocation, x86_64_linux: "710836e27c44c8e3ad181d668f4a9f78c4cb4c355d7b148a397599a7cd42713d"
|
||||||
sha256 cellar: :any_skip_relocation, x86_64_linux: "53f036b0114814c28982e8c022dcf494e7024de088641d7076fd73d12a45a0e9"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
depends_on "python@3.10"
|
depends_on "python@3.10"
|
||||||
@ -26,8 +25,8 @@ class Httpie < Formula
|
|||||||
end
|
end
|
||||||
|
|
||||||
resource "charset-normalizer" do
|
resource "charset-normalizer" do
|
||||||
url "https://files.pythonhosted.org/packages/48/44/76b179e0d1afe6e6a91fd5661c284f60238987f3b42b676d141d01cd5b97/charset-normalizer-2.0.10.tar.gz"
|
url "https://files.pythonhosted.org/packages/56/31/7bcaf657fafb3c6db8c787a865434290b726653c912085fbd371e9b92e1c/charset-normalizer-2.0.12.tar.gz"
|
||||||
sha256 "876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"
|
sha256 "2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"
|
||||||
end
|
end
|
||||||
|
|
||||||
resource "defusedxml" do
|
resource "defusedxml" do
|
||||||
@ -36,8 +35,13 @@ class Httpie < Formula
|
|||||||
end
|
end
|
||||||
|
|
||||||
resource "idna" do
|
resource "idna" do
|
||||||
url "https://files.pythonhosted.org/packages/cb/38/4c4d00ddfa48abe616d7e572e02a04273603db446975ab46bbcd36552005/idna-3.2.tar.gz"
|
url "https://files.pythonhosted.org/packages/62/08/e3fc7c8161090f742f504f40b1bccbfc544d4a4e09eb774bf40aafce5436/idna-3.3.tar.gz"
|
||||||
sha256 "467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
|
sha256 "9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
|
||||||
|
end
|
||||||
|
|
||||||
|
resource "multidict" do
|
||||||
|
url "https://files.pythonhosted.org/packages/fa/a7/71c253cdb8a1528802bac7503bf82fe674367e4055b09c28846fdfa4ab90/multidict-6.0.2.tar.gz"
|
||||||
|
sha256 "5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"
|
||||||
end
|
end
|
||||||
|
|
||||||
resource "Pygments" do
|
resource "Pygments" do
|
||||||
@ -65,20 +69,14 @@ class Httpie < Formula
|
|||||||
sha256 "0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"
|
sha256 "0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"
|
||||||
end
|
end
|
||||||
|
|
||||||
resource "multidict" do
|
|
||||||
url "https://files.pythonhosted.org/packages/8e/7c/e12a69795b7b7d5071614af2c691c97fbf16a2a513c66ec52dd7d0a115bb/multidict-5.2.0.tar.gz"
|
|
||||||
sha256 "0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"
|
|
||||||
end
|
|
||||||
|
|
||||||
def install
|
def install
|
||||||
virtualenv_install_with_resources
|
virtualenv_install_with_resources
|
||||||
end
|
end
|
||||||
|
|
||||||
test do
|
test do
|
||||||
# shell_output() already checks the status code
|
assert_match version.to_s, shell_output("#{bin}/httpie --version")
|
||||||
shell_output("#{bin}/httpie -v")
|
assert_match version.to_s, shell_output("#{bin}/https --version")
|
||||||
shell_output("#{bin}/https -v")
|
assert_match version.to_s, shell_output("#{bin}/http --version")
|
||||||
shell_output("#{bin}/http -v")
|
|
||||||
|
|
||||||
raw_url = "https://raw.githubusercontent.com/Homebrew/homebrew-core/HEAD/Formula/httpie.rb"
|
raw_url = "https://raw.githubusercontent.com/Homebrew/homebrew-core/HEAD/Formula/httpie.rb"
|
||||||
assert_match "PYTHONPATH", shell_output("#{bin}/http --ignore-stdin #{raw_url}")
|
assert_match "PYTHONPATH", shell_output("#{bin}/http --ignore-stdin #{raw_url}")
|
||||||
|
6
docs/packaging/brew/update.sh
Executable file
6
docs/packaging/brew/update.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -xe
|
||||||
|
|
||||||
|
rm -f httpie.rb
|
||||||
|
http --download https://raw.githubusercontent.com/Homebrew/homebrew-core/master/Formula/httpie.rb
|
@ -7,7 +7,7 @@ pkgname=httpie
|
|||||||
pkgver=2.6.0
|
pkgver=2.6.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="human-friendly CLI HTTP client for the API era"
|
pkgdesc="human-friendly CLI HTTP client for the API era"
|
||||||
url="https://github.com/httpie/httpie"
|
url="https://github.com/httpie/cli"
|
||||||
depends=('python-defusedxml'
|
depends=('python-defusedxml'
|
||||||
'python-pygments'
|
'python-pygments'
|
||||||
'python-pysocks'
|
'python-pysocks'
|
||||||
@ -22,7 +22,7 @@ conflicts=(python-httpie)
|
|||||||
replaces=(python-httpie python2-httpie)
|
replaces=(python-httpie python2-httpie)
|
||||||
license=('BSD')
|
license=('BSD')
|
||||||
arch=('any')
|
arch=('any')
|
||||||
source=($pkgname-$pkgver.tar.gz::"https://github.com/httpie/httpie/archive/$pkgver.tar.gz")
|
source=($pkgname-$pkgver.tar.gz::"https://github.com/httpie/cli/archive/$pkgver.tar.gz")
|
||||||
sha256sums=('3bcd9a8cb2b11299da12d3af36c095c6d4b665e41c395898a07f1ae4d99fc14a')
|
sha256sums=('3bcd9a8cb2b11299da12d3af36c095c6d4b665e41c395898a07f1ae4d99fc14a')
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
|
@ -11,19 +11,16 @@ Welcome to the documentation about **packaging HTTPie for Debian GNU/Linux**.
|
|||||||
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Debian GNU/Linux. They apply to Ubuntu as well, and any Debian-derived distributions like MX Linux, Linux Mint, deepin, Pop!_OS, KDE neon, Zorin OS, elementary OS, Kubuntu, Devuan, Linux Lite, Peppermint OS, Lubuntu, antiX, Xubuntu, etc.
|
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Debian GNU/Linux. They apply to Ubuntu as well, and any Debian-derived distributions like MX Linux, Linux Mint, deepin, Pop!_OS, KDE neon, Zorin OS, elementary OS, Kubuntu, Devuan, Linux Lite, Peppermint OS, Lubuntu, antiX, Xubuntu, etc.
|
||||||
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
|
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
|
||||||
|
|
||||||
The current maintainer is Bartosz Fenski.
|
We create the standalone binaries (see this [for more details](../../../extras/packaging/linux/)) and package them with
|
||||||
|
[FPM](https://github.com/jordansissel/fpm)'s `dir` mode. The core `http`/`https` commands don't have any dependencies, but the `httpie`
|
||||||
|
command (due to the underlying `httpie cli plugins` interface) explicitly depends to the system Python (through `python3`/`python3-pip`).
|
||||||
|
|
||||||
## Overall process
|
## Overall process
|
||||||
|
|
||||||
Open a new bug on the Debian Bug Tracking System by sending an email:
|
The [`Release as Standalone Linux Binary`](https://github.com/httpie/cli/actions/workflows/release-linux-standalone.yml) will be automatically
|
||||||
|
triggered when a new release is created, and it will submit the `.deb` package as a release asset.
|
||||||
|
|
||||||
- To: `Debian Bug Tracking System <submit@bugs.debian.org>`
|
For making that asset available for all debian users, the release manager needs to go to the [`httpie/debian.httpie.io`](https://github.com/httpie/debian.httpie.io) repo
|
||||||
- Subject: `httpie: Version XXX available`
|
and trigger the [`Update Index`](https://github.com/httpie/debian.httpie.io/actions/workflows/update-index.yml) action. It will automatically
|
||||||
- Message template (examples [1](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=993937), and [2](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=996479)):
|
scrape all new debian packages from the release assets, properly update the indexes and create a new PR ([an example](https://github.com/httpie/debian.httpie.io/pull/1))
|
||||||
|
which then will become active when merged.
|
||||||
```email
|
|
||||||
Package: httpie
|
|
||||||
Severity: normal
|
|
||||||
|
|
||||||
<MESSAGE>
|
|
||||||
```
|
|
||||||
|
@ -15,7 +15,7 @@ The current maintainer is [Miro Hrončok](https://github.com/hroncok).
|
|||||||
|
|
||||||
## Overall process
|
## Overall process
|
||||||
|
|
||||||
We added the [.packit.yaml](https://github.com/httpie/httpie/blob/master/.packit.yaml) local file.
|
We added the [.packit.yaml](https://github.com/httpie/cli/blob/master/.packit.yaml) local file.
|
||||||
It unlocks real-time Fedora checks on pull requests and new releases.
|
It unlocks real-time Fedora checks on pull requests and new releases.
|
||||||
|
|
||||||
So there is nothing to do on our side: `Packit` will see the new release and open a pull request [there](https://src.fedoraproject.org/rpms/httpie). Then, the Fedora maintainer will review and merge.
|
So there is nothing to do on our side: `Packit` will see the new release and open a pull request [there](https://src.fedoraproject.org/rpms/httpie). Then, the Fedora maintainer will review and merge.
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
Name: httpie
|
Name: httpie
|
||||||
Version: 2.6.0
|
Version: 3.1.0
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: A Curl-like tool for humans
|
Summary: A Curl-like tool for humans
|
||||||
|
|
||||||
License: BSD
|
License: BSD
|
||||||
URL: https://httpie.org/
|
URL: https://httpie.org/
|
||||||
Source0: https://github.com/httpie/httpie/archive/%{version}/%{name}-%{version}.tar.gz
|
Source0: https://github.com/httpie/cli/archive/%{version}/%{name}-%{version}.tar.gz
|
||||||
|
|
||||||
BuildArch: noarch
|
BuildArch: noarch
|
||||||
|
|
||||||
@ -78,6 +78,25 @@ help2man %{buildroot}%{_bindir}/httpie > %{buildroot}%{_mandir}/man1/httpie.1
|
|||||||
|
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Mar 08 2022 Miro Hrončok <mhroncok@redhat.com> - 3.1.0-1
|
||||||
|
- Update to 3.1.0
|
||||||
|
- Fixes: rhbz#2061597
|
||||||
|
|
||||||
|
* Mon Jan 24 2022 Miro Hrončok <mhroncok@redhat.com> - 3.0.2-1
|
||||||
|
- Update to 3.0.2
|
||||||
|
- Fixes: rhbz#2044572
|
||||||
|
|
||||||
|
* Mon Jan 24 2022 Miro Hrončok <mhroncok@redhat.com> - 3.0.1-1
|
||||||
|
- Update to 3.0.1
|
||||||
|
- Fixes: rhbz#2044058
|
||||||
|
|
||||||
|
* Fri Jan 21 2022 Miro Hrončok <mhroncok@redhat.com> - 3.0.0-1
|
||||||
|
- Update to 3.0.0
|
||||||
|
- Fixes: rhbz#2043680
|
||||||
|
|
||||||
|
* Thu Jan 20 2022 Fedora Release Engineering <releng@fedoraproject.org> - 2.6.0-2
|
||||||
|
- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild
|
||||||
|
|
||||||
* Fri Oct 15 2021 Miro Hrončok <mhroncok@redhat.com> - 2.6.0-1
|
* Fri Oct 15 2021 Miro Hrončok <mhroncok@redhat.com> - 2.6.0-1
|
||||||
- Update to 2.6.0
|
- Update to 2.6.0
|
||||||
- Fixes: rhbz#2014022
|
- Fixes: rhbz#2014022
|
||||||
|
6
docs/packaging/linux-fedora/update.sh
Executable file
6
docs/packaging/linux-fedora/update.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -xe
|
||||||
|
|
||||||
|
rm -f httpie.spec.txt
|
||||||
|
https --download src.fedoraproject.org/rpms/httpie/raw/rawhide/f/httpie.spec -o httpie.spec.txt
|
@ -19,7 +19,7 @@ Open a pull request to update the [downstream file](https://github.com/macports/
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Download the archive
|
# Download the archive
|
||||||
$ wget https://api.github.com/repos/httpie/httpie/tarball/2.5.0
|
$ wget https://api.github.com/repos/httpie/cli/tarball/2.5.0
|
||||||
|
|
||||||
# Size
|
# Size
|
||||||
$ stat --printf="%s\n" 2.5.0
|
$ stat --printf="%s\n" 2.5.0
|
||||||
|
@ -13,7 +13,16 @@ We will discuss setting up the environment, installing development tools, instal
|
|||||||
|
|
||||||
## Overall process
|
## Overall process
|
||||||
|
|
||||||
Trigger a new [build](https://snapcraft.io/httpie/builds), then [promote it](https://snapcraft.io/httpie/releases). If more management is needed: [revisions supervision](https://dashboard.snapcraft.io/snaps/httpie/revisions/).
|
Trigger the [`Release on Snap`](https://github.com/httpie/cli/actions/workflows/release-snap.yml) action, which will
|
||||||
|
create a snap package for HTTPie and then push it to Snap Store in the following channels:
|
||||||
|
|
||||||
|
- Edge
|
||||||
|
- Beta
|
||||||
|
- Candidate
|
||||||
|
- Stable
|
||||||
|
|
||||||
|
If a push to any of them fail, all the release tasks for the following channels will be cancelled so that the
|
||||||
|
release manager can look into the underlying cause.
|
||||||
|
|
||||||
## Hacking
|
## Hacking
|
||||||
|
|
||||||
@ -28,7 +37,7 @@ From inside the container:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone
|
# Clone
|
||||||
git clone --depth=1 https://github.com/httpie/httpie.git
|
git clone --depth=1 https://github.com/httpie/cli.git
|
||||||
cd httpie
|
cd httpie
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
|
@ -13,18 +13,23 @@ We will discuss setting up the environment, installing development tools, instal
|
|||||||
|
|
||||||
## Overall process
|
## Overall process
|
||||||
|
|
||||||
After having successfully [built and tested](#hacking) the package, push it:
|
After having successfully [built and tested](#hacking) the package, either trigger the
|
||||||
|
[`Release on Chocolatey`](https://github.com/httpie/cli/actions/workflows/release-choco.yml) action
|
||||||
|
to push it to the `Chocolatey` store or use the CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Replace 2.5.0 with the correct version
|
# Replace 2.5.0 with the correct version
|
||||||
choco push httpie.2.5.0.nupkg -s https://push.chocolatey.org/ --api-key=API_KEY
|
choco push httpie.2.5.0.nupkg -s https://push.chocolatey.org/ --api-key=API_KEY
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Be aware that it might take multiple days until the release is approved, sine it goes through multiple
|
||||||
|
sets of reviews (some of them are done manually).
|
||||||
|
|
||||||
## Hacking
|
## Hacking
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clone
|
# Clone
|
||||||
git clone --depth=1 https://github.com/httpie/httpie.git
|
git clone --depth=1 https://github.com/httpie/cli.git
|
||||||
cd httpie/docs/packaging/windows-chocolatey
|
cd httpie/docs/packaging/windows-chocolatey
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>httpie</id>
|
<id>httpie</id>
|
||||||
<version>2.6.0</version>
|
<version>3.2.2</version>
|
||||||
<summary>Modern, user-friendly command-line HTTP client for the API era</summary>
|
<summary>Modern, user-friendly command-line HTTP client for the API era</summary>
|
||||||
<description>
|
<description>
|
||||||
HTTPie *aitch-tee-tee-pie* is a user-friendly command-line HTTP client for the API era.
|
HTTPie *aitch-tee-tee-pie* is a user-friendly command-line HTTP client for the API era.
|
||||||
@ -29,17 +29,17 @@ Main features:
|
|||||||
<title>HTTPie</title>
|
<title>HTTPie</title>
|
||||||
<authors>HTTPie</authors>
|
<authors>HTTPie</authors>
|
||||||
<owners>jakubroztocil</owners>
|
<owners>jakubroztocil</owners>
|
||||||
<copyright>2012-2021 Jakub Roztocil</copyright>
|
<copyright>2012-2022 Jakub Roztocil</copyright>
|
||||||
<licenseUrl>https://raw.githubusercontent.com/httpie/httpie/master/LICENSE</licenseUrl>
|
<licenseUrl>https://raw.githubusercontent.com/httpie/cli/master/LICENSE</licenseUrl>
|
||||||
<iconUrl>https://pie-assets.s3.eu-central-1.amazonaws.com/LogoIcons/GB.png</iconUrl>
|
<iconUrl>https://pie-assets.s3.eu-central-1.amazonaws.com/LogoIcons/GB.png</iconUrl>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<releaseNotes>See the [changelog](https://github.com/httpie/httpie/blob/2.6.0/CHANGELOG.md).</releaseNotes>
|
<releaseNotes>See the [changelog](https://github.com/httpie/cli/releases/tag/3.2.2).</releaseNotes>
|
||||||
<tags>httpie http https rest api client curl python ssl cli foss oss url</tags>
|
<tags>httpie http https rest api client curl python ssl cli foss oss url</tags>
|
||||||
<projectUrl>https://httpie.io</projectUrl>
|
<projectUrl>https://httpie.io</projectUrl>
|
||||||
<packageSourceUrl>https://github.com/httpie/httpie/tree/master/docs/packaging/windows-chocolatey</packageSourceUrl>
|
<packageSourceUrl>https://github.com/httpie/cli/tree/master/docs/packaging/windows-chocolatey</packageSourceUrl>
|
||||||
<projectSourceUrl>https://github.com/httpie/httpie</projectSourceUrl>
|
<projectSourceUrl>https://github.com/httpie/cli</projectSourceUrl>
|
||||||
<docsUrl>https://httpie.io/docs</docsUrl>
|
<docsUrl>https://httpie.io/docs</docsUrl>
|
||||||
<bugTrackerUrl>https://github.com/httpie/httpie/issues</bugTrackerUrl>
|
<bugTrackerUrl>https://github.com/httpie/cli/issues</bugTrackerUrl>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="python3" version="3.7" />
|
<dependency id="python3" version="3.7" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
BIN
docs/stardust.png
Normal file
BIN
docs/stardust.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
@ -1,52 +1,24 @@
|
|||||||
function __fish_httpie_styles
|
function __fish_httpie_styles
|
||||||
echo "
|
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
|
||||||
abap
|
end
|
||||||
algol
|
|
||||||
algol_nu
|
function __fish_httpie_mime_types
|
||||||
arduino
|
test -r /usr/share/mime/types && cat /usr/share/mime/types
|
||||||
auto
|
end
|
||||||
autumn
|
|
||||||
borland
|
function __fish_httpie_print_args
|
||||||
bw
|
set -l arg (commandline -t)
|
||||||
colorful
|
string match -qe H "$arg" || echo -e $arg"H\trequest headers"
|
||||||
default
|
string match -qe B "$arg" || echo -e $arg"B\trequest body"
|
||||||
emacs
|
string match -qe h "$arg" || echo -e $arg"h\tresponse headers"
|
||||||
friendly
|
string match -qe b "$arg" || echo -e $arg"b\tresponse body"
|
||||||
fruity
|
string match -qe m "$arg" || echo -e $arg"m\tresponse metadata"
|
||||||
gruvbox-dark
|
|
||||||
gruvbox-light
|
|
||||||
igor
|
|
||||||
inkpot
|
|
||||||
lovelace
|
|
||||||
manni
|
|
||||||
material
|
|
||||||
monokai
|
|
||||||
murphy
|
|
||||||
native
|
|
||||||
paraiso-dark
|
|
||||||
paraiso-light
|
|
||||||
pastie
|
|
||||||
perldoc
|
|
||||||
rainbow_dash
|
|
||||||
rrt
|
|
||||||
sas
|
|
||||||
solarized
|
|
||||||
solarized-dark
|
|
||||||
solarized-light
|
|
||||||
stata
|
|
||||||
stata-dark
|
|
||||||
stata-light
|
|
||||||
tango
|
|
||||||
trac
|
|
||||||
vim
|
|
||||||
vs
|
|
||||||
xcode
|
|
||||||
zenburn"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fish_httpie_auth_types
|
function __fish_httpie_auth_types
|
||||||
echo -e "basic\tBasic HTTP auth"
|
echo -e "basic\tBasic HTTP auth"
|
||||||
echo -e "digest\tDigest HTTP auth"
|
echo -e "digest\tDigest HTTP auth"
|
||||||
|
echo -e "bearer\tBearer HTTP Auth"
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fish_http_verify_options
|
function __fish_http_verify_options
|
||||||
@ -54,6 +26,7 @@ function __fish_http_verify_options
|
|||||||
echo -e "no\tDisable cert verification"
|
echo -e "no\tDisable cert verification"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Predefined Content Types
|
# Predefined Content Types
|
||||||
|
|
||||||
complete -c http -s j -l json -d 'Data items are serialized as a JSON object'
|
complete -c http -s j -l json -d 'Data items are serialized as a JSON object'
|
||||||
@ -70,26 +43,28 @@ complete -c http -s x -l compress -d 'Content compressed with Deflate algorithm'
|
|||||||
|
|
||||||
# Output Processing
|
# Output Processing
|
||||||
|
|
||||||
complete -c http -l pretty -xa "all colors format none" -d 'Controls output processing'
|
complete -c http -l pretty -xa "all colors format none" -d 'Controls output processing'
|
||||||
complete -c http -s s -l style -xa "(__fish_httpie_styles)" -d 'Output coloring style'
|
complete -c http -s s -l style -xa "(__fish_httpie_styles)" -d 'Output coloring style'
|
||||||
complete -c http -l unsorted -d 'Disables all sorting while formatting output'
|
complete -c http -l unsorted -d 'Disables all sorting while formatting output'
|
||||||
complete -c http -l sorted -d 'Re-enables all sorting options while formatting output'
|
complete -c http -l sorted -d 'Re-enables all sorting options while formatting output'
|
||||||
complete -c http -l format-options -x -d 'Controls output formatting'
|
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
|
# Output Options
|
||||||
|
|
||||||
complete -c http -s p -l print -x -d 'String specifying what the output should contain'
|
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 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 m -l meta -d 'Print only the response metadata'
|
||||||
complete -c http -s v -l verbose -d 'Print the whole request as well as the response'
|
complete -c http -s b -l body -d 'Print only the response body'
|
||||||
complete -c http -l all -d 'Show any intermediary requests/responses'
|
complete -c http -s v -l verbose -d 'Print the whole request as well as the response'
|
||||||
complete -c http -s P -l history-print -x -d 'The same as --print but applies only to intermediary requests/responses'
|
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 S -l stream -d 'Always stream the response body by line'
|
||||||
complete -c http -s o -l output -F -d 'Save output to FILE'
|
complete -c http -s o -l output -F -d 'Save output to FILE'
|
||||||
complete -c http -s d -l download -d 'Download a file'
|
complete -c http -s d -l download -d 'Download a file'
|
||||||
complete -c http -s c -l continue -d 'Resume an interrupted download'
|
complete -c http -s c -l continue -d 'Resume an interrupted download'
|
||||||
complete -c http -s q -l quiet -d 'Do not print to stdout or stderr'
|
complete -c http -s q -l quiet -d 'Do not print to stdout or stderr'
|
||||||
|
|
||||||
|
|
||||||
# Sessions
|
# Sessions
|
||||||
@ -115,23 +90,30 @@ complete -c http -l max-headers -x -d 'Maximum number of response headers
|
|||||||
complete -c http -l timeout -x -d 'Connection timeout in seconds'
|
complete -c http -l timeout -x -d 'Connection timeout in seconds'
|
||||||
complete -c http -l check-status -d 'Error with non-200 HTTP status code'
|
complete -c http -l check-status -d 'Error with non-200 HTTP status code'
|
||||||
complete -c http -l path-as-is -d 'Bypass dot segment URL squashing'
|
complete -c http -l path-as-is -d 'Bypass dot segment URL squashing'
|
||||||
complete -c http -l chunked -d ''
|
complete -c http -l chunked -d 'Enable streaming via chunked transfer encoding'
|
||||||
|
|
||||||
|
|
||||||
# SSL
|
# SSL
|
||||||
|
|
||||||
complete -c http -l verify -xa "(__fish_http_verify_options)" -d 'Enable/disable cert verification'
|
complete -c http -l verify -xa "(__fish_http_verify_options)" -d 'Enable/disable cert verification'
|
||||||
complete -c http -l ssl -x -d 'Desired protocol version to use'
|
complete -c http -l ssl -x -d 'Desired protocol version to use'
|
||||||
complete -c http -l ciphers -x -d 'String in the OpenSSL cipher list format'
|
complete -c http -l ciphers -x -d 'String in the OpenSSL cipher list format'
|
||||||
complete -c http -l cert -F -d 'Client side SSL certificate'
|
complete -c http -l cert -F -d 'Client side SSL certificate'
|
||||||
complete -c http -l cert-key -F -d 'Private key to use with SSL'
|
complete -c http -l cert-key -F -d 'Private key to use with SSL'
|
||||||
|
complete -c http -l cert-key-pass -x -d 'Passphrase for the given private key'
|
||||||
|
|
||||||
|
|
||||||
# Troubleshooting
|
# Troubleshooting
|
||||||
|
|
||||||
complete -c http -s I -l ignore-stdin -d 'Do not attempt to read stdin'
|
complete -c http -s I -l ignore-stdin -d 'Do not attempt to read stdin'
|
||||||
complete -c http -l help -d 'Show help'
|
complete -c http -l help -d 'Show help'
|
||||||
|
complete -c http -l manual -d 'Show the full manual'
|
||||||
complete -c http -l version -d 'Show version'
|
complete -c http -l version -d 'Show version'
|
||||||
complete -c http -l traceback -d 'Prints exception traceback should one occur'
|
complete -c http -l traceback -d 'Prints exception traceback should one occur'
|
||||||
complete -c http -l default-scheme -x -d 'The default scheme to use'
|
complete -c http -l default-scheme -x -d 'The default scheme to use'
|
||||||
complete -c http -l debug -d 'Show debugging output'
|
complete -c http -l debug -d 'Show debugging output'
|
||||||
|
|
||||||
|
|
||||||
|
# Alias for https to http
|
||||||
|
|
||||||
|
complete -c https -w http
|
||||||
|
600
extras/man/http.1
Normal file
600
extras/man/http.1
Normal file
@ -0,0 +1,600 @@
|
|||||||
|
.\" This file is auto-generated from the parser declaration in httpie/cli/definition.py by extras/scripts/generate_man_pages.py.
|
||||||
|
.TH http 1 "2024-11-01" "HTTPie 3.2.4" "HTTPie Manual"
|
||||||
|
.SH NAME
|
||||||
|
http
|
||||||
|
.SH SYNOPSIS
|
||||||
|
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
|
||||||
|
|
||||||
|
These arguments come after any flags and in the order they are listed here.
|
||||||
|
Only URL is required.
|
||||||
|
|
||||||
|
.IP "\fB\,METHOD\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).
|
||||||
|
|
||||||
|
This argument can be omitted in which case HTTPie will use POST if there
|
||||||
|
is some data to be sent, otherwise GET:
|
||||||
|
|
||||||
|
$ http example.org # => GET
|
||||||
|
$ http example.org hello=world # => POST
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,URL\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
$ http :3000 # => http://localhost:3000
|
||||||
|
$ http :/foo # => http://localhost/foo
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,REQUEST_ITEM\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Optional key-value pairs to be included in the request. The separator used
|
||||||
|
determines the type:
|
||||||
|
|
||||||
|
\[aq]:\[aq] HTTP headers:
|
||||||
|
|
||||||
|
Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0
|
||||||
|
|
||||||
|
\[aq]==\[aq] URL parameters to be appended to the request URI:
|
||||||
|
|
||||||
|
search==httpie
|
||||||
|
|
||||||
|
\[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=\[aq]CLI HTTP client\[aq]
|
||||||
|
|
||||||
|
\[aq]:=\[aq] Non-string JSON data fields (only with \fB\,--json\/\fR, \fB\,-j\/\fR):
|
||||||
|
|
||||||
|
awesome:=true amount:=42 colors:=\[aq][\[dq]red\[dq], \[dq]green\[dq], \[dq]blue\[dq]]\[aq]
|
||||||
|
|
||||||
|
\[aq]@\[aq] Form file fields (only with \fB\,--form\/\fR or \fB\,--multipart\/\fR):
|
||||||
|
|
||||||
|
cv@\(ti/Documents/CV.pdf
|
||||||
|
cv@\[aq]\(ti/Documents/CV.pdf;type=application/pdf\[aq]
|
||||||
|
|
||||||
|
\[aq]=@\[aq] A data field like \[aq]=\[aq], but takes a file path and embeds its content:
|
||||||
|
|
||||||
|
essay=@Documents/essay.txt
|
||||||
|
|
||||||
|
\[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\e:colon=value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Predefined content types
|
||||||
|
.IP "\fB\,--json\/\fR, \fB\,-j\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
(default) Data items from the command line are serialized as a JSON object.
|
||||||
|
The Content-Type and Accept headers are set to application/json
|
||||||
|
(if not specified).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--form\/\fR, \fB\,-f\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Data items from the command line are serialized as form fields.
|
||||||
|
|
||||||
|
The Content-Type is set to application/x-www-form-urlencoded (if not
|
||||||
|
specified). The presence of any file fields results in a
|
||||||
|
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).
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--boundary\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Specify a custom boundary string for multipart/form-data requests. Only has effect only together with \fB\,--form\/\fR.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--raw\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
This option allows you to pass raw request data without extra processing
|
||||||
|
(as opposed to the structured request items syntax):
|
||||||
|
|
||||||
|
$ http \fB\,--raw\/\fR=\[aq]data\[aq] pie.dev/post
|
||||||
|
|
||||||
|
You can achieve the same by piping the data via stdin:
|
||||||
|
|
||||||
|
$ echo data | http pie.dev/post
|
||||||
|
|
||||||
|
Or have HTTPie load the raw data from a file:
|
||||||
|
|
||||||
|
$ http pie.dev/post @data.txt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Content processing options
|
||||||
|
.IP "\fB\,--compress\/\fR, \fB\,-x\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Content compressed (encoded) with Deflate algorithm.
|
||||||
|
The Content-Encoding header is set to deflate.
|
||||||
|
|
||||||
|
Compression is skipped if it appears that compression ratio is
|
||||||
|
negative. Compression can be forced by repeating the argument.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Output processing
|
||||||
|
.IP "\fB\,--pretty\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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 \[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
|
||||||
|
|
||||||
|
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 \[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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--sorted\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Re-enables all sorting options while formatting output. It is a shortcut for:
|
||||||
|
|
||||||
|
\fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--response-charset\/\fR \fI\,ENCODING\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Override the response encoding for terminal display purposes, e.g.:
|
||||||
|
|
||||||
|
\fB\,--response-charset\/\fR=utf8
|
||||||
|
\fB\,--response-charset\/\fR=big5
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--response-mime\/\fR \fI\,MIME_TYPE\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--format-options\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Controls output formatting. Only relevant when formatting is enabled
|
||||||
|
through (explicit or implied) \fB\,--pretty\/\fR=all or \fB\,--pretty\/\fR=format.
|
||||||
|
The following are the default options:
|
||||||
|
|
||||||
|
headers.sort:true
|
||||||
|
json.format:true
|
||||||
|
json.indent:4
|
||||||
|
json.sort_keys:true
|
||||||
|
xml.format:true
|
||||||
|
xml.indent:2
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
This is something you will typically put into your config file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Output options
|
||||||
|
.IP "\fB\,--print\/\fR, \fB\,-p\/\fR \fI\,WHAT\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
String specifying what the output should contain:
|
||||||
|
|
||||||
|
\[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 \[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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--headers\/\fR, \fB\,-h\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--body\/\fR, \fB\,-b\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--all\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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\,--stream\/\fR, \fB\,-S\/\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),
|
||||||
|
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
|
||||||
|
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
|
||||||
|
the response body is saved to FILE. Other parts of the HTTP exchange are
|
||||||
|
printed to stderr.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--download\/\fR, \fB\,-d\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
[filename]. This action is similar to the default behaviour of wget.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--continue\/\fR, \fB\,-c\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Resume an interrupted download. Note that the \fB\,--output\/\fR option needs to be
|
||||||
|
specified as well.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--quiet\/\fR, \fB\,-q\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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\[aq]t affect behaviour of download beyond not printing to terminal.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Sessions
|
||||||
|
.IP "\fB\,--session\/\fR \fI\,SESSION_NAME_OR_PATH\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Create, or reuse and update a session. Within a session, custom headers,
|
||||||
|
auth credential, as well as any cookies sent by the server persist between
|
||||||
|
requests.
|
||||||
|
|
||||||
|
Session files are stored in:
|
||||||
|
|
||||||
|
[HTTPIE_CONFIG_DIR]/<HOST>/<SESSION_NAME>.json.
|
||||||
|
|
||||||
|
See the following page to find out your default HTTPIE_CONFIG_DIR:
|
||||||
|
|
||||||
|
https://httpie.io/docs/cli/config-file-directory
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--session-read-only\/\fR \fI\,SESSION_NAME_OR_PATH\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Create or read a session without updating it form the request/response
|
||||||
|
exchange.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Authentication
|
||||||
|
.IP "\fB\,--auth\/\fR, \fB\,-a\/\fR \fI\,USER[:PASS] | TOKEN\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--auth-type\/\fR, \fB\,-A\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The authentication mechanism to be used. Defaults to \[dq]basic\[dq].
|
||||||
|
|
||||||
|
\[dq]basic\[dq]: Basic HTTP auth
|
||||||
|
|
||||||
|
\[dq]digest\[dq]: Digest HTTP auth
|
||||||
|
|
||||||
|
\[dq]bearer\[dq]: Bearer HTTP Auth
|
||||||
|
|
||||||
|
To see all available auth types on your system, including ones installed via plugins, run:
|
||||||
|
|
||||||
|
$ http \fB\,--auth-type\/\fR
|
||||||
|
|
||||||
|
.IP "\fB\,--ignore-netrc\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Ignore credentials from .netrc.
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Network
|
||||||
|
.IP "\fB\,--offline\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Build the request and print it but don\(gat actually send it.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--proxy\/\fR \fI\,PROTOCOL:PROXY_URL\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
String mapping protocol to the URL of the proxy
|
||||||
|
(e.g. http:http://foo.bar:3128). You can specify multiple proxies with
|
||||||
|
different protocols. The environment variables $ALL_PROXY, $HTTP_PROXY,
|
||||||
|
and $HTTPS_proxy are supported as well.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--follow\/\fR, \fB\,-F\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Follow 30x Location redirects.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--max-redirects\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
By default, requests have a limit of 30 redirects (works with \fB\,--follow\/\fR).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--max-headers\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The maximum number of response headers to be read before giving up (default 0, i.e., no limit).
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--timeout\/\fR \fI\,SECONDS\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The connection timeout of the request in seconds.
|
||||||
|
The default value is 0, i.e., there is no timeout limit.
|
||||||
|
This is not a time limit on the entire response download;
|
||||||
|
rather, an error is reported if the server has not issued a response for
|
||||||
|
timeout seconds (more precisely, if no bytes have been received on
|
||||||
|
the underlying socket for timeout seconds).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--check-status\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
By default, HTTPie exits with 0 when no network or other fatal errors
|
||||||
|
occur. This flag instructs HTTPie to also check the HTTP status code and
|
||||||
|
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\[aq]t been set, then the exit status is 3.
|
||||||
|
Also an error message is written to stderr if stdout is redirected.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--path-as-is\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Bypass dot segment (/../ or /./) URL squashing.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--chunked\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Enable streaming via chunked transfer encoding. The Transfer-Encoding header is set to chunked.
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH SSL
|
||||||
|
.IP "\fB\,--verify\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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.)
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--ssl\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The desired protocol version to use. This will default to
|
||||||
|
SSL v2.3 which will negotiate the highest protocol that both
|
||||||
|
the server and your installation of OpenSSL support. Available protocols
|
||||||
|
may vary depending on OpenSSL installation (only the supported ones
|
||||||
|
are shown here).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--ciphers\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
A string in the OpenSSL cipher list format.
|
||||||
|
|
||||||
|
|
||||||
|
See `http \fB\,--help\/\fR` for the default ciphers list on you system.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--cert\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--cert-key\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--cert-key-pass\/\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\(gall be prompted interactively.
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Troubleshooting
|
||||||
|
.IP "\fB\,--ignore-stdin\/\fR, \fB\,-I\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Do not attempt to read stdin
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--help\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Show this help message and exit.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--manual\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Show the full manual.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--version\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Show version and exit.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--traceback\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Prints the exception traceback should one occur.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--default-scheme\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The default scheme to use if not specified in the URL.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--debug\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Prints the exception traceback should one occur, as well as other
|
||||||
|
information useful for debugging HTTPie itself and for reporting bugs.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.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/cli/issues
|
100
extras/man/httpie.1
Normal file
100
extras/man/httpie.1
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
.\" This file is auto-generated from the parser declaration in httpie/manager/cli.py by extras/scripts/generate_man_pages.py.
|
||||||
|
.TH httpie 1 "2024-11-01" "HTTPie 3.2.4" "HTTPie Manual"
|
||||||
|
.SH NAME
|
||||||
|
httpie
|
||||||
|
.SH SYNOPSIS
|
||||||
|
httpie
|
||||||
|
.SH DESCRIPTION
|
||||||
|
|
||||||
|
Managing interface for the HTTPie itself. <https://httpie.io/docs#manager>
|
||||||
|
|
||||||
|
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.
|
||||||
|
.IP "\fB\,HOSTNAME\/\fR"
|
||||||
|
|
||||||
|
The host this session belongs.
|
||||||
|
|
||||||
|
.IP "\fB\,SESSION_NAME_OR_PATH\/\fR"
|
||||||
|
|
||||||
|
The name or the path for the session that will be upgraded.
|
||||||
|
|
||||||
|
.IP "\fB\,--bind-cookies\/\fR"
|
||||||
|
|
||||||
|
Bind domainless cookies to the host that session belongs.
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH httpie cli sessions upgrade-all
|
||||||
|
Upgrade all named sessions with the latest layout. A list of changes between different session versions can be found in the official documentation.
|
||||||
|
.IP "\fB\,--bind-cookies\/\fR"
|
||||||
|
|
||||||
|
Bind domainless cookies to the host that session belongs.
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH httpie cli plugins install
|
||||||
|
Install the given targets from PyPI or from a local paths.
|
||||||
|
.IP "\fB\,TARGET\/\fR"
|
||||||
|
|
||||||
|
targets to install
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH httpie cli plugins upgrade
|
||||||
|
Upgrade the given plugins
|
||||||
|
.IP "\fB\,TARGET\/\fR"
|
||||||
|
|
||||||
|
targets to upgrade
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH httpie cli plugins uninstall
|
||||||
|
Uninstall the given HTTPie plugins.
|
||||||
|
.IP "\fB\,TARGET\/\fR"
|
||||||
|
|
||||||
|
targets to install
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH httpie cli plugins list
|
||||||
|
List all installed HTTPie plugins.
|
||||||
|
.PP
|
||||||
|
.SH httpie plugins install
|
||||||
|
Install the given targets from PyPI or from a local paths.
|
||||||
|
.IP "\fB\,TARGET\/\fR"
|
||||||
|
|
||||||
|
targets to install
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH httpie plugins upgrade
|
||||||
|
Upgrade the given plugins
|
||||||
|
.IP "\fB\,TARGET\/\fR"
|
||||||
|
|
||||||
|
targets to upgrade
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH httpie plugins uninstall
|
||||||
|
Uninstall the given HTTPie plugins.
|
||||||
|
.IP "\fB\,TARGET\/\fR"
|
||||||
|
|
||||||
|
targets to install
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH httpie plugins list
|
||||||
|
List all installed HTTPie plugins.
|
||||||
|
.PP
|
600
extras/man/https.1
Normal file
600
extras/man/https.1
Normal file
@ -0,0 +1,600 @@
|
|||||||
|
.\" This file is auto-generated from the parser declaration in httpie/cli/definition.py by extras/scripts/generate_man_pages.py.
|
||||||
|
.TH https 1 "2024-11-01" "HTTPie 3.2.4" "HTTPie Manual"
|
||||||
|
.SH NAME
|
||||||
|
https
|
||||||
|
.SH SYNOPSIS
|
||||||
|
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
|
||||||
|
|
||||||
|
These arguments come after any flags and in the order they are listed here.
|
||||||
|
Only URL is required.
|
||||||
|
|
||||||
|
.IP "\fB\,METHOD\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).
|
||||||
|
|
||||||
|
This argument can be omitted in which case HTTPie will use POST if there
|
||||||
|
is some data to be sent, otherwise GET:
|
||||||
|
|
||||||
|
$ http example.org # => GET
|
||||||
|
$ http example.org hello=world # => POST
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,URL\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
$ http :3000 # => http://localhost:3000
|
||||||
|
$ http :/foo # => http://localhost/foo
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,REQUEST_ITEM\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Optional key-value pairs to be included in the request. The separator used
|
||||||
|
determines the type:
|
||||||
|
|
||||||
|
\[aq]:\[aq] HTTP headers:
|
||||||
|
|
||||||
|
Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0
|
||||||
|
|
||||||
|
\[aq]==\[aq] URL parameters to be appended to the request URI:
|
||||||
|
|
||||||
|
search==httpie
|
||||||
|
|
||||||
|
\[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=\[aq]CLI HTTP client\[aq]
|
||||||
|
|
||||||
|
\[aq]:=\[aq] Non-string JSON data fields (only with \fB\,--json\/\fR, \fB\,-j\/\fR):
|
||||||
|
|
||||||
|
awesome:=true amount:=42 colors:=\[aq][\[dq]red\[dq], \[dq]green\[dq], \[dq]blue\[dq]]\[aq]
|
||||||
|
|
||||||
|
\[aq]@\[aq] Form file fields (only with \fB\,--form\/\fR or \fB\,--multipart\/\fR):
|
||||||
|
|
||||||
|
cv@\(ti/Documents/CV.pdf
|
||||||
|
cv@\[aq]\(ti/Documents/CV.pdf;type=application/pdf\[aq]
|
||||||
|
|
||||||
|
\[aq]=@\[aq] A data field like \[aq]=\[aq], but takes a file path and embeds its content:
|
||||||
|
|
||||||
|
essay=@Documents/essay.txt
|
||||||
|
|
||||||
|
\[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\e:colon=value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Predefined content types
|
||||||
|
.IP "\fB\,--json\/\fR, \fB\,-j\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
(default) Data items from the command line are serialized as a JSON object.
|
||||||
|
The Content-Type and Accept headers are set to application/json
|
||||||
|
(if not specified).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--form\/\fR, \fB\,-f\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Data items from the command line are serialized as form fields.
|
||||||
|
|
||||||
|
The Content-Type is set to application/x-www-form-urlencoded (if not
|
||||||
|
specified). The presence of any file fields results in a
|
||||||
|
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).
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--boundary\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Specify a custom boundary string for multipart/form-data requests. Only has effect only together with \fB\,--form\/\fR.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--raw\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
This option allows you to pass raw request data without extra processing
|
||||||
|
(as opposed to the structured request items syntax):
|
||||||
|
|
||||||
|
$ http \fB\,--raw\/\fR=\[aq]data\[aq] pie.dev/post
|
||||||
|
|
||||||
|
You can achieve the same by piping the data via stdin:
|
||||||
|
|
||||||
|
$ echo data | http pie.dev/post
|
||||||
|
|
||||||
|
Or have HTTPie load the raw data from a file:
|
||||||
|
|
||||||
|
$ http pie.dev/post @data.txt
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Content processing options
|
||||||
|
.IP "\fB\,--compress\/\fR, \fB\,-x\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Content compressed (encoded) with Deflate algorithm.
|
||||||
|
The Content-Encoding header is set to deflate.
|
||||||
|
|
||||||
|
Compression is skipped if it appears that compression ratio is
|
||||||
|
negative. Compression can be forced by repeating the argument.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Output processing
|
||||||
|
.IP "\fB\,--pretty\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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 \[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
|
||||||
|
|
||||||
|
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 \[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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--sorted\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Re-enables all sorting options while formatting output. It is a shortcut for:
|
||||||
|
|
||||||
|
\fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--response-charset\/\fR \fI\,ENCODING\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Override the response encoding for terminal display purposes, e.g.:
|
||||||
|
|
||||||
|
\fB\,--response-charset\/\fR=utf8
|
||||||
|
\fB\,--response-charset\/\fR=big5
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--response-mime\/\fR \fI\,MIME_TYPE\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--format-options\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Controls output formatting. Only relevant when formatting is enabled
|
||||||
|
through (explicit or implied) \fB\,--pretty\/\fR=all or \fB\,--pretty\/\fR=format.
|
||||||
|
The following are the default options:
|
||||||
|
|
||||||
|
headers.sort:true
|
||||||
|
json.format:true
|
||||||
|
json.indent:4
|
||||||
|
json.sort_keys:true
|
||||||
|
xml.format:true
|
||||||
|
xml.indent:2
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
This is something you will typically put into your config file.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Output options
|
||||||
|
.IP "\fB\,--print\/\fR, \fB\,-p\/\fR \fI\,WHAT\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
String specifying what the output should contain:
|
||||||
|
|
||||||
|
\[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 \[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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--headers\/\fR, \fB\,-h\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--body\/\fR, \fB\,-b\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--all\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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\,--stream\/\fR, \fB\,-S\/\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),
|
||||||
|
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
|
||||||
|
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
|
||||||
|
the response body is saved to FILE. Other parts of the HTTP exchange are
|
||||||
|
printed to stderr.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--download\/\fR, \fB\,-d\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
[filename]. This action is similar to the default behaviour of wget.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--continue\/\fR, \fB\,-c\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Resume an interrupted download. Note that the \fB\,--output\/\fR option needs to be
|
||||||
|
specified as well.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--quiet\/\fR, \fB\,-q\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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\[aq]t affect behaviour of download beyond not printing to terminal.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Sessions
|
||||||
|
.IP "\fB\,--session\/\fR \fI\,SESSION_NAME_OR_PATH\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Create, or reuse and update a session. Within a session, custom headers,
|
||||||
|
auth credential, as well as any cookies sent by the server persist between
|
||||||
|
requests.
|
||||||
|
|
||||||
|
Session files are stored in:
|
||||||
|
|
||||||
|
[HTTPIE_CONFIG_DIR]/<HOST>/<SESSION_NAME>.json.
|
||||||
|
|
||||||
|
See the following page to find out your default HTTPIE_CONFIG_DIR:
|
||||||
|
|
||||||
|
https://httpie.io/docs/cli/config-file-directory
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--session-read-only\/\fR \fI\,SESSION_NAME_OR_PATH\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Create or read a session without updating it form the request/response
|
||||||
|
exchange.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Authentication
|
||||||
|
.IP "\fB\,--auth\/\fR, \fB\,-a\/\fR \fI\,USER[:PASS] | TOKEN\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--auth-type\/\fR, \fB\,-A\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The authentication mechanism to be used. Defaults to \[dq]basic\[dq].
|
||||||
|
|
||||||
|
\[dq]basic\[dq]: Basic HTTP auth
|
||||||
|
|
||||||
|
\[dq]digest\[dq]: Digest HTTP auth
|
||||||
|
|
||||||
|
\[dq]bearer\[dq]: Bearer HTTP Auth
|
||||||
|
|
||||||
|
To see all available auth types on your system, including ones installed via plugins, run:
|
||||||
|
|
||||||
|
$ http \fB\,--auth-type\/\fR
|
||||||
|
|
||||||
|
.IP "\fB\,--ignore-netrc\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Ignore credentials from .netrc.
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Network
|
||||||
|
.IP "\fB\,--offline\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Build the request and print it but don\(gat actually send it.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--proxy\/\fR \fI\,PROTOCOL:PROXY_URL\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
String mapping protocol to the URL of the proxy
|
||||||
|
(e.g. http:http://foo.bar:3128). You can specify multiple proxies with
|
||||||
|
different protocols. The environment variables $ALL_PROXY, $HTTP_PROXY,
|
||||||
|
and $HTTPS_proxy are supported as well.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--follow\/\fR, \fB\,-F\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Follow 30x Location redirects.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--max-redirects\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
By default, requests have a limit of 30 redirects (works with \fB\,--follow\/\fR).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--max-headers\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The maximum number of response headers to be read before giving up (default 0, i.e., no limit).
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--timeout\/\fR \fI\,SECONDS\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The connection timeout of the request in seconds.
|
||||||
|
The default value is 0, i.e., there is no timeout limit.
|
||||||
|
This is not a time limit on the entire response download;
|
||||||
|
rather, an error is reported if the server has not issued a response for
|
||||||
|
timeout seconds (more precisely, if no bytes have been received on
|
||||||
|
the underlying socket for timeout seconds).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--check-status\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
By default, HTTPie exits with 0 when no network or other fatal errors
|
||||||
|
occur. This flag instructs HTTPie to also check the HTTP status code and
|
||||||
|
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\[aq]t been set, then the exit status is 3.
|
||||||
|
Also an error message is written to stderr if stdout is redirected.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--path-as-is\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Bypass dot segment (/../ or /./) URL squashing.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--chunked\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Enable streaming via chunked transfer encoding. The Transfer-Encoding header is set to chunked.
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH SSL
|
||||||
|
.IP "\fB\,--verify\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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.)
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--ssl\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The desired protocol version to use. This will default to
|
||||||
|
SSL v2.3 which will negotiate the highest protocol that both
|
||||||
|
the server and your installation of OpenSSL support. Available protocols
|
||||||
|
may vary depending on OpenSSL installation (only the supported ones
|
||||||
|
are shown here).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--ciphers\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
A string in the OpenSSL cipher list format.
|
||||||
|
|
||||||
|
|
||||||
|
See `http \fB\,--help\/\fR` for the default ciphers list on you system.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--cert\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--cert-key\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--cert-key-pass\/\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\(gall be prompted interactively.
|
||||||
|
|
||||||
|
|
||||||
|
.PP
|
||||||
|
.SH Troubleshooting
|
||||||
|
.IP "\fB\,--ignore-stdin\/\fR, \fB\,-I\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Do not attempt to read stdin
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--help\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Show this help message and exit.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--manual\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Show the full manual.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--version\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Show version and exit.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--traceback\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Prints the exception traceback should one occur.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--default-scheme\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
The default scheme to use if not specified in the URL.
|
||||||
|
|
||||||
|
|
||||||
|
.IP "\fB\,--debug\/\fR"
|
||||||
|
|
||||||
|
|
||||||
|
Prints the exception traceback should one occur, as well as other
|
||||||
|
information useful for debugging HTTPie itself and for reporting bugs.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.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/cli/issues
|
33
extras/packaging/linux/Dockerfile
Normal file
33
extras/packaging/linux/Dockerfile
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Use the oldest (but still supported) Ubuntu as the base for PyInstaller
|
||||||
|
# packages. This will prevent stuff like glibc from conflicting.
|
||||||
|
FROM ubuntu:18.04
|
||||||
|
|
||||||
|
RUN apt-get update
|
||||||
|
RUN apt-get install -y software-properties-common binutils
|
||||||
|
RUN apt-get install -y ruby-dev
|
||||||
|
RUN gem install fpm
|
||||||
|
|
||||||
|
# Use deadsnakes for the latest Pythons (e.g 3.9)
|
||||||
|
RUN add-apt-repository ppa:deadsnakes/ppa
|
||||||
|
RUN apt-get update && apt-get install -y python3.9 python3.9-dev python3.9-venv
|
||||||
|
|
||||||
|
# Install rpm as well, since we are going to build fedora dists too
|
||||||
|
RUN apt-get install -y rpm
|
||||||
|
|
||||||
|
ADD . /app
|
||||||
|
WORKDIR /app/extras/packaging/linux
|
||||||
|
|
||||||
|
ENV VIRTUAL_ENV=/opt/venv
|
||||||
|
RUN python3.9 -m venv $VIRTUAL_ENV
|
||||||
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
|
# Ensure that pip is renewed, otherwise we would be using distro-provided pip
|
||||||
|
# which strips vendored packages and doesn't work with PyInstaller.
|
||||||
|
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"]
|
52
extras/packaging/linux/README.md
Normal file
52
extras/packaging/linux/README.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# Standalone Linux Packages
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
This directory contains the build scripts for creating:
|
||||||
|
|
||||||
|
- A self-contained binary executable for the HTTPie itself
|
||||||
|
- `httpie.deb` and `httpie.rpm` packages for Debian and Fedora.
|
||||||
|
|
||||||
|
The process of constructing them are fully automated, and can be easily done through the [`Release as Standalone Linux Package`](https://github.com/httpie/cli/actions/workflows/release-linux-standalone.yml)
|
||||||
|
action. Once it finishes, the release artifacts will be attached in the summary page of the triggered run.
|
||||||
|
|
||||||
|
|
||||||
|
## Hacking
|
||||||
|
|
||||||
|
The main entry point for the package builder is the [`build.py`](https://github.com/httpie/cli/blob/master/extras/packaging/linux/build.py). It
|
||||||
|
contains 2 major methods:
|
||||||
|
|
||||||
|
- `build_binaries`, for the self-contained executables
|
||||||
|
- `build_packages`, for the OS-specific packages (which wrap the binaries)
|
||||||
|
|
||||||
|
### `build_binaries`
|
||||||
|
|
||||||
|
We use [PyInstaller](https://pyinstaller.readthedocs.io/en/stable/) for the binaries. Normally pyinstaller offers two different modes:
|
||||||
|
|
||||||
|
- Single directory (harder to distribute, low redundancy. Library files are shared across different executables)
|
||||||
|
- Single binary (easier to distribute, higher redundancy. Same libraries are statically linked to different executables, so higher total size)
|
||||||
|
|
||||||
|
Since our binary size (in total 20 MiBs) is not that big, we have decided to choose the single binary mode for the sake of easier distribution.
|
||||||
|
|
||||||
|
We also disable `UPX`, which is a runtime decompression method since it adds some startup cost.
|
||||||
|
|
||||||
|
### `build_packages`
|
||||||
|
|
||||||
|
We build our OS-specific packages with [FPM](https://github.com/jordansissel/fpm) which offers a really nice abstraction. We use the `dir` mode,
|
||||||
|
and package `http`, `https` and `httpie` commands. More can be added to the `files` option.
|
||||||
|
|
||||||
|
Since the `httpie` depends on having a pip executable, we explicitly depend on the system Python even though the core does not use it.
|
||||||
|
|
||||||
|
### Docker Image
|
||||||
|
|
||||||
|
This directory also contains a [docker image](https://github.com/httpie/cli/blob/master/extras/packaging/linux/Dockerfile) which helps
|
||||||
|
building our standalone binaries in an isolated environment with the lowest possible library versions. This is important, since even though
|
||||||
|
the executables are standalone they still depend on some main system C libraries (like `glibc`) so we need to create our executables inside
|
||||||
|
an environment with a very old (but not deprecated) glibc version. It makes us soundproof for all active Ubuntu/Debian versions.
|
||||||
|
|
||||||
|
It also contains the Python version we package our HTTPie with, so it is the place if you need to change it.
|
||||||
|
|
||||||
|
### `./get_release_artifacts.sh`
|
||||||
|
|
||||||
|
If you make a change in the `build.py`, run the following script to test it out. It will return multiple files under `artifacts/dist` which
|
||||||
|
then you can test out and ensure their quality (it is also the script that we use in our automation).
|
109
extras/packaging/linux/build.py
Normal file
109
extras/packaging/linux/build.py
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import stat
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
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'
|
||||||
|
|
||||||
|
DIST_DIR = BUILD_DIR / 'dist'
|
||||||
|
|
||||||
|
TARGET_SCRIPTS = {
|
||||||
|
SCRIPT_DIR / 'http_cli.py': [],
|
||||||
|
SCRIPT_DIR / 'httpie_cli.py': ['--hidden-import=pip'],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_binaries() -> Iterator[Tuple[str, Path]]:
|
||||||
|
for target_script, extra_args in TARGET_SCRIPTS.items():
|
||||||
|
subprocess.check_call(
|
||||||
|
[
|
||||||
|
'pyinstaller',
|
||||||
|
'--onefile',
|
||||||
|
'--noupx',
|
||||||
|
'-p',
|
||||||
|
HTTPIE_DIR,
|
||||||
|
'--additional-hooks-dir',
|
||||||
|
HOOKS_DIR,
|
||||||
|
*extra_args,
|
||||||
|
target_script,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
for executable_path in DIST_DIR.iterdir():
|
||||||
|
if executable_path.suffix:
|
||||||
|
continue
|
||||||
|
stat_r = executable_path.stat()
|
||||||
|
executable_path.chmod(stat_r.st_mode | stat.S_IEXEC)
|
||||||
|
yield executable_path.stem, executable_path
|
||||||
|
|
||||||
|
|
||||||
|
def build_packages(http_binary: Path, httpie_binary: Path) -> None:
|
||||||
|
import httpie
|
||||||
|
|
||||||
|
# Mapping of src_file -> dst_file
|
||||||
|
files = [
|
||||||
|
(http_binary, '/usr/bin/http'),
|
||||||
|
(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',
|
||||||
|
'python3-pip'
|
||||||
|
]
|
||||||
|
|
||||||
|
processed_deps = [
|
||||||
|
f'--depends={dep}'
|
||||||
|
for dep in deps
|
||||||
|
]
|
||||||
|
processed_files = [
|
||||||
|
'='.join([str(src.resolve()), dst]) for src, dst in files
|
||||||
|
]
|
||||||
|
for target in ['deb', 'rpm']:
|
||||||
|
subprocess.check_call(
|
||||||
|
[
|
||||||
|
'fpm',
|
||||||
|
'--force',
|
||||||
|
'-s',
|
||||||
|
'dir',
|
||||||
|
'-t',
|
||||||
|
target,
|
||||||
|
'--name',
|
||||||
|
'httpie',
|
||||||
|
'--version',
|
||||||
|
httpie.__version__,
|
||||||
|
'--description',
|
||||||
|
httpie.__doc__.strip(),
|
||||||
|
'--license',
|
||||||
|
httpie.__licence__,
|
||||||
|
*processed_deps,
|
||||||
|
*processed_files,
|
||||||
|
],
|
||||||
|
cwd=DIST_DIR,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
binaries = dict(build_binaries())
|
||||||
|
build_packages(binaries['http_cli'], binaries['httpie_cli'])
|
||||||
|
|
||||||
|
# Rename http_cli/httpie_cli to http/httpie
|
||||||
|
binaries['http_cli'].rename(DIST_DIR / 'http')
|
||||||
|
binaries['httpie_cli'].rename(DIST_DIR / 'httpie')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
22
extras/packaging/linux/get_release_artifacts.sh
Executable file
22
extras/packaging/linux/get_release_artifacts.sh
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -xe
|
||||||
|
|
||||||
|
REPO_ROOT=../../../
|
||||||
|
ARTIFACTS_DIR=$(pwd)/artifacts
|
||||||
|
|
||||||
|
# Reset the ARTIFACTS_DIR.
|
||||||
|
rm -rf $ARTIFACTS_DIR
|
||||||
|
mkdir -p $ARTIFACTS_DIR
|
||||||
|
|
||||||
|
# Operate on the repository root to have the proper
|
||||||
|
# docker context.
|
||||||
|
pushd $REPO_ROOT
|
||||||
|
|
||||||
|
# Build the PyInstaller image
|
||||||
|
docker build -t pyinstaller-httpie -f extras/packaging/linux/Dockerfile .
|
||||||
|
|
||||||
|
# Copy the artifacts to the designated directory.
|
||||||
|
docker run --rm -i -v $ARTIFACTS_DIR:/artifacts pyinstaller-httpie:latest
|
||||||
|
|
||||||
|
popd
|
14
extras/packaging/linux/scripts/hooks/hook-pip.py
Normal file
14
extras/packaging/linux/scripts/hooks/hook-pip.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from PyInstaller.utils.hooks import collect_all
|
||||||
|
|
||||||
|
def hook(hook_api):
|
||||||
|
for pkg in [
|
||||||
|
'pip',
|
||||||
|
'setuptools',
|
||||||
|
'distutils',
|
||||||
|
'pkg_resources'
|
||||||
|
]:
|
||||||
|
datas, binaries, hiddenimports = collect_all(pkg)
|
||||||
|
hook_api.add_datas(datas)
|
||||||
|
hook_api.add_binaries(binaries)
|
||||||
|
hook_api.add_imports(*hiddenimports)
|
5
extras/packaging/linux/scripts/http_cli.py
Normal file
5
extras/packaging/linux/scripts/http_cli.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from httpie.__main__ import main
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
sys.exit(main())
|
5
extras/packaging/linux/scripts/httpie_cli.py
Normal file
5
extras/packaging/linux/scripts/httpie_cli.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from httpie.manager.__main__ import main
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import sys
|
||||||
|
sys.exit(main())
|
39
extras/profiling/README.md
Normal file
39
extras/profiling/README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# HTTPie Benchmarking Infrastructure
|
||||||
|
|
||||||
|
This directory includes the benchmarks we use for testing HTTPie's speed and the
|
||||||
|
infrastructure to automate this testing across versions.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Ensure the following requirements are satisfied:
|
||||||
|
|
||||||
|
- Python 3.7+
|
||||||
|
- `pyperf`
|
||||||
|
|
||||||
|
Then, run the `extras/profiling/run.py`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ python extras/profiling/run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Without any options, this command will initially create an isolated environment
|
||||||
|
and install `httpie` from the latest commit. Then it will create a second
|
||||||
|
environment with the `master` of the current repository and run the benchmarks
|
||||||
|
on both of them. It will compare the results and print it as a markdown table:
|
||||||
|
|
||||||
|
| Benchmark | master | this_branch |
|
||||||
|
| -------------------------------------- | :----: | :------------------: |
|
||||||
|
| `http --version` (startup) | 201 ms | 174 ms: 1.16x faster |
|
||||||
|
| `http --offline pie.dev/get` (startup) | 200 ms | 174 ms: 1.15x faster |
|
||||||
|
| Geometric mean | (ref) | 1.10x faster |
|
||||||
|
|
||||||
|
If your `master` branch is not up-to-date, you can get a fresh clone by passing
|
||||||
|
`--fresh` option. This way, the benchmark runner will clone the `httpie/cli`
|
||||||
|
repo from `GitHub` and use it as the baseline.
|
||||||
|
|
||||||
|
You can customize these branches by passing `--local-repo`/`--target-branch`,
|
||||||
|
and customize the repos by passing `--local-repo`/`--target-repo` (can either
|
||||||
|
take a URL or a path).
|
||||||
|
|
||||||
|
If you want to run a third environment with additional dependencies (such as
|
||||||
|
`pyOpenSSL`), you can pass `--complex`.
|
@ -9,11 +9,11 @@ timings.
|
|||||||
|
|
||||||
The benchmarks are run through 'pyperf', which allows to
|
The benchmarks are run through 'pyperf', which allows to
|
||||||
do get very precise results. For micro-benchmarks like startup,
|
do get very precise results. For micro-benchmarks like startup,
|
||||||
please run `pyperf system tune` to get even more acurrate results.
|
please run `pyperf system tune` to get even more accurate results.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
# Run everything as usual, the default is that we do 3 warmup runs
|
# Run everything as usual, the default is that we do 3 warm-up runs
|
||||||
# and 5 actual runs.
|
# and 5 actual runs.
|
||||||
$ python extras/profiling/benchmarks.py
|
$ python extras/profiling/benchmarks.py
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ Examples:
|
|||||||
$ python extras/profiling/benchmarks.py --fast
|
$ python extras/profiling/benchmarks.py --fast
|
||||||
|
|
||||||
# For verify everything works as expected, pass --debug-single-value.
|
# For verify everything works as expected, pass --debug-single-value.
|
||||||
# It will only run everything once, so the resuls are not realiable. But
|
# It will only run everything once, so the resuls are not reliable. But
|
||||||
# very useful when iterating on a benchmark
|
# very useful when iterating on a benchmark
|
||||||
$ python extras/profiling/benchmarks.py --debug-single-value
|
$ python extras/profiling/benchmarks.py --debug-single-value
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ DownloadRunner('download', '`http --download :/big_file.txt` (3GB)', '3G')
|
|||||||
def main() -> None:
|
def main() -> None:
|
||||||
# PyPerf will bring it's own argument parser, so configure the script.
|
# PyPerf will bring it's own argument parser, so configure the script.
|
||||||
# The somewhat fast and also precise enough configuration is this. We run
|
# The somewhat fast and also precise enough configuration is this. We run
|
||||||
# benchmarks 3 times to warmup (e.g especially for download benchmark, this
|
# benchmarks 3 times to warm up (e.g especially for download benchmark, this
|
||||||
# is important). And then 5 actual runs where we record.
|
# is important). And then 5 actual runs where we record.
|
||||||
sys.argv.extend(
|
sys.argv.extend(
|
||||||
['--worker', '--loops=1', '--warmup=3', '--values=5', '--processes=2']
|
['--worker', '--loops=1', '--warmup=3', '--values=5', '--processes=2']
|
||||||
|
@ -19,19 +19,19 @@ which would include additional dependencies like pyOpenSSL.
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
# Run everything as usual, and compare last commit with master
|
# Run everything as usual, and compare last commit with master
|
||||||
$ python extras/benchmarks/run.py
|
$ python extras/profiling/run.py
|
||||||
|
|
||||||
# Include complex environments
|
# Include complex environments
|
||||||
$ python extras/benchmarks/run.py --complex
|
$ python extras/profiling/run.py --complex
|
||||||
|
|
||||||
# Compare against a fresh copy
|
# 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
|
# 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)
|
# Debug changes made on this script (only run benchmarks once)
|
||||||
$ python extras/benchmarks/run.py --debug
|
$ python extras/profiling/run.py --debug
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
@ -50,7 +50,7 @@ from typing import (IO, Dict, Generator, Iterable, List, Optional,
|
|||||||
BENCHMARK_SCRIPT = Path(__file__).parent / 'benchmarks.py'
|
BENCHMARK_SCRIPT = Path(__file__).parent / 'benchmarks.py'
|
||||||
CURRENT_REPO = Path(__file__).parent.parent.parent
|
CURRENT_REPO = Path(__file__).parent.parent.parent
|
||||||
|
|
||||||
GITHUB_URL = 'https://github.com/httpie/httpie.git'
|
GITHUB_URL = 'https://github.com/httpie/cli.git'
|
||||||
TARGET_BRANCH = 'master'
|
TARGET_BRANCH = 'master'
|
||||||
|
|
||||||
# Additional dependencies for --complex
|
# Additional dependencies for --complex
|
||||||
|
188
extras/scripts/generate_man_pages.py
Normal file
188
extras/scripts/generate_man_pages.py
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
import os
|
||||||
|
import re
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Iterator, Iterable
|
||||||
|
|
||||||
|
|
||||||
|
# So that httpie.cli.definition can provide man-page-specific output. Must be set before importing httpie.
|
||||||
|
os.environ['HTTPIE_BUILDING_MAN_PAGES'] = '1'
|
||||||
|
|
||||||
|
import httpie
|
||||||
|
from httpie.cli.definition import options as core_options, IS_MAN_PAGE
|
||||||
|
from httpie.cli.options import ParserSpec
|
||||||
|
from httpie.manager.cli import options as manager_options
|
||||||
|
from httpie.output.ui.rich_help import OptionsHighlighter, to_usage
|
||||||
|
from httpie.output.ui.rich_utils import render_as_string
|
||||||
|
|
||||||
|
|
||||||
|
assert IS_MAN_PAGE, 'CLI definition does not understand we’re building man pages'
|
||||||
|
|
||||||
|
# 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]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ManPageBuilder:
|
||||||
|
def __init__(self):
|
||||||
|
self.source = []
|
||||||
|
|
||||||
|
def title_line(
|
||||||
|
self,
|
||||||
|
full_name: str,
|
||||||
|
program_name: str,
|
||||||
|
program_version: str,
|
||||||
|
last_edit_date: str,
|
||||||
|
) -> None:
|
||||||
|
self.source.append(
|
||||||
|
f'.TH {program_name} 1 "{last_edit_date}" '
|
||||||
|
f'"{full_name} {program_version}" "{full_name} Manual"'
|
||||||
|
)
|
||||||
|
|
||||||
|
def set_name(self, program_name: str) -> None:
|
||||||
|
with self.section('NAME'):
|
||||||
|
self.write(program_name)
|
||||||
|
|
||||||
|
def write(self, text: str, *, bold: bool = False) -> None:
|
||||||
|
if bold:
|
||||||
|
text = '.B ' + text
|
||||||
|
self.source.append(text)
|
||||||
|
|
||||||
|
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:
|
||||||
|
text += f' {self.underline(metavar)}'
|
||||||
|
self.write(f'.IP "{text}"')
|
||||||
|
|
||||||
|
def build(self) -> str:
|
||||||
|
return '\n'.join(self.source)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def section(self, section_name: str) -> Iterator[None]:
|
||||||
|
self.write(f'.SH {section_name}')
|
||||||
|
self.in_section = True
|
||||||
|
yield
|
||||||
|
self.in_section = False
|
||||||
|
|
||||||
|
def underline(self, text: str) -> str:
|
||||||
|
return r'\fI\,{}\/\fR'.format(text)
|
||||||
|
|
||||||
|
def boldify(self, text: str) -> str:
|
||||||
|
return r'\fB\,{}\/\fR'.format(text)
|
||||||
|
|
||||||
|
|
||||||
|
def _escape_and_dedent(text: str) -> str:
|
||||||
|
lines = []
|
||||||
|
for should_act, line in enumerate(text.splitlines()):
|
||||||
|
# Only dedent after the first line.
|
||||||
|
if should_act:
|
||||||
|
if line.startswith(' '):
|
||||||
|
line = line[4:]
|
||||||
|
|
||||||
|
lines.append(line)
|
||||||
|
return '\n'.join(lines).translate(ESCAPE_MAP)
|
||||||
|
|
||||||
|
|
||||||
|
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',
|
||||||
|
program_name=program_name,
|
||||||
|
program_version=httpie.__version__,
|
||||||
|
last_edit_date=httpie.__date__,
|
||||||
|
)
|
||||||
|
builder.set_name(program_name)
|
||||||
|
|
||||||
|
with builder.section('SYNOPSIS'):
|
||||||
|
# `http` and `https` are commands that can be directly used, so they can 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):
|
||||||
|
if group.description:
|
||||||
|
builder.write(group.description)
|
||||||
|
|
||||||
|
for argument in group.arguments:
|
||||||
|
if argument.is_hidden:
|
||||||
|
continue
|
||||||
|
|
||||||
|
raw_arg = argument.serialize(isolation_mode=True)
|
||||||
|
|
||||||
|
metavar = raw_arg.get('metavar')
|
||||||
|
if raw_arg.get('is_positional'):
|
||||||
|
# In case of positional arguments, metavar is always equal
|
||||||
|
# to the list of options (e.g `METHOD`).
|
||||||
|
metavar = None
|
||||||
|
builder.add_options(raw_arg['options'], metavar=metavar)
|
||||||
|
|
||||||
|
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, 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, **config))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -3,6 +3,7 @@ HTTPie: modern, user-friendly command-line HTTP client for the API era.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '3.0.0'
|
__version__ = '3.2.4'
|
||||||
|
__date__ = '2024-11-01'
|
||||||
__author__ = 'Jakub Roztocil'
|
__author__ = 'Jakub Roztocil'
|
||||||
__licence__ = 'BSD'
|
__licence__ = 'BSD'
|
||||||
|
@ -10,7 +10,8 @@ from urllib.parse import urlsplit
|
|||||||
from requests.utils import get_netrc_auth
|
from requests.utils import get_netrc_auth
|
||||||
|
|
||||||
from .argtypes import (
|
from .argtypes import (
|
||||||
AuthCredentials, KeyValueArgType, PARSED_DEFAULT_FORMAT_OPTIONS,
|
AuthCredentials, SSLCredentials, KeyValueArgType,
|
||||||
|
PARSED_DEFAULT_FORMAT_OPTIONS,
|
||||||
parse_auth,
|
parse_auth,
|
||||||
parse_format_options,
|
parse_format_options,
|
||||||
)
|
)
|
||||||
@ -47,12 +48,39 @@ class HTTPieHelpFormatter(RawDescriptionHelpFormatter):
|
|||||||
text = dedent(text).strip() + '\n\n'
|
text = dedent(text).strip() + '\n\n'
|
||||||
return text.splitlines()
|
return text.splitlines()
|
||||||
|
|
||||||
|
def add_usage(self, usage, actions, groups, prefix=None):
|
||||||
|
# Only display the positional arguments
|
||||||
|
displayed_actions = [
|
||||||
|
action
|
||||||
|
for action in actions
|
||||||
|
if not action.option_strings
|
||||||
|
]
|
||||||
|
|
||||||
|
_, exception, _ = sys.exc_info()
|
||||||
|
if (
|
||||||
|
isinstance(exception, argparse.ArgumentError)
|
||||||
|
and len(exception.args) >= 1
|
||||||
|
and isinstance(exception.args[0], argparse.Action)
|
||||||
|
):
|
||||||
|
# add_usage path is also taken when you pass an invalid option,
|
||||||
|
# e.g --style=invalid. If something like that happens, we want
|
||||||
|
# to include to action that caused to the invalid usage into
|
||||||
|
# the list of actions we are displaying.
|
||||||
|
displayed_actions.insert(0, exception.args[0])
|
||||||
|
|
||||||
|
super().add_usage(
|
||||||
|
usage,
|
||||||
|
displayed_actions,
|
||||||
|
groups,
|
||||||
|
prefix="usage:\n "
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# TODO: refactor and design type-annotated data structures
|
# TODO: refactor and design type-annotated data structures
|
||||||
# for raw args + parsed args and keep things immutable.
|
# for raw args + parsed args and keep things immutable.
|
||||||
class BaseHTTPieArgumentParser(argparse.ArgumentParser):
|
class BaseHTTPieArgumentParser(argparse.ArgumentParser):
|
||||||
def __init__(self, *args, formatter_class=HTTPieHelpFormatter, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, formatter_class=formatter_class, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.env = None
|
self.env = None
|
||||||
self.args = None
|
self.args = None
|
||||||
self.has_stdin_data = False
|
self.has_stdin_data = False
|
||||||
@ -115,9 +143,9 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, formatter_class=HTTPieHelpFormatter, **kwargs):
|
||||||
kwargs.setdefault('add_help', False)
|
kwargs.setdefault('add_help', False)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, formatter_class=formatter_class, **kwargs)
|
||||||
|
|
||||||
# noinspection PyMethodOverriding
|
# noinspection PyMethodOverriding
|
||||||
def parse_args(
|
def parse_args(
|
||||||
@ -127,6 +155,7 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
|
|||||||
namespace=None
|
namespace=None
|
||||||
) -> argparse.Namespace:
|
) -> argparse.Namespace:
|
||||||
self.env = env
|
self.env = env
|
||||||
|
self.env.args = namespace = namespace or argparse.Namespace()
|
||||||
self.args, no_options = super().parse_known_args(args, namespace)
|
self.args, no_options = super().parse_known_args(args, namespace)
|
||||||
if self.args.debug:
|
if self.args.debug:
|
||||||
self.args.traceback = True
|
self.args.traceback = True
|
||||||
@ -148,6 +177,7 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
|
|||||||
self._parse_items()
|
self._parse_items()
|
||||||
self._process_url()
|
self._process_url()
|
||||||
self._process_auth()
|
self._process_auth()
|
||||||
|
self._process_ssl_cert()
|
||||||
|
|
||||||
if self.args.raw is not None:
|
if self.args.raw is not None:
|
||||||
self._body_from_input(self.args.raw)
|
self._body_from_input(self.args.raw)
|
||||||
@ -230,9 +260,24 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
|
|||||||
self.env.stdout_isatty = False
|
self.env.stdout_isatty = False
|
||||||
|
|
||||||
if self.args.quiet:
|
if self.args.quiet:
|
||||||
|
self.env.quiet = self.args.quiet
|
||||||
self.env.stderr = self.env.devnull
|
self.env.stderr = self.env.devnull
|
||||||
if not (self.args.output_file_specified and not self.args.download):
|
if not (self.args.output_file_specified and not self.args.download):
|
||||||
self.env.stdout = self.env.devnull
|
self.env.stdout = self.env.devnull
|
||||||
|
self.env.apply_warnings_filter()
|
||||||
|
|
||||||
|
def _process_ssl_cert(self):
|
||||||
|
from httpie.ssl_ import _is_key_file_encrypted
|
||||||
|
|
||||||
|
if self.args.cert_key_pass is None:
|
||||||
|
self.args.cert_key_pass = SSLCredentials(None)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.args.cert_key is not None
|
||||||
|
and self.args.cert_key_pass.value is None
|
||||||
|
and _is_key_file_encrypted(self.args.cert_key)
|
||||||
|
):
|
||||||
|
self.args.cert_key_pass.prompt_password(self.args.cert_key)
|
||||||
|
|
||||||
def _process_auth(self):
|
def _process_auth(self):
|
||||||
# TODO: refactor & simplify this method.
|
# TODO: refactor & simplify this method.
|
||||||
@ -512,3 +557,57 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
|
|||||||
for options_group in format_options:
|
for options_group in format_options:
|
||||||
parsed_options = parse_format_options(options_group, defaults=parsed_options)
|
parsed_options = parse_format_options(options_group, defaults=parsed_options)
|
||||||
self.args.format_options = parsed_options
|
self.args.format_options = parsed_options
|
||||||
|
|
||||||
|
def print_manual(self):
|
||||||
|
from httpie.output.ui import man_pages
|
||||||
|
|
||||||
|
if man_pages.is_available(self.env.program_name):
|
||||||
|
man_pages.display_for(self.env, self.env.program_name)
|
||||||
|
return None
|
||||||
|
|
||||||
|
text = self.format_help()
|
||||||
|
with self.env.rich_console.pager():
|
||||||
|
self.env.rich_console.print(
|
||||||
|
text,
|
||||||
|
highlight=False
|
||||||
|
)
|
||||||
|
|
||||||
|
def print_usage(self, file):
|
||||||
|
from rich.text import Text
|
||||||
|
from httpie.output.ui import rich_help
|
||||||
|
|
||||||
|
whitelist = set()
|
||||||
|
_, exception, _ = sys.exc_info()
|
||||||
|
if (
|
||||||
|
isinstance(exception, argparse.ArgumentError)
|
||||||
|
and len(exception.args) >= 1
|
||||||
|
and isinstance(exception.args[0], argparse.Action)
|
||||||
|
and exception.args[0].option_strings
|
||||||
|
):
|
||||||
|
# add_usage path is also taken when you pass an invalid option,
|
||||||
|
# e.g --style=invalid. If something like that happens, we want
|
||||||
|
# to include to action that caused to the invalid usage into
|
||||||
|
# the list of actions we are displaying.
|
||||||
|
whitelist.add(exception.args[0].option_strings[0])
|
||||||
|
|
||||||
|
usage_text = Text('usage', style='bold')
|
||||||
|
usage_text.append(':\n ')
|
||||||
|
usage_text.append(rich_help.to_usage(self.spec, whitelist=whitelist))
|
||||||
|
self.env.rich_error_console.print(usage_text)
|
||||||
|
|
||||||
|
def error(self, message):
|
||||||
|
"""Prints a usage message incorporating the message to stderr and
|
||||||
|
exits."""
|
||||||
|
self.print_usage(sys.stderr)
|
||||||
|
self.env.rich_error_console.print(
|
||||||
|
dedent(
|
||||||
|
f'''
|
||||||
|
[bold]error[/bold]:
|
||||||
|
{message}
|
||||||
|
|
||||||
|
[bold]for more information[/bold]:
|
||||||
|
run '{self.prog} --help' or visit https://httpie.io/docs/cli
|
||||||
|
'''.rstrip()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.exit(2)
|
||||||
|
@ -130,16 +130,11 @@ class KeyValueArgType:
|
|||||||
return tokens
|
return tokens
|
||||||
|
|
||||||
|
|
||||||
class AuthCredentials(KeyValueArg):
|
class PromptMixin:
|
||||||
"""Represents parsed credentials."""
|
def _prompt_password(self, prompt: str) -> str:
|
||||||
|
prompt_text = f'http: {prompt}: '
|
||||||
def has_password(self) -> bool:
|
|
||||||
return self.value is not None
|
|
||||||
|
|
||||||
def prompt_password(self, host: str):
|
|
||||||
prompt_text = f'http: password for {self.key}@{host}: '
|
|
||||||
try:
|
try:
|
||||||
self.value = self._getpass(prompt_text)
|
return self._getpass(prompt_text)
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
sys.stderr.write('\n')
|
sys.stderr.write('\n')
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@ -150,6 +145,26 @@ class AuthCredentials(KeyValueArg):
|
|||||||
return getpass.getpass(str(prompt))
|
return getpass.getpass(str(prompt))
|
||||||
|
|
||||||
|
|
||||||
|
class SSLCredentials(PromptMixin):
|
||||||
|
"""Represents the passphrase for the certificate's key."""
|
||||||
|
|
||||||
|
def __init__(self, value: Optional[str]) -> None:
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def prompt_password(self, key_file: str) -> None:
|
||||||
|
self.value = self._prompt_password(f'passphrase for {key_file}')
|
||||||
|
|
||||||
|
|
||||||
|
class AuthCredentials(KeyValueArg, PromptMixin):
|
||||||
|
"""Represents parsed credentials."""
|
||||||
|
|
||||||
|
def has_password(self) -> bool:
|
||||||
|
return self.value is not None
|
||||||
|
|
||||||
|
def prompt_password(self, host: str) -> None:
|
||||||
|
self.value = self._prompt_password(f'password for {self.key}@{host}:')
|
||||||
|
|
||||||
|
|
||||||
class AuthCredentialsArgType(KeyValueArgType):
|
class AuthCredentialsArgType(KeyValueArgType):
|
||||||
"""A key-value arg type that parses credentials."""
|
"""A key-value arg type that parses credentials."""
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ URL_SCHEME_RE = re.compile(r'^[a-z][a-z0-9.+-]*://', re.IGNORECASE)
|
|||||||
|
|
||||||
HTTP_POST = 'POST'
|
HTTP_POST = 'POST'
|
||||||
HTTP_GET = 'GET'
|
HTTP_GET = 'GET'
|
||||||
|
HTTP_OPTIONS = 'OPTIONS'
|
||||||
|
|
||||||
# Various separators used in args
|
# Various separators used in args
|
||||||
SEPARATOR_HEADER = ':'
|
SEPARATOR_HEADER = ':'
|
||||||
@ -90,13 +91,19 @@ OUTPUT_OPTIONS = frozenset({
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Pretty
|
# Pretty
|
||||||
|
|
||||||
|
|
||||||
|
class PrettyOptions(enum.Enum):
|
||||||
|
STDOUT_TTY_ONLY = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
PRETTY_MAP = {
|
PRETTY_MAP = {
|
||||||
'all': ['format', 'colors'],
|
'all': ['format', 'colors'],
|
||||||
'colors': ['colors'],
|
'colors': ['colors'],
|
||||||
'format': ['format'],
|
'format': ['format'],
|
||||||
'none': []
|
'none': []
|
||||||
}
|
}
|
||||||
PRETTY_STDOUT_TTY_ONLY = object()
|
PRETTY_STDOUT_TTY_ONLY = PrettyOptions.STDOUT_TTY_ONLY
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_FORMAT_OPTIONS = [
|
DEFAULT_FORMAT_OPTIONS = [
|
||||||
@ -125,9 +132,3 @@ class RequestType(enum.Enum):
|
|||||||
FORM = enum.auto()
|
FORM = enum.auto()
|
||||||
MULTIPART = enum.auto()
|
MULTIPART = enum.auto()
|
||||||
JSON = enum.auto()
|
JSON = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
OPEN_BRACKET = '['
|
|
||||||
CLOSE_BRACKET = ']'
|
|
||||||
BACKSLASH = '\\'
|
|
||||||
HIGHLIGHTER = '^'
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -35,6 +35,16 @@ class HTTPHeadersDict(CIMultiDict, BaseMultiDict):
|
|||||||
|
|
||||||
super().add(key, value)
|
super().add(key, value)
|
||||||
|
|
||||||
|
def remove_item(self, key, value):
|
||||||
|
"""
|
||||||
|
Remove a (key, value) pair from the dict.
|
||||||
|
"""
|
||||||
|
existing_values = self.popall(key)
|
||||||
|
existing_values.remove(value)
|
||||||
|
|
||||||
|
for value in existing_values:
|
||||||
|
self.add(key, value)
|
||||||
|
|
||||||
|
|
||||||
class RequestJSONDataDict(OrderedDict):
|
class RequestJSONDataDict(OrderedDict):
|
||||||
pass
|
pass
|
||||||
|
@ -1,344 +0,0 @@
|
|||||||
from enum import Enum, auto
|
|
||||||
from typing import (
|
|
||||||
Any,
|
|
||||||
Iterator,
|
|
||||||
NamedTuple,
|
|
||||||
Optional,
|
|
||||||
List,
|
|
||||||
NoReturn,
|
|
||||||
Type,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
from httpie.cli.constants import OPEN_BRACKET, CLOSE_BRACKET, BACKSLASH, HIGHLIGHTER
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPieSyntaxError(ValueError):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
source: str,
|
|
||||||
token: Optional['Token'],
|
|
||||||
message: str,
|
|
||||||
message_kind: str = 'Syntax',
|
|
||||||
) -> None:
|
|
||||||
self.source = source
|
|
||||||
self.token = token
|
|
||||||
self.message = message
|
|
||||||
self.message_kind = message_kind
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
lines = [f'HTTPie {self.message_kind} Error: {self.message}']
|
|
||||||
if self.token is not None:
|
|
||||||
lines.append(self.source)
|
|
||||||
lines.append(
|
|
||||||
' ' * (self.token.start)
|
|
||||||
+ HIGHLIGHTER * (self.token.end - self.token.start)
|
|
||||||
)
|
|
||||||
return '\n'.join(lines)
|
|
||||||
|
|
||||||
|
|
||||||
class TokenKind(Enum):
|
|
||||||
TEXT = auto()
|
|
||||||
NUMBER = auto()
|
|
||||||
LEFT_BRACKET = auto()
|
|
||||||
RIGHT_BRACKET = auto()
|
|
||||||
|
|
||||||
def to_name(self) -> str:
|
|
||||||
for key, value in OPERATORS.items():
|
|
||||||
if value is self:
|
|
||||||
return repr(key)
|
|
||||||
else:
|
|
||||||
return 'a ' + self.name.lower()
|
|
||||||
|
|
||||||
|
|
||||||
OPERATORS = {OPEN_BRACKET: TokenKind.LEFT_BRACKET, CLOSE_BRACKET: TokenKind.RIGHT_BRACKET}
|
|
||||||
SPECIAL_CHARS = OPERATORS.keys() | {BACKSLASH}
|
|
||||||
|
|
||||||
|
|
||||||
class Token(NamedTuple):
|
|
||||||
kind: TokenKind
|
|
||||||
value: Union[str, int]
|
|
||||||
start: int
|
|
||||||
end: int
|
|
||||||
|
|
||||||
|
|
||||||
def assert_cant_happen() -> NoReturn:
|
|
||||||
raise ValueError('Unexpected value')
|
|
||||||
|
|
||||||
|
|
||||||
def check_escaped_int(value: str) -> str:
|
|
||||||
if not value.startswith(BACKSLASH):
|
|
||||||
raise ValueError('Not an escaped int')
|
|
||||||
|
|
||||||
try:
|
|
||||||
int(value[1:])
|
|
||||||
except ValueError as exc:
|
|
||||||
raise ValueError('Not an escaped int') from exc
|
|
||||||
else:
|
|
||||||
return value[1:]
|
|
||||||
|
|
||||||
|
|
||||||
def tokenize(source: str) -> Iterator[Token]:
|
|
||||||
cursor = 0
|
|
||||||
backslashes = 0
|
|
||||||
buffer = []
|
|
||||||
|
|
||||||
def send_buffer() -> Iterator[Token]:
|
|
||||||
nonlocal backslashes
|
|
||||||
if not buffer:
|
|
||||||
return None
|
|
||||||
|
|
||||||
value = ''.join(buffer)
|
|
||||||
for variation, kind in [
|
|
||||||
(int, TokenKind.NUMBER),
|
|
||||||
(check_escaped_int, TokenKind.TEXT),
|
|
||||||
]:
|
|
||||||
try:
|
|
||||||
value = variation(value)
|
|
||||||
except ValueError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
kind = TokenKind.TEXT
|
|
||||||
|
|
||||||
yield Token(
|
|
||||||
kind, value, start=cursor - (len(buffer) + backslashes), end=cursor
|
|
||||||
)
|
|
||||||
buffer.clear()
|
|
||||||
backslashes = 0
|
|
||||||
|
|
||||||
def can_advance() -> bool:
|
|
||||||
return cursor < len(source)
|
|
||||||
|
|
||||||
while can_advance():
|
|
||||||
index = source[cursor]
|
|
||||||
if index in OPERATORS:
|
|
||||||
yield from send_buffer()
|
|
||||||
yield Token(OPERATORS[index], index, cursor, cursor + 1)
|
|
||||||
elif index == BACKSLASH and can_advance():
|
|
||||||
if source[cursor + 1] in SPECIAL_CHARS:
|
|
||||||
backslashes += 1
|
|
||||||
else:
|
|
||||||
buffer.append(index)
|
|
||||||
|
|
||||||
buffer.append(source[cursor + 1])
|
|
||||||
cursor += 1
|
|
||||||
else:
|
|
||||||
buffer.append(index)
|
|
||||||
|
|
||||||
cursor += 1
|
|
||||||
|
|
||||||
yield from send_buffer()
|
|
||||||
|
|
||||||
|
|
||||||
class PathAction(Enum):
|
|
||||||
KEY = auto()
|
|
||||||
INDEX = auto()
|
|
||||||
APPEND = auto()
|
|
||||||
|
|
||||||
# Pseudo action, used by the interpreter
|
|
||||||
SET = auto()
|
|
||||||
|
|
||||||
def to_string(self) -> str:
|
|
||||||
return self.name.lower()
|
|
||||||
|
|
||||||
|
|
||||||
class Path:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
kind: PathAction,
|
|
||||||
accessor: Optional[Union[str, int]] = None,
|
|
||||||
tokens: Optional[List[Token]] = None,
|
|
||||||
is_root: bool = False,
|
|
||||||
):
|
|
||||||
self.kind = kind
|
|
||||||
self.accessor = accessor
|
|
||||||
self.tokens = tokens or []
|
|
||||||
self.is_root = is_root
|
|
||||||
|
|
||||||
def reconstruct(self) -> str:
|
|
||||||
if self.kind is PathAction.KEY:
|
|
||||||
if self.is_root:
|
|
||||||
return str(self.accessor)
|
|
||||||
return OPEN_BRACKET + self.accessor + CLOSE_BRACKET
|
|
||||||
elif self.kind is PathAction.INDEX:
|
|
||||||
return OPEN_BRACKET + str(self.accessor) + CLOSE_BRACKET
|
|
||||||
elif self.kind is PathAction.APPEND:
|
|
||||||
return OPEN_BRACKET + CLOSE_BRACKET
|
|
||||||
else:
|
|
||||||
assert_cant_happen()
|
|
||||||
|
|
||||||
|
|
||||||
def parse(source: str) -> Iterator[Path]:
|
|
||||||
"""
|
|
||||||
start: literal? path*
|
|
||||||
|
|
||||||
literal: TEXT | NUMBER
|
|
||||||
|
|
||||||
path:
|
|
||||||
key_path
|
|
||||||
| index_path
|
|
||||||
| append_path
|
|
||||||
key_path: LEFT_BRACKET TEXT RIGHT_BRACKET
|
|
||||||
index_path: LEFT_BRACKET NUMBER RIGHT_BRACKET
|
|
||||||
append_path: LEFT_BRACKET RIGHT_BRACKET
|
|
||||||
"""
|
|
||||||
|
|
||||||
tokens = list(tokenize(source))
|
|
||||||
cursor = 0
|
|
||||||
|
|
||||||
def can_advance():
|
|
||||||
return cursor < len(tokens)
|
|
||||||
|
|
||||||
def expect(*kinds):
|
|
||||||
nonlocal cursor
|
|
||||||
|
|
||||||
assert len(kinds) > 0
|
|
||||||
if can_advance():
|
|
||||||
token = tokens[cursor]
|
|
||||||
cursor += 1
|
|
||||||
if token.kind in kinds:
|
|
||||||
return token
|
|
||||||
elif tokens:
|
|
||||||
token = tokens[-1]._replace(
|
|
||||||
start=tokens[-1].end + 0, end=tokens[-1].end + 1
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
token = None
|
|
||||||
|
|
||||||
if len(kinds) == 1:
|
|
||||||
suffix = kinds[0].to_name()
|
|
||||||
else:
|
|
||||||
suffix = ', '.join(kind.to_name() for kind in kinds[:-1])
|
|
||||||
suffix += ' or ' + kinds[-1].to_name()
|
|
||||||
|
|
||||||
message = f'Expecting {suffix}'
|
|
||||||
raise HTTPieSyntaxError(source, token, message)
|
|
||||||
|
|
||||||
root = Path(PathAction.KEY, '', is_root=True)
|
|
||||||
if can_advance():
|
|
||||||
token = tokens[cursor]
|
|
||||||
if token.kind in {TokenKind.TEXT, TokenKind.NUMBER}:
|
|
||||||
token = expect(TokenKind.TEXT, TokenKind.NUMBER)
|
|
||||||
root.accessor = str(token.value)
|
|
||||||
root.tokens.append(token)
|
|
||||||
|
|
||||||
yield root
|
|
||||||
|
|
||||||
while can_advance():
|
|
||||||
path_tokens = []
|
|
||||||
path_tokens.append(expect(TokenKind.LEFT_BRACKET))
|
|
||||||
|
|
||||||
token = expect(
|
|
||||||
TokenKind.TEXT, TokenKind.NUMBER, TokenKind.RIGHT_BRACKET
|
|
||||||
)
|
|
||||||
path_tokens.append(token)
|
|
||||||
if token.kind is TokenKind.RIGHT_BRACKET:
|
|
||||||
path = Path(PathAction.APPEND, tokens=path_tokens)
|
|
||||||
elif token.kind is TokenKind.TEXT:
|
|
||||||
path = Path(PathAction.KEY, token.value, tokens=path_tokens)
|
|
||||||
path_tokens.append(expect(TokenKind.RIGHT_BRACKET))
|
|
||||||
elif token.kind is TokenKind.NUMBER:
|
|
||||||
path = Path(PathAction.INDEX, token.value, tokens=path_tokens)
|
|
||||||
path_tokens.append(expect(TokenKind.RIGHT_BRACKET))
|
|
||||||
else:
|
|
||||||
assert_cant_happen()
|
|
||||||
yield path
|
|
||||||
|
|
||||||
|
|
||||||
JSON_TYPE_MAPPING = {
|
|
||||||
dict: 'object',
|
|
||||||
list: 'array',
|
|
||||||
int: 'number',
|
|
||||||
float: 'number',
|
|
||||||
str: 'string',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def interpret(context: Any, key: str, value: Any) -> Any:
|
|
||||||
cursor = context
|
|
||||||
|
|
||||||
paths = list(parse(key))
|
|
||||||
paths.append(Path(PathAction.SET, value))
|
|
||||||
|
|
||||||
def type_check(index: int, path: Path, expected_type: Type[Any]) -> None:
|
|
||||||
if not isinstance(cursor, expected_type):
|
|
||||||
if path.tokens:
|
|
||||||
pseudo_token = Token(
|
|
||||||
None, None, path.tokens[0].start, path.tokens[-1].end
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
pseudo_token = None
|
|
||||||
|
|
||||||
cursor_type = JSON_TYPE_MAPPING.get(
|
|
||||||
type(cursor), type(cursor).__name__
|
|
||||||
)
|
|
||||||
required_type = JSON_TYPE_MAPPING[expected_type]
|
|
||||||
|
|
||||||
message = f"Can't perform {path.kind.to_string()!r} based access on "
|
|
||||||
message += repr(
|
|
||||||
''.join(path.reconstruct() for path in paths[:index])
|
|
||||||
)
|
|
||||||
message += (
|
|
||||||
f' which has a type of {cursor_type!r} but this operation'
|
|
||||||
)
|
|
||||||
message += f' requires a type of {required_type!r}.'
|
|
||||||
raise HTTPieSyntaxError(
|
|
||||||
key, pseudo_token, message, message_kind='Type'
|
|
||||||
)
|
|
||||||
|
|
||||||
def object_for(kind: str) -> Any:
|
|
||||||
if kind is PathAction.KEY:
|
|
||||||
return {}
|
|
||||||
elif kind in {PathAction.INDEX, PathAction.APPEND}:
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
assert_cant_happen()
|
|
||||||
|
|
||||||
for index, (path, next_path) in enumerate(zip(paths, paths[1:])):
|
|
||||||
if path.kind is PathAction.KEY:
|
|
||||||
type_check(index, path, dict)
|
|
||||||
if next_path.kind is PathAction.SET:
|
|
||||||
cursor[path.accessor] = next_path.accessor
|
|
||||||
break
|
|
||||||
|
|
||||||
cursor = cursor.setdefault(
|
|
||||||
path.accessor, object_for(next_path.kind)
|
|
||||||
)
|
|
||||||
elif path.kind is PathAction.INDEX:
|
|
||||||
type_check(index, path, list)
|
|
||||||
if path.accessor < 0:
|
|
||||||
raise HTTPieSyntaxError(
|
|
||||||
key,
|
|
||||||
path.tokens[1],
|
|
||||||
'Negative indexes are not supported.',
|
|
||||||
message_kind='Value',
|
|
||||||
)
|
|
||||||
cursor.extend([None] * (path.accessor - len(cursor) + 1))
|
|
||||||
if next_path.kind is PathAction.SET:
|
|
||||||
cursor[path.accessor] = next_path.accessor
|
|
||||||
break
|
|
||||||
|
|
||||||
if cursor[path.accessor] is None:
|
|
||||||
cursor[path.accessor] = object_for(next_path.kind)
|
|
||||||
|
|
||||||
cursor = cursor[path.accessor]
|
|
||||||
elif path.kind is PathAction.APPEND:
|
|
||||||
type_check(index, path, list)
|
|
||||||
if next_path.kind is PathAction.SET:
|
|
||||||
cursor.append(next_path.accessor)
|
|
||||||
break
|
|
||||||
|
|
||||||
cursor.append(object_for(next_path.kind))
|
|
||||||
cursor = cursor[-1]
|
|
||||||
else:
|
|
||||||
assert_cant_happen()
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
|
|
||||||
def interpret_nested_json(pairs):
|
|
||||||
context = {}
|
|
||||||
for key, value in pairs:
|
|
||||||
interpret(context, key, value)
|
|
||||||
return context
|
|
20
httpie/cli/nested_json/__init__.py
Normal file
20
httpie/cli/nested_json/__init__.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
"""
|
||||||
|
A library for parsing the HTTPie nested JSON key syntax and constructing the resulting objects.
|
||||||
|
|
||||||
|
<https://httpie.io/docs/cli/nested-json>
|
||||||
|
|
||||||
|
It has no dependencies.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from .interpret import interpret_nested_json, unwrap_top_level_list_if_needed
|
||||||
|
from .errors import NestedJSONSyntaxError
|
||||||
|
from .tokens import EMPTY_STRING, NestedJSONArray
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'interpret_nested_json',
|
||||||
|
'unwrap_top_level_list_if_needed',
|
||||||
|
'EMPTY_STRING',
|
||||||
|
'NestedJSONArray',
|
||||||
|
'NestedJSONSyntaxError'
|
||||||
|
]
|
27
httpie/cli/nested_json/errors.py
Normal file
27
httpie/cli/nested_json/errors.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .tokens import Token, HIGHLIGHTER
|
||||||
|
|
||||||
|
|
||||||
|
class NestedJSONSyntaxError(ValueError):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
source: str,
|
||||||
|
token: Optional[Token],
|
||||||
|
message: str,
|
||||||
|
message_kind: str = 'Syntax',
|
||||||
|
) -> None:
|
||||||
|
self.source = source
|
||||||
|
self.token = token
|
||||||
|
self.message = message
|
||||||
|
self.message_kind = message_kind
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
lines = [f'HTTPie {self.message_kind} Error: {self.message}']
|
||||||
|
if self.token is not None:
|
||||||
|
lines.append(self.source)
|
||||||
|
lines.append(
|
||||||
|
' ' * self.token.start
|
||||||
|
+ HIGHLIGHTER * (self.token.end - self.token.start)
|
||||||
|
)
|
||||||
|
return '\n'.join(lines)
|
129
httpie/cli/nested_json/interpret.py
Normal file
129
httpie/cli/nested_json/interpret.py
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
from typing import Type, Union, Any, Iterable, Tuple
|
||||||
|
|
||||||
|
from .parse import parse, assert_cant_happen
|
||||||
|
from .errors import NestedJSONSyntaxError
|
||||||
|
from .tokens import EMPTY_STRING, TokenKind, Token, PathAction, Path, NestedJSONArray
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'interpret_nested_json',
|
||||||
|
'unwrap_top_level_list_if_needed',
|
||||||
|
]
|
||||||
|
|
||||||
|
JSONType = Type[Union[dict, list, int, float, str]]
|
||||||
|
JSON_TYPE_MAPPING = {
|
||||||
|
dict: 'object',
|
||||||
|
list: 'array',
|
||||||
|
int: 'number',
|
||||||
|
float: 'number',
|
||||||
|
str: 'string',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def interpret_nested_json(pairs: Iterable[Tuple[str, str]]) -> dict:
|
||||||
|
context = None
|
||||||
|
for key, value in pairs:
|
||||||
|
context = interpret(context, key, value)
|
||||||
|
return wrap_with_dict(context)
|
||||||
|
|
||||||
|
|
||||||
|
def interpret(context: Any, key: str, value: Any) -> Any:
|
||||||
|
cursor = context
|
||||||
|
paths = list(parse(key))
|
||||||
|
paths.append(Path(PathAction.SET, value))
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
def type_check(index: int, path: Path, expected_type: JSONType):
|
||||||
|
if not isinstance(cursor, expected_type):
|
||||||
|
if path.tokens:
|
||||||
|
pseudo_token = Token(
|
||||||
|
kind=TokenKind.PSEUDO,
|
||||||
|
value='',
|
||||||
|
start=path.tokens[0].start,
|
||||||
|
end=path.tokens[-1].end,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pseudo_token = None
|
||||||
|
cursor_type = JSON_TYPE_MAPPING.get(type(cursor), type(cursor).__name__)
|
||||||
|
required_type = JSON_TYPE_MAPPING[expected_type]
|
||||||
|
message = f'Cannot perform {path.kind.to_string()!r} based access on '
|
||||||
|
message += repr(''.join(path.reconstruct() for path in paths[:index]))
|
||||||
|
message += f' which has a type of {cursor_type!r} but this operation'
|
||||||
|
message += f' requires a type of {required_type!r}.'
|
||||||
|
raise NestedJSONSyntaxError(
|
||||||
|
source=key,
|
||||||
|
token=pseudo_token,
|
||||||
|
message=message,
|
||||||
|
message_kind='Type',
|
||||||
|
)
|
||||||
|
|
||||||
|
def object_for(kind: PathAction) -> Any:
|
||||||
|
if kind is PathAction.KEY:
|
||||||
|
return {}
|
||||||
|
elif kind in {PathAction.INDEX, PathAction.APPEND}:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
assert_cant_happen()
|
||||||
|
|
||||||
|
for index, (path, next_path) in enumerate(zip(paths, paths[1:])):
|
||||||
|
# If there is no context yet, set it.
|
||||||
|
if cursor is None:
|
||||||
|
context = cursor = object_for(path.kind)
|
||||||
|
if path.kind is PathAction.KEY:
|
||||||
|
type_check(index, path, dict)
|
||||||
|
if next_path.kind is PathAction.SET:
|
||||||
|
cursor[path.accessor] = next_path.accessor
|
||||||
|
break
|
||||||
|
cursor = cursor.setdefault(path.accessor, object_for(next_path.kind))
|
||||||
|
elif path.kind is PathAction.INDEX:
|
||||||
|
type_check(index, path, list)
|
||||||
|
if path.accessor < 0:
|
||||||
|
raise NestedJSONSyntaxError(
|
||||||
|
source=key,
|
||||||
|
token=path.tokens[1],
|
||||||
|
message='Negative indexes are not supported.',
|
||||||
|
message_kind='Value',
|
||||||
|
)
|
||||||
|
cursor.extend([None] * (path.accessor - len(cursor) + 1))
|
||||||
|
if next_path.kind is PathAction.SET:
|
||||||
|
cursor[path.accessor] = next_path.accessor
|
||||||
|
break
|
||||||
|
if cursor[path.accessor] is None:
|
||||||
|
cursor[path.accessor] = object_for(next_path.kind)
|
||||||
|
cursor = cursor[path.accessor]
|
||||||
|
elif path.kind is PathAction.APPEND:
|
||||||
|
type_check(index, path, list)
|
||||||
|
if next_path.kind is PathAction.SET:
|
||||||
|
cursor.append(next_path.accessor)
|
||||||
|
break
|
||||||
|
cursor.append(object_for(next_path.kind))
|
||||||
|
cursor = cursor[-1]
|
||||||
|
else:
|
||||||
|
assert_cant_happen()
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_with_dict(context):
|
||||||
|
if context is None:
|
||||||
|
return {}
|
||||||
|
elif isinstance(context, list):
|
||||||
|
return {
|
||||||
|
EMPTY_STRING: NestedJSONArray(context),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
assert isinstance(context, dict)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def unwrap_top_level_list_if_needed(data: dict):
|
||||||
|
"""
|
||||||
|
Propagate the top-level list, if that’s what we got.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if len(data) == 1:
|
||||||
|
key, value = list(data.items())[0]
|
||||||
|
if isinstance(value, NestedJSONArray):
|
||||||
|
assert key == EMPTY_STRING
|
||||||
|
return value
|
||||||
|
return data
|
193
httpie/cli/nested_json/parse.py
Normal file
193
httpie/cli/nested_json/parse.py
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
from typing import Iterator
|
||||||
|
|
||||||
|
from .errors import NestedJSONSyntaxError
|
||||||
|
from .tokens import (
|
||||||
|
EMPTY_STRING,
|
||||||
|
BACKSLASH,
|
||||||
|
TokenKind,
|
||||||
|
OPERATORS,
|
||||||
|
SPECIAL_CHARS,
|
||||||
|
LITERAL_TOKENS,
|
||||||
|
Token,
|
||||||
|
PathAction,
|
||||||
|
Path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'parse',
|
||||||
|
'assert_cant_happen',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def parse(source: str) -> Iterator[Path]:
|
||||||
|
"""
|
||||||
|
start: root_path path*
|
||||||
|
root_path: (literal | index_path | append_path)
|
||||||
|
literal: TEXT | NUMBER
|
||||||
|
|
||||||
|
path:
|
||||||
|
key_path
|
||||||
|
| index_path
|
||||||
|
| append_path
|
||||||
|
key_path: LEFT_BRACKET TEXT RIGHT_BRACKET
|
||||||
|
index_path: LEFT_BRACKET NUMBER RIGHT_BRACKET
|
||||||
|
append_path: LEFT_BRACKET RIGHT_BRACKET
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
tokens = list(tokenize(source))
|
||||||
|
cursor = 0
|
||||||
|
|
||||||
|
def can_advance():
|
||||||
|
return cursor < len(tokens)
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
def expect(*kinds):
|
||||||
|
nonlocal cursor
|
||||||
|
assert kinds
|
||||||
|
if can_advance():
|
||||||
|
token = tokens[cursor]
|
||||||
|
cursor += 1
|
||||||
|
if token.kind in kinds:
|
||||||
|
return token
|
||||||
|
elif tokens:
|
||||||
|
token = tokens[-1]._replace(
|
||||||
|
start=tokens[-1].end + 0,
|
||||||
|
end=tokens[-1].end + 1,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
token = None
|
||||||
|
if len(kinds) == 1:
|
||||||
|
suffix = kinds[0].to_name()
|
||||||
|
else:
|
||||||
|
suffix = ', '.join(kind.to_name() for kind in kinds[:-1])
|
||||||
|
suffix += ' or ' + kinds[-1].to_name()
|
||||||
|
message = f'Expecting {suffix}'
|
||||||
|
raise NestedJSONSyntaxError(source, token, message)
|
||||||
|
|
||||||
|
# noinspection PyShadowingNames
|
||||||
|
def parse_root():
|
||||||
|
tokens = []
|
||||||
|
if not can_advance():
|
||||||
|
return Path(
|
||||||
|
kind=PathAction.KEY,
|
||||||
|
accessor=EMPTY_STRING,
|
||||||
|
is_root=True
|
||||||
|
)
|
||||||
|
# (literal | index_path | append_path)?
|
||||||
|
token = expect(*LITERAL_TOKENS, TokenKind.LEFT_BRACKET)
|
||||||
|
tokens.append(token)
|
||||||
|
if token.kind in LITERAL_TOKENS:
|
||||||
|
action = PathAction.KEY
|
||||||
|
value = str(token.value)
|
||||||
|
elif token.kind is TokenKind.LEFT_BRACKET:
|
||||||
|
token = expect(TokenKind.NUMBER, TokenKind.RIGHT_BRACKET)
|
||||||
|
tokens.append(token)
|
||||||
|
if token.kind is TokenKind.NUMBER:
|
||||||
|
action = PathAction.INDEX
|
||||||
|
value = token.value
|
||||||
|
tokens.append(expect(TokenKind.RIGHT_BRACKET))
|
||||||
|
elif token.kind is TokenKind.RIGHT_BRACKET:
|
||||||
|
action = PathAction.APPEND
|
||||||
|
value = None
|
||||||
|
else:
|
||||||
|
assert_cant_happen()
|
||||||
|
else:
|
||||||
|
assert_cant_happen()
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
return Path(
|
||||||
|
kind=action,
|
||||||
|
accessor=value,
|
||||||
|
tokens=tokens,
|
||||||
|
is_root=True
|
||||||
|
)
|
||||||
|
|
||||||
|
yield parse_root()
|
||||||
|
|
||||||
|
# path*
|
||||||
|
while can_advance():
|
||||||
|
path_tokens = [expect(TokenKind.LEFT_BRACKET)]
|
||||||
|
token = expect(TokenKind.TEXT, TokenKind.NUMBER, TokenKind.RIGHT_BRACKET)
|
||||||
|
path_tokens.append(token)
|
||||||
|
if token.kind is TokenKind.RIGHT_BRACKET:
|
||||||
|
path = Path(PathAction.APPEND, tokens=path_tokens)
|
||||||
|
elif token.kind is TokenKind.TEXT:
|
||||||
|
path = Path(PathAction.KEY, token.value, tokens=path_tokens)
|
||||||
|
path_tokens.append(expect(TokenKind.RIGHT_BRACKET))
|
||||||
|
elif token.kind is TokenKind.NUMBER:
|
||||||
|
path = Path(PathAction.INDEX, token.value, tokens=path_tokens)
|
||||||
|
path_tokens.append(expect(TokenKind.RIGHT_BRACKET))
|
||||||
|
else:
|
||||||
|
assert_cant_happen()
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
yield path
|
||||||
|
|
||||||
|
|
||||||
|
def tokenize(source: str) -> Iterator[Token]:
|
||||||
|
cursor = 0
|
||||||
|
backslashes = 0
|
||||||
|
buffer = []
|
||||||
|
|
||||||
|
def send_buffer() -> Iterator[Token]:
|
||||||
|
nonlocal backslashes
|
||||||
|
if not buffer:
|
||||||
|
return None
|
||||||
|
|
||||||
|
value = ''.join(buffer)
|
||||||
|
kind = TokenKind.TEXT
|
||||||
|
if not backslashes:
|
||||||
|
for variation, kind in [
|
||||||
|
(int, TokenKind.NUMBER),
|
||||||
|
(check_escaped_int, TokenKind.TEXT),
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
value = variation(value)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
yield Token(
|
||||||
|
kind=kind,
|
||||||
|
value=value,
|
||||||
|
start=cursor - (len(buffer) + backslashes),
|
||||||
|
end=cursor,
|
||||||
|
)
|
||||||
|
buffer.clear()
|
||||||
|
backslashes = 0
|
||||||
|
|
||||||
|
def can_advance() -> bool:
|
||||||
|
return cursor < len(source)
|
||||||
|
|
||||||
|
while can_advance():
|
||||||
|
index = source[cursor]
|
||||||
|
if index in OPERATORS:
|
||||||
|
yield from send_buffer()
|
||||||
|
yield Token(OPERATORS[index], index, cursor, cursor + 1)
|
||||||
|
elif index == BACKSLASH and can_advance():
|
||||||
|
if source[cursor + 1] in SPECIAL_CHARS:
|
||||||
|
backslashes += 1
|
||||||
|
else:
|
||||||
|
buffer.append(index)
|
||||||
|
buffer.append(source[cursor + 1])
|
||||||
|
cursor += 1
|
||||||
|
else:
|
||||||
|
buffer.append(index)
|
||||||
|
cursor += 1
|
||||||
|
|
||||||
|
yield from send_buffer()
|
||||||
|
|
||||||
|
|
||||||
|
def check_escaped_int(value: str) -> str:
|
||||||
|
if not value.startswith(BACKSLASH):
|
||||||
|
raise ValueError('Not an escaped int')
|
||||||
|
try:
|
||||||
|
int(value[1:])
|
||||||
|
except ValueError as exc:
|
||||||
|
raise ValueError('Not an escaped int') from exc
|
||||||
|
else:
|
||||||
|
return value[1:]
|
||||||
|
|
||||||
|
|
||||||
|
def assert_cant_happen():
|
||||||
|
raise ValueError('Unexpected value')
|
80
httpie/cli/nested_json/tokens.py
Normal file
80
httpie/cli/nested_json/tokens.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
from enum import Enum, auto
|
||||||
|
from typing import NamedTuple, Union, Optional, List
|
||||||
|
|
||||||
|
EMPTY_STRING = ''
|
||||||
|
HIGHLIGHTER = '^'
|
||||||
|
OPEN_BRACKET = '['
|
||||||
|
CLOSE_BRACKET = ']'
|
||||||
|
BACKSLASH = '\\'
|
||||||
|
|
||||||
|
|
||||||
|
class TokenKind(Enum):
|
||||||
|
TEXT = auto()
|
||||||
|
NUMBER = auto()
|
||||||
|
LEFT_BRACKET = auto()
|
||||||
|
RIGHT_BRACKET = auto()
|
||||||
|
PSEUDO = auto() # Not a real token, use when representing location only.
|
||||||
|
|
||||||
|
def to_name(self) -> str:
|
||||||
|
for key, value in OPERATORS.items():
|
||||||
|
if value is self:
|
||||||
|
return repr(key)
|
||||||
|
else:
|
||||||
|
return 'a ' + self.name.lower()
|
||||||
|
|
||||||
|
|
||||||
|
OPERATORS = {
|
||||||
|
OPEN_BRACKET: TokenKind.LEFT_BRACKET,
|
||||||
|
CLOSE_BRACKET: TokenKind.RIGHT_BRACKET,
|
||||||
|
}
|
||||||
|
SPECIAL_CHARS = OPERATORS.keys() | {BACKSLASH}
|
||||||
|
LITERAL_TOKENS = [
|
||||||
|
TokenKind.TEXT,
|
||||||
|
TokenKind.NUMBER,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Token(NamedTuple):
|
||||||
|
kind: TokenKind
|
||||||
|
value: Union[str, int]
|
||||||
|
start: int
|
||||||
|
end: int
|
||||||
|
|
||||||
|
|
||||||
|
class PathAction(Enum):
|
||||||
|
KEY = auto()
|
||||||
|
INDEX = auto()
|
||||||
|
APPEND = auto()
|
||||||
|
# Pseudo action, used by the interpreter
|
||||||
|
SET = auto()
|
||||||
|
|
||||||
|
def to_string(self) -> str:
|
||||||
|
return self.name.lower()
|
||||||
|
|
||||||
|
|
||||||
|
class Path:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
kind: PathAction,
|
||||||
|
accessor: Optional[Union[str, int]] = None,
|
||||||
|
tokens: Optional[List[Token]] = None,
|
||||||
|
is_root: bool = False,
|
||||||
|
):
|
||||||
|
self.kind = kind
|
||||||
|
self.accessor = accessor
|
||||||
|
self.tokens = tokens or []
|
||||||
|
self.is_root = is_root
|
||||||
|
|
||||||
|
def reconstruct(self) -> str:
|
||||||
|
if self.kind is PathAction.KEY:
|
||||||
|
if self.is_root:
|
||||||
|
return str(self.accessor)
|
||||||
|
return OPEN_BRACKET + self.accessor + CLOSE_BRACKET
|
||||||
|
elif self.kind is PathAction.INDEX:
|
||||||
|
return OPEN_BRACKET + str(self.accessor) + CLOSE_BRACKET
|
||||||
|
elif self.kind is PathAction.APPEND:
|
||||||
|
return OPEN_BRACKET + CLOSE_BRACKET
|
||||||
|
|
||||||
|
|
||||||
|
class NestedJSONArray(list):
|
||||||
|
"""Denotes a top-level JSON array."""
|
249
httpie/cli/options.py
Normal file
249
httpie/cli/options.py
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
import argparse
|
||||||
|
import textwrap
|
||||||
|
import typing
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from enum import Enum, auto
|
||||||
|
from typing import Any, Optional, Dict, List, Tuple, Type, TypeVar
|
||||||
|
|
||||||
|
from httpie.cli.argparser import HTTPieArgumentParser
|
||||||
|
from httpie.cli.utils import Manual, LazyChoices
|
||||||
|
|
||||||
|
|
||||||
|
class Qualifiers(Enum):
|
||||||
|
OPTIONAL = auto()
|
||||||
|
ZERO_OR_MORE = auto()
|
||||||
|
ONE_OR_MORE = auto()
|
||||||
|
SUPPRESS = auto()
|
||||||
|
|
||||||
|
|
||||||
|
def map_qualifiers(
|
||||||
|
configuration: Dict[str, Any], qualifier_map: Dict[Qualifiers, Any]
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
key: qualifier_map[value] if isinstance(value, Qualifiers) else value
|
||||||
|
for key, value in configuration.items()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def drop_keys(
|
||||||
|
configuration: Dict[str, Any], key_blacklist: Tuple[str, ...]
|
||||||
|
):
|
||||||
|
return {
|
||||||
|
key: value
|
||||||
|
for key, value in configuration.items()
|
||||||
|
if key not in key_blacklist
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PARSER_SPEC_VERSION = '0.0.1a0'
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ParserSpec:
|
||||||
|
program: str
|
||||||
|
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:
|
||||||
|
self.description = textwrap.dedent(self.description)
|
||||||
|
if self.epilog:
|
||||||
|
self.epilog = textwrap.dedent(self.epilog)
|
||||||
|
for group in self.groups:
|
||||||
|
group.finalize()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def add_group(self, name: str, **kwargs) -> 'Group':
|
||||||
|
group = Group(name, **kwargs)
|
||||||
|
self.groups.append(group)
|
||||||
|
return group
|
||||||
|
|
||||||
|
def serialize(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
'name': self.program,
|
||||||
|
'description': self.description,
|
||||||
|
'groups': [group.serialize() for group in self.groups],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Group:
|
||||||
|
name: str
|
||||||
|
description: str = ''
|
||||||
|
is_mutually_exclusive: bool = False
|
||||||
|
arguments: List['Argument'] = field(default_factory=list)
|
||||||
|
|
||||||
|
def finalize(self) -> None:
|
||||||
|
if self.description:
|
||||||
|
self.description = textwrap.dedent(self.description)
|
||||||
|
|
||||||
|
def add_argument(self, *args, **kwargs):
|
||||||
|
argument = Argument(list(args), kwargs.copy())
|
||||||
|
argument.post_init()
|
||||||
|
self.arguments.append(argument)
|
||||||
|
return argument
|
||||||
|
|
||||||
|
def serialize(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
'name': self.name,
|
||||||
|
'description': self.description or None,
|
||||||
|
'is_mutually_exclusive': self.is_mutually_exclusive,
|
||||||
|
'args': [argument.serialize() for argument in self.arguments],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Argument(typing.NamedTuple):
|
||||||
|
aliases: List[str]
|
||||||
|
configuration: Dict[str, Any]
|
||||||
|
|
||||||
|
def post_init(self):
|
||||||
|
"""Run a bunch of post-init hooks."""
|
||||||
|
# If there is a short help, then create the longer version from it.
|
||||||
|
short_help = self.configuration.get('short_help')
|
||||||
|
if (
|
||||||
|
short_help
|
||||||
|
and 'help' not in self.configuration
|
||||||
|
and self.configuration.get('action') != 'lazy_choices'
|
||||||
|
):
|
||||||
|
self.configuration['help'] = f'\n{short_help}\n\n'
|
||||||
|
|
||||||
|
def serialize(self, *, isolation_mode: bool = False) -> Dict[str, Any]:
|
||||||
|
configuration = self.configuration.copy()
|
||||||
|
|
||||||
|
# Unpack the dynamically computed choices, since we
|
||||||
|
# will need to store the actual values somewhere.
|
||||||
|
action = configuration.pop('action', None)
|
||||||
|
short_help = configuration.pop('short_help', None)
|
||||||
|
nested_options = configuration.pop('nested_options', None)
|
||||||
|
|
||||||
|
if action == 'lazy_choices':
|
||||||
|
choices = LazyChoices(
|
||||||
|
self.aliases,
|
||||||
|
**{'dest': None, **configuration},
|
||||||
|
isolation_mode=isolation_mode
|
||||||
|
)
|
||||||
|
configuration['choices'] = list(choices.load())
|
||||||
|
configuration['help'] = choices.help
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
if self.aliases:
|
||||||
|
result['options'] = self.aliases.copy()
|
||||||
|
else:
|
||||||
|
result['options'] = [configuration['metavar']]
|
||||||
|
result['is_positional'] = True
|
||||||
|
|
||||||
|
qualifiers = JSON_QUALIFIER_TO_OPTIONS[configuration.get('nargs', Qualifiers.SUPPRESS)]
|
||||||
|
result.update(qualifiers)
|
||||||
|
|
||||||
|
description = configuration.get('help')
|
||||||
|
if description and description is not Qualifiers.SUPPRESS:
|
||||||
|
result['short_description'] = short_help
|
||||||
|
result['description'] = description
|
||||||
|
|
||||||
|
if nested_options:
|
||||||
|
result['nested_options'] = nested_options
|
||||||
|
|
||||||
|
python_type = configuration.get('type')
|
||||||
|
if python_type is not None:
|
||||||
|
if hasattr(python_type, '__name__'):
|
||||||
|
type_name = python_type.__name__
|
||||||
|
else:
|
||||||
|
type_name = type(python_type).__name__
|
||||||
|
|
||||||
|
result['python_type_name'] = type_name
|
||||||
|
|
||||||
|
result.update({
|
||||||
|
key: value
|
||||||
|
for key, value in configuration.items()
|
||||||
|
if key in JSON_DIRECT_MIRROR_OPTIONS
|
||||||
|
if value is not Qualifiers.SUPPRESS
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_positional(self):
|
||||||
|
return len(self.aliases) == 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_hidden(self):
|
||||||
|
return self.configuration.get('help') is Qualifiers.SUPPRESS
|
||||||
|
|
||||||
|
def __getattr__(self, attribute_name):
|
||||||
|
if attribute_name in self.configuration:
|
||||||
|
return self.configuration[attribute_name]
|
||||||
|
else:
|
||||||
|
raise AttributeError(attribute_name)
|
||||||
|
|
||||||
|
|
||||||
|
ParserType = TypeVar('ParserType', bound=Type[argparse.ArgumentParser])
|
||||||
|
|
||||||
|
ARGPARSE_QUALIFIER_MAP = {
|
||||||
|
Qualifiers.OPTIONAL: argparse.OPTIONAL,
|
||||||
|
Qualifiers.SUPPRESS: argparse.SUPPRESS,
|
||||||
|
Qualifiers.ZERO_OR_MORE: argparse.ZERO_OR_MORE,
|
||||||
|
Qualifiers.ONE_OR_MORE: argparse.ONE_OR_MORE
|
||||||
|
}
|
||||||
|
ARGPARSE_IGNORE_KEYS = ('short_help', 'nested_options')
|
||||||
|
|
||||||
|
|
||||||
|
def to_argparse(
|
||||||
|
abstract_options: ParserSpec,
|
||||||
|
parser_type: ParserType = HTTPieArgumentParser,
|
||||||
|
) -> ParserType:
|
||||||
|
concrete_parser = parser_type(
|
||||||
|
prog=abstract_options.program,
|
||||||
|
description=abstract_options.description,
|
||||||
|
epilog=abstract_options.epilog,
|
||||||
|
)
|
||||||
|
concrete_parser.spec = abstract_options
|
||||||
|
concrete_parser.register('action', 'lazy_choices', LazyChoices)
|
||||||
|
concrete_parser.register('action', 'manual', Manual)
|
||||||
|
|
||||||
|
for abstract_group in abstract_options.groups:
|
||||||
|
concrete_group = concrete_parser.add_argument_group(
|
||||||
|
title=abstract_group.name, description=abstract_group.description
|
||||||
|
)
|
||||||
|
if abstract_group.is_mutually_exclusive:
|
||||||
|
concrete_group = concrete_group.add_mutually_exclusive_group(required=False)
|
||||||
|
|
||||||
|
for abstract_argument in abstract_group.arguments:
|
||||||
|
concrete_group.add_argument(
|
||||||
|
*abstract_argument.aliases,
|
||||||
|
**drop_keys(map_qualifiers(
|
||||||
|
abstract_argument.configuration, ARGPARSE_QUALIFIER_MAP
|
||||||
|
), ARGPARSE_IGNORE_KEYS)
|
||||||
|
)
|
||||||
|
|
||||||
|
return concrete_parser
|
||||||
|
|
||||||
|
|
||||||
|
JSON_DIRECT_MIRROR_OPTIONS = (
|
||||||
|
'choices',
|
||||||
|
'metavar'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
JSON_QUALIFIER_TO_OPTIONS = {
|
||||||
|
Qualifiers.OPTIONAL: {'is_optional': True},
|
||||||
|
Qualifiers.ZERO_OR_MORE: {'is_optional': True, 'is_variadic': True},
|
||||||
|
Qualifiers.ONE_OR_MORE: {'is_optional': False, 'is_variadic': True},
|
||||||
|
Qualifiers.SUPPRESS: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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, **kwargs) -> ParserSpec:
|
||||||
|
"""Take an existing argparse parser, and create a spec from it."""
|
||||||
|
return ParserSpec(
|
||||||
|
program=parser.prog,
|
||||||
|
description=parser.description,
|
||||||
|
epilog=parser.epilog,
|
||||||
|
**kwargs
|
||||||
|
)
|
@ -18,7 +18,7 @@ from .dicts import (
|
|||||||
)
|
)
|
||||||
from .exceptions import ParseError
|
from .exceptions import ParseError
|
||||||
from .nested_json import interpret_nested_json
|
from .nested_json import interpret_nested_json
|
||||||
from ..utils import get_content_type, load_json_preserve_order_and_dupe_keys, split
|
from ..utils import get_content_type, load_json_preserve_order_and_dupe_keys, split_iterable
|
||||||
|
|
||||||
|
|
||||||
class RequestItems:
|
class RequestItems:
|
||||||
@ -78,25 +78,28 @@ class RequestItems:
|
|||||||
instance.data,
|
instance.data,
|
||||||
),
|
),
|
||||||
SEPARATOR_DATA_RAW_JSON: (
|
SEPARATOR_DATA_RAW_JSON: (
|
||||||
json_only(instance, process_data_raw_json_embed_arg),
|
convert_json_value_to_form_if_needed(
|
||||||
|
in_json_mode=instance.is_json,
|
||||||
|
processor=process_data_raw_json_embed_arg
|
||||||
|
),
|
||||||
instance.data,
|
instance.data,
|
||||||
),
|
),
|
||||||
SEPARATOR_DATA_EMBED_RAW_JSON_FILE: (
|
SEPARATOR_DATA_EMBED_RAW_JSON_FILE: (
|
||||||
json_only(instance, process_data_embed_raw_json_file_arg),
|
convert_json_value_to_form_if_needed(
|
||||||
|
in_json_mode=instance.is_json,
|
||||||
|
processor=process_data_embed_raw_json_file_arg,
|
||||||
|
),
|
||||||
instance.data,
|
instance.data,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
if instance.is_json:
|
if instance.is_json:
|
||||||
json_item_args, request_item_args = split(
|
json_item_args, request_item_args = split_iterable(
|
||||||
request_item_args,
|
iterable=request_item_args,
|
||||||
lambda arg: arg.sep in SEPARATOR_GROUP_NESTED_JSON_ITEMS
|
key=lambda arg: arg.sep in SEPARATOR_GROUP_NESTED_JSON_ITEMS
|
||||||
)
|
)
|
||||||
if json_item_args:
|
if json_item_args:
|
||||||
pairs = [
|
pairs = [(arg.key, rules[arg.sep][0](arg)) for arg in json_item_args]
|
||||||
(arg.key, rules[arg.sep][0](arg))
|
|
||||||
for arg in json_item_args
|
|
||||||
]
|
|
||||||
processor_func, target_dict = rules[SEPARATOR_GROUP_NESTED_JSON_ITEMS]
|
processor_func, target_dict = rules[SEPARATOR_GROUP_NESTED_JSON_ITEMS]
|
||||||
value = processor_func(pairs)
|
value = processor_func(pairs)
|
||||||
target_dict.update(value)
|
target_dict.update(value)
|
||||||
@ -159,6 +162,30 @@ def process_file_upload_arg(arg: KeyValueArg) -> Tuple[str, IO, str]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_json_value_to_form_if_needed(in_json_mode: bool, processor: Callable[[KeyValueArg], JSONType]) -> Callable[[], str]:
|
||||||
|
"""
|
||||||
|
We allow primitive values to be passed to forms via JSON key/value syntax.
|
||||||
|
|
||||||
|
But complex values lead to an error because there’s no clear way to serialize them.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if in_json_mode:
|
||||||
|
return processor
|
||||||
|
|
||||||
|
@functools.wraps(processor)
|
||||||
|
def wrapper(*args, **kwargs) -> str:
|
||||||
|
try:
|
||||||
|
output = processor(*args, **kwargs)
|
||||||
|
except ParseError:
|
||||||
|
output = None
|
||||||
|
if isinstance(output, (str, int, float)):
|
||||||
|
return str(output)
|
||||||
|
else:
|
||||||
|
raise ParseError('Cannot use complex JSON value types with --form/--multipart.')
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def process_data_item_arg(arg: KeyValueArg) -> str:
|
def process_data_item_arg(arg: KeyValueArg) -> str:
|
||||||
return arg.value
|
return arg.value
|
||||||
|
|
||||||
@ -167,29 +194,6 @@ def process_data_embed_file_contents_arg(arg: KeyValueArg) -> str:
|
|||||||
return load_text_file(arg)
|
return load_text_file(arg)
|
||||||
|
|
||||||
|
|
||||||
def json_only(items: RequestItems, func: Callable[[KeyValueArg], JSONType]) -> str:
|
|
||||||
if items.is_json:
|
|
||||||
return func
|
|
||||||
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapper(*args, **kwargs) -> str:
|
|
||||||
try:
|
|
||||||
ret = func(*args, **kwargs)
|
|
||||||
except ParseError:
|
|
||||||
ret = None
|
|
||||||
|
|
||||||
# If it is a basic type, then allow it
|
|
||||||
if isinstance(ret, (str, int, float)):
|
|
||||||
return str(ret)
|
|
||||||
else:
|
|
||||||
raise ParseError(
|
|
||||||
'Can\'t use complex JSON value types with '
|
|
||||||
'--form/--multipart.'
|
|
||||||
)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
def process_data_embed_raw_json_file_arg(arg: KeyValueArg) -> JSONType:
|
def process_data_embed_raw_json_file_arg(arg: KeyValueArg) -> JSONType:
|
||||||
contents = load_text_file(arg)
|
contents = load_text_file(arg)
|
||||||
value = load_json(arg, contents)
|
value = load_json(arg, contents)
|
||||||
|
@ -4,20 +4,43 @@ from typing import Any, Callable, Generic, Iterator, Iterable, Optional, TypeVar
|
|||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
class Manual(argparse.Action):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
option_strings,
|
||||||
|
dest=argparse.SUPPRESS,
|
||||||
|
default=argparse.SUPPRESS,
|
||||||
|
help=None
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
option_strings=option_strings,
|
||||||
|
dest=dest,
|
||||||
|
default=default,
|
||||||
|
nargs=0,
|
||||||
|
help=help
|
||||||
|
)
|
||||||
|
|
||||||
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
|
parser.print_manual()
|
||||||
|
parser.exit()
|
||||||
|
|
||||||
|
|
||||||
class LazyChoices(argparse.Action, Generic[T]):
|
class LazyChoices(argparse.Action, Generic[T]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*args,
|
*args,
|
||||||
getter: Callable[[], Iterable[T]],
|
getter: Callable[[], Iterable[T]],
|
||||||
help_formatter: Optional[Callable[[T], str]] = None,
|
help_formatter: Optional[Callable[[T, bool], str]] = None,
|
||||||
sort: bool = False,
|
sort: bool = False,
|
||||||
cache: bool = True,
|
cache: bool = True,
|
||||||
|
isolation_mode: bool = False,
|
||||||
**kwargs
|
**kwargs
|
||||||
) -> None:
|
) -> None:
|
||||||
self.getter = getter
|
self.getter = getter
|
||||||
self.help_formatter = help_formatter
|
self.help_formatter = help_formatter
|
||||||
self.sort = sort
|
self.sort = sort
|
||||||
self.cache = cache
|
self.cache = cache
|
||||||
|
self.isolation_mode = isolation_mode
|
||||||
self._help: Optional[str] = None
|
self._help: Optional[str] = None
|
||||||
self._obj: Optional[Iterable[T]] = None
|
self._obj: Optional[Iterable[T]] = None
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
@ -33,7 +56,10 @@ class LazyChoices(argparse.Action, Generic[T]):
|
|||||||
@property
|
@property
|
||||||
def help(self) -> str:
|
def help(self) -> str:
|
||||||
if self._help is None and self.help_formatter is not None:
|
if self._help is None and self.help_formatter is not None:
|
||||||
self._help = self.help_formatter(self.load())
|
self._help = self.help_formatter(
|
||||||
|
self.load(),
|
||||||
|
isolation_mode=self.isolation_mode
|
||||||
|
)
|
||||||
return self._help
|
return self._help
|
||||||
|
|
||||||
@help.setter
|
@help.setter
|
||||||
|
@ -3,21 +3,26 @@ import http.client
|
|||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from time import monotonic
|
||||||
from typing import Any, Dict, Callable, Iterable
|
from typing import Any, Dict, Callable, Iterable
|
||||||
from urllib.parse import urlparse, urlunparse
|
from urllib.parse import urlparse, urlunparse
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
# noinspection PyPackageRequirements
|
# noinspection PyPackageRequirements
|
||||||
import urllib3
|
import urllib3
|
||||||
|
from urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from .adapters import HTTPieHTTPAdapter
|
from .adapters import HTTPieHTTPAdapter
|
||||||
from .context import Environment
|
from .cli.constants import HTTP_OPTIONS
|
||||||
from .cli.dicts import HTTPHeadersDict
|
from .cli.dicts import HTTPHeadersDict
|
||||||
|
from .cli.nested_json import unwrap_top_level_list_if_needed
|
||||||
|
from .context import Environment
|
||||||
from .encoding import UTF8
|
from .encoding import UTF8
|
||||||
from .models import RequestsMessage
|
from .models import RequestsMessage
|
||||||
from .plugins.registry import plugin_manager
|
from .plugins.registry import plugin_manager
|
||||||
from .sessions import get_httpie_session
|
from .sessions import get_httpie_session
|
||||||
from .ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, HTTPieHTTPSAdapter
|
from .ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, HTTPieCertificate, HTTPieHTTPSAdapter
|
||||||
from .uploads import (
|
from .uploads import (
|
||||||
compress_request, prepare_request_body,
|
compress_request, prepare_request_body,
|
||||||
get_multipart_data_and_content_type,
|
get_multipart_data_and_content_type,
|
||||||
@ -32,6 +37,8 @@ JSON_CONTENT_TYPE = 'application/json'
|
|||||||
JSON_ACCEPT = f'{JSON_CONTENT_TYPE}, */*;q=0.5'
|
JSON_ACCEPT = f'{JSON_CONTENT_TYPE}, */*;q=0.5'
|
||||||
DEFAULT_UA = f'HTTPie/{__version__}'
|
DEFAULT_UA = f'HTTPie/{__version__}'
|
||||||
|
|
||||||
|
IGNORE_CONTENT_LENGTH_METHODS = frozenset([HTTP_OPTIONS])
|
||||||
|
|
||||||
|
|
||||||
def collect_messages(
|
def collect_messages(
|
||||||
env: Environment,
|
env: Environment,
|
||||||
@ -42,6 +49,7 @@ def collect_messages(
|
|||||||
httpie_session_headers = None
|
httpie_session_headers = None
|
||||||
if args.session or args.session_read_only:
|
if args.session or args.session_read_only:
|
||||||
httpie_session = get_httpie_session(
|
httpie_session = get_httpie_session(
|
||||||
|
env=env,
|
||||||
config_dir=env.config.directory,
|
config_dir=env.config.directory,
|
||||||
session_name=args.session or args.session_read_only,
|
session_name=args.session or args.session_read_only,
|
||||||
host=args.headers.get('Host'),
|
host=args.headers.get('Host'),
|
||||||
@ -82,7 +90,7 @@ def collect_messages(
|
|||||||
|
|
||||||
request = requests.Request(**request_kwargs)
|
request = requests.Request(**request_kwargs)
|
||||||
prepared_request = requests_session.prepare_request(request)
|
prepared_request = requests_session.prepare_request(request)
|
||||||
apply_missing_repeated_headers(prepared_request, request.headers)
|
transform_headers(request, prepared_request)
|
||||||
if args.path_as_is:
|
if args.path_as_is:
|
||||||
prepared_request.url = ensure_path_as_is(
|
prepared_request.url = ensure_path_as_is(
|
||||||
orig_url=args.url,
|
orig_url=args.url,
|
||||||
@ -108,7 +116,7 @@ def collect_messages(
|
|||||||
**send_kwargs_merged,
|
**send_kwargs_merged,
|
||||||
**send_kwargs,
|
**send_kwargs,
|
||||||
)
|
)
|
||||||
|
response._httpie_headers_parsed_at = monotonic()
|
||||||
expired_cookies += get_expired_cookies(
|
expired_cookies += get_expired_cookies(
|
||||||
response.headers.get('Set-Cookie', '')
|
response.headers.get('Set-Cookie', '')
|
||||||
)
|
)
|
||||||
@ -128,17 +136,14 @@ def collect_messages(
|
|||||||
if httpie_session:
|
if httpie_session:
|
||||||
if httpie_session.is_new() or not args.session_read_only:
|
if httpie_session.is_new() or not args.session_read_only:
|
||||||
httpie_session.cookies = requests_session.cookies
|
httpie_session.cookies = requests_session.cookies
|
||||||
httpie_session.remove_cookies(
|
httpie_session.remove_cookies(expired_cookies)
|
||||||
# TODO: take path & domain into account?
|
|
||||||
cookie['name'] for cookie in expired_cookies
|
|
||||||
)
|
|
||||||
httpie_session.save()
|
httpie_session.save()
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def max_headers(limit):
|
def max_headers(limit):
|
||||||
# <https://github.com/httpie/httpie/issues/802>
|
# <https://github.com/httpie/cli/issues/802>
|
||||||
# noinspection PyUnresolvedReferences
|
# noinspection PyUnresolvedReferences
|
||||||
orig = http.client._MAXHEADERS
|
orig = http.client._MAXHEADERS
|
||||||
http.client._MAXHEADERS = limit or float('Inf')
|
http.client._MAXHEADERS = limit or float('Inf')
|
||||||
@ -194,15 +199,40 @@ def finalize_headers(headers: HTTPHeadersDict) -> HTTPHeadersDict:
|
|||||||
# Also, requests raises `InvalidHeader` for leading spaces.
|
# Also, requests raises `InvalidHeader` for leading spaces.
|
||||||
value = value.strip()
|
value = value.strip()
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
# See <https://github.com/httpie/httpie/issues/212>
|
# See <https://github.com/httpie/cli/issues/212>
|
||||||
value = value.encode()
|
value = value.encode()
|
||||||
|
elif name.lower() in SKIPPABLE_HEADERS:
|
||||||
|
# Some headers get overwritten by urllib3 when set to `None`
|
||||||
|
# and should be replaced with the `SKIP_HEADER` constant.
|
||||||
|
value = SKIP_HEADER
|
||||||
final_headers.add(name, value)
|
final_headers.add(name, value)
|
||||||
return final_headers
|
return final_headers
|
||||||
|
|
||||||
|
|
||||||
|
def transform_headers(
|
||||||
|
request: requests.Request,
|
||||||
|
prepared_request: requests.PreparedRequest
|
||||||
|
) -> None:
|
||||||
|
"""Apply various transformations on top of the `prepared_requests`'s
|
||||||
|
headers to change the request prepreation behavior."""
|
||||||
|
|
||||||
|
# Remove 'Content-Length' when it is misplaced by requests.
|
||||||
|
if (
|
||||||
|
prepared_request.method in IGNORE_CONTENT_LENGTH_METHODS
|
||||||
|
and prepared_request.headers.get('Content-Length') == '0'
|
||||||
|
and request.headers.get('Content-Length') != '0'
|
||||||
|
):
|
||||||
|
prepared_request.headers.pop('Content-Length')
|
||||||
|
|
||||||
|
apply_missing_repeated_headers(
|
||||||
|
request.headers,
|
||||||
|
prepared_request
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def apply_missing_repeated_headers(
|
def apply_missing_repeated_headers(
|
||||||
prepared_request: requests.PreparedRequest,
|
original_headers: HTTPHeadersDict,
|
||||||
original_headers: HTTPHeadersDict
|
prepared_request: requests.PreparedRequest
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Update the given `prepared_request`'s headers with the original
|
"""Update the given `prepared_request`'s headers with the original
|
||||||
ones. This allows the requests to be prepared as usual, and then later
|
ones. This allows the requests to be prepared as usual, and then later
|
||||||
@ -260,7 +290,14 @@ def make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict:
|
|||||||
if args.cert:
|
if args.cert:
|
||||||
cert = args.cert
|
cert = args.cert
|
||||||
if args.cert_key:
|
if args.cert_key:
|
||||||
cert = cert, args.cert_key
|
# Having a client certificate key passphrase is not supported
|
||||||
|
# by requests. So we are using our own transportation structure
|
||||||
|
# which is compatible with their format (a tuple of minimum two
|
||||||
|
# items).
|
||||||
|
#
|
||||||
|
# See: https://github.com/psf/requests/issues/2519
|
||||||
|
cert = HTTPieCertificate(cert, args.cert_key, args.cert_key_pass.value)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'proxies': {p.key: p.value for p in args.proxy},
|
'proxies': {p.key: p.value for p in args.proxy},
|
||||||
'stream': True,
|
'stream': True,
|
||||||
@ -275,20 +312,13 @@ def make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def json_dict_to_request_body(data: Dict[str, Any]) -> str:
|
def json_dict_to_request_body(data: Dict[str, Any]) -> str:
|
||||||
# Propagate the top-level list if there is only one
|
data = unwrap_top_level_list_if_needed(data)
|
||||||
# item in the object, with an en empty key.
|
|
||||||
if len(data) == 1:
|
|
||||||
[(key, value)] = data.items()
|
|
||||||
if key == '' and isinstance(value, list):
|
|
||||||
data = value
|
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
data = json.dumps(data)
|
data = json.dumps(data)
|
||||||
else:
|
else:
|
||||||
# We need to set data to an empty string to prevent requests
|
# We need to set data to an empty string to prevent requests
|
||||||
# from assigning an empty list to `response.request.data`.
|
# from assigning an empty list to `response.request.data`.
|
||||||
data = ''
|
data = ''
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@ -351,7 +381,7 @@ def ensure_path_as_is(orig_url: str, prepped_url: str) -> str:
|
|||||||
untouched because other (welcome) processing on the URL might have
|
untouched because other (welcome) processing on the URL might have
|
||||||
taken place.
|
taken place.
|
||||||
|
|
||||||
<https://github.com/httpie/httpie/issues/895>
|
<https://github.com/httpie/cli/issues/895>
|
||||||
|
|
||||||
|
|
||||||
<https://ec.haxx.se/http/http-basics#path-as-is>
|
<https://ec.haxx.se/http/http-basics#path-as-is>
|
||||||
|
@ -1,9 +1,21 @@
|
|||||||
import sys
|
import sys
|
||||||
|
from ssl import SSLContext
|
||||||
from typing import Any, Optional, Iterable
|
from typing import Any, Optional, Iterable
|
||||||
|
|
||||||
|
from httpie.cookies import HTTPieCookiePolicy
|
||||||
|
from http import cookiejar # noqa
|
||||||
|
|
||||||
|
|
||||||
|
# Request does not carry the original policy attached to the
|
||||||
|
# cookie jar, so until it is resolved we change the global cookie
|
||||||
|
# policy. <https://github.com/psf/requests/issues/5449>
|
||||||
|
cookiejar.DefaultCookiePolicy = HTTPieCookiePolicy
|
||||||
|
|
||||||
is_windows = 'win32' in str(sys.platform).lower()
|
is_windows = 'win32' in str(sys.platform).lower()
|
||||||
|
is_frozen = getattr(sys, 'frozen', False)
|
||||||
|
|
||||||
|
MIN_SUPPORTED_PY_VERSION = (3, 7)
|
||||||
|
MAX_SUPPORTED_PY_VERSION = (3, 11)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
@ -54,7 +66,6 @@ except ImportError:
|
|||||||
res = instance.__dict__[self.name] = self.func(instance)
|
res = instance.__dict__[self.name] = self.func(instance)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
# importlib_metadata was a provisional module, so the APIs changed quite a few times
|
# importlib_metadata was a provisional module, so the APIs changed quite a few times
|
||||||
# between 3.8-3.10. It was also not included in the standard library until 3.8, so
|
# between 3.8-3.10. It was also not included in the standard library until 3.8, so
|
||||||
# we install the backport for <3.8.
|
# we install the backport for <3.8.
|
||||||
@ -88,3 +99,15 @@ def get_dist_name(entry_point: importlib_metadata.EntryPoint) -> Optional[str]:
|
|||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
return metadata.get('name')
|
return metadata.get('name')
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_default_certs_loaded(ssl_context: SSLContext) -> None:
|
||||||
|
"""
|
||||||
|
Workaround for a bug in Requests 2.32.3
|
||||||
|
|
||||||
|
See <https://github.com/httpie/cli/issues/1583>
|
||||||
|
|
||||||
|
"""
|
||||||
|
if hasattr(ssl_context, 'load_default_certs'):
|
||||||
|
if not ssl_context.get_ca_certs():
|
||||||
|
ssl_context.load_default_certs()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Union
|
from typing import Any, Dict, Union
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from .compat import is_windows
|
from .compat import is_windows
|
||||||
@ -62,6 +62,21 @@ class ConfigFileError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def read_raw_config(config_type: str, path: Path) -> Dict[str, Any]:
|
||||||
|
try:
|
||||||
|
with path.open(encoding=UTF8) as f:
|
||||||
|
try:
|
||||||
|
return json.load(f)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ConfigFileError(
|
||||||
|
f'invalid {config_type} file: {e} [{path}]'
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
except OSError as e:
|
||||||
|
raise ConfigFileError(f'cannot read {config_type} file: {e}')
|
||||||
|
|
||||||
|
|
||||||
class BaseConfigDict(dict):
|
class BaseConfigDict(dict):
|
||||||
name = None
|
name = None
|
||||||
helpurl = None
|
helpurl = None
|
||||||
@ -77,26 +92,25 @@ class BaseConfigDict(dict):
|
|||||||
def is_new(self) -> bool:
|
def is_new(self) -> bool:
|
||||||
return not self.path.exists()
|
return not self.path.exists()
|
||||||
|
|
||||||
|
def pre_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Hook for processing the incoming config data."""
|
||||||
|
return data
|
||||||
|
|
||||||
|
def post_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Hook for processing the outgoing config data."""
|
||||||
|
return data
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
config_type = type(self).__name__.lower()
|
config_type = type(self).__name__.lower()
|
||||||
try:
|
data = read_raw_config(config_type, self.path)
|
||||||
with self.path.open(encoding=UTF8) as f:
|
if data is not None:
|
||||||
try:
|
data = self.pre_process_data(data)
|
||||||
data = json.load(f)
|
self.update(data)
|
||||||
except ValueError as e:
|
|
||||||
raise ConfigFileError(
|
|
||||||
f'invalid {config_type} file: {e} [{self.path}]'
|
|
||||||
)
|
|
||||||
self.update(data)
|
|
||||||
except FileNotFoundError:
|
|
||||||
pass
|
|
||||||
except OSError as e:
|
|
||||||
raise ConfigFileError(f'cannot read {config_type} file: {e}')
|
|
||||||
|
|
||||||
def save(self):
|
def save(self, *, bump_version: bool = False):
|
||||||
self['__meta__'] = {
|
self.setdefault('__meta__', {})
|
||||||
'httpie': __version__
|
if bump_version or 'httpie' not in self['__meta__']:
|
||||||
}
|
self['__meta__']['httpie'] = __version__
|
||||||
if self.helpurl:
|
if self.helpurl:
|
||||||
self['__meta__']['help'] = self.helpurl
|
self['__meta__']['help'] = self.helpurl
|
||||||
|
|
||||||
@ -106,13 +120,19 @@ class BaseConfigDict(dict):
|
|||||||
self.ensure_directory()
|
self.ensure_directory()
|
||||||
|
|
||||||
json_string = json.dumps(
|
json_string = json.dumps(
|
||||||
obj=self,
|
obj=self.post_process_data(self),
|
||||||
indent=4,
|
indent=4,
|
||||||
sort_keys=True,
|
sort_keys=True,
|
||||||
ensure_ascii=True,
|
ensure_ascii=True,
|
||||||
)
|
)
|
||||||
self.path.write_text(json_string + '\n', encoding=UTF8)
|
self.path.write_text(json_string + '\n', encoding=UTF8)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self):
|
||||||
|
return self.get(
|
||||||
|
'__meta__', {}
|
||||||
|
).get('httpie', __version__)
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseConfigDict):
|
class Config(BaseConfigDict):
|
||||||
FILENAME = 'config.json'
|
FILENAME = 'config.json'
|
||||||
@ -129,6 +149,24 @@ class Config(BaseConfigDict):
|
|||||||
def default_options(self) -> list:
|
def default_options(self) -> list:
|
||||||
return self['default_options']
|
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
|
@property
|
||||||
def plugins_dir(self) -> Path:
|
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')
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import warnings
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterator, IO, Optional
|
from typing import Iterator, IO, Optional, TYPE_CHECKING
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -10,11 +13,34 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
curses = None # Compiled w/o curses
|
curses = None # Compiled w/o curses
|
||||||
|
|
||||||
from .compat import is_windows
|
from .compat import is_windows, cached_property
|
||||||
from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
|
from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
|
||||||
from .encoding import UTF8
|
from .encoding import UTF8
|
||||||
|
|
||||||
from .utils import repr_dict
|
from .utils import repr_dict
|
||||||
|
from .output.ui.palette import GenericColor
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from rich.console import Console
|
||||||
|
|
||||||
|
|
||||||
|
class LogLevel(str, Enum):
|
||||||
|
INFO = 'info'
|
||||||
|
WARNING = 'warning'
|
||||||
|
ERROR = 'error'
|
||||||
|
|
||||||
|
|
||||||
|
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.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Environment:
|
class Environment:
|
||||||
@ -27,6 +53,7 @@ class Environment:
|
|||||||
is used by the test suite to simulate various scenarios.
|
is used by the test suite to simulate various scenarios.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
args = argparse.Namespace()
|
||||||
is_windows: bool = is_windows
|
is_windows: bool = is_windows
|
||||||
config_dir: Path = DEFAULT_CONFIG_DIR
|
config_dir: Path = DEFAULT_CONFIG_DIR
|
||||||
stdin: Optional[IO] = sys.stdin # `None` when closed fd (#791)
|
stdin: Optional[IO] = sys.stdin # `None` when closed fd (#791)
|
||||||
@ -39,6 +66,10 @@ class Environment:
|
|||||||
stderr_isatty: bool = stderr.isatty()
|
stderr_isatty: bool = stderr.isatty()
|
||||||
colors = 256
|
colors = 256
|
||||||
program_name: str = 'http'
|
program_name: str = 'http'
|
||||||
|
|
||||||
|
# Whether to show progress bars / status spinners etc.
|
||||||
|
show_displays: bool = True
|
||||||
|
|
||||||
if not is_windows:
|
if not is_windows:
|
||||||
if curses:
|
if curses:
|
||||||
try:
|
try:
|
||||||
@ -87,6 +118,8 @@ class Environment:
|
|||||||
self.stdout_encoding = getattr(
|
self.stdout_encoding = getattr(
|
||||||
actual_stdout, 'encoding', None) or UTF8
|
actual_stdout, 'encoding', None) or UTF8
|
||||||
|
|
||||||
|
self.quiet = kwargs.pop('quiet', 0)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
defaults = dict(type(self).__dict__)
|
defaults = dict(type(self).__dict__)
|
||||||
actual = dict(defaults)
|
actual = dict(defaults)
|
||||||
@ -112,7 +145,7 @@ class Environment:
|
|||||||
try:
|
try:
|
||||||
config.load()
|
config.load()
|
||||||
except ConfigFileError as e:
|
except ConfigFileError as e:
|
||||||
self.log_error(e, level='warning')
|
self.log_error(e, level=LogLevel.WARNING)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -134,6 +167,51 @@ class Environment:
|
|||||||
self.stdout = original_stdout
|
self.stdout = original_stdout
|
||||||
self.stderr = original_stderr
|
self.stderr = original_stderr
|
||||||
|
|
||||||
def log_error(self, msg, level='error'):
|
def log_error(self, msg: str, level: LogLevel = LogLevel.ERROR) -> None:
|
||||||
assert level in ['error', 'warning']
|
if self.stdout_isatty and self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[level]:
|
||||||
self._orig_stderr.write(f'\n{self.program_name}: {level}: {msg}\n\n')
|
stderr = self.stderr # Not directly /dev/null, since stderr might be mocked
|
||||||
|
else:
|
||||||
|
stderr = self._orig_stderr
|
||||||
|
rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty())
|
||||||
|
rich_console.print(
|
||||||
|
f'\n{self.program_name}: {level.value}: {msg}\n\n',
|
||||||
|
style=LOG_LEVEL_COLORS[level],
|
||||||
|
markup=False,
|
||||||
|
highlight=False,
|
||||||
|
soft_wrap=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def apply_warnings_filter(self) -> None:
|
||||||
|
if self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[LogLevel.WARNING]:
|
||||||
|
warnings.simplefilter("ignore")
|
||||||
|
|
||||||
|
def _make_rich_console(
|
||||||
|
self,
|
||||||
|
file: IO[str],
|
||||||
|
force_terminal: bool
|
||||||
|
) -> 'Console':
|
||||||
|
from rich.console import Console
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
# Rich recommends separating the actual console (stdout) from
|
||||||
|
# the error (stderr) console for better isolation between parts.
|
||||||
|
# https://rich.readthedocs.io/en/stable/console.html#error-console
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def rich_console(self):
|
||||||
|
return self._make_rich_console(self.stdout, self.stdout_isatty)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def rich_error_console(self):
|
||||||
|
return self._make_rich_console(self.stderr, self.stderr_isatty)
|
||||||
|
25
httpie/cookies.py
Normal file
25
httpie/cookies.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from http import cookiejar
|
||||||
|
|
||||||
|
|
||||||
|
_LOCALHOST = 'localhost'
|
||||||
|
_LOCALHOST_SUFFIX = '.localhost'
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPieCookiePolicy(cookiejar.DefaultCookiePolicy):
|
||||||
|
def return_ok_secure(self, cookie, request):
|
||||||
|
"""Check whether the given cookie is sent to a secure host."""
|
||||||
|
|
||||||
|
is_secure_protocol = super().return_ok_secure(cookie, request)
|
||||||
|
if is_secure_protocol:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# The original implementation of this method only takes secure protocols
|
||||||
|
# (e.g., https) into account, but the latest developments in modern browsers
|
||||||
|
# (chrome, firefox) assume 'localhost' is also a secure location. So we
|
||||||
|
# override it with our own strategy.
|
||||||
|
return self._is_local_host(cookiejar.request_host(request))
|
||||||
|
|
||||||
|
def _is_local_host(self, hostname):
|
||||||
|
# Implements the static localhost detection algorithm in firefox.
|
||||||
|
# <https://searchfox.org/mozilla-central/rev/d4d7611ee4dd0003b492b865bc5988a4e6afc985/netwerk/dns/DNS.cpp#205-218>
|
||||||
|
return hostname == _LOCALHOST or hostname.endswith(_LOCALHOST_SUFFIX)
|
@ -11,18 +11,21 @@ from requests import __version__ as requests_version
|
|||||||
|
|
||||||
from . import __version__ as httpie_version
|
from . import __version__ as httpie_version
|
||||||
from .cli.constants import OUT_REQ_BODY
|
from .cli.constants import OUT_REQ_BODY
|
||||||
from .cli.nested_json import HTTPieSyntaxError
|
from .cli.nested_json import NestedJSONSyntaxError
|
||||||
from .client import collect_messages
|
from .client import collect_messages
|
||||||
from .context import Environment
|
from .context import Environment, LogLevel
|
||||||
from .downloads import Downloader
|
from .downloads import Downloader
|
||||||
from .models import (
|
from .models import (
|
||||||
RequestsMessageKind,
|
RequestsMessageKind,
|
||||||
OutputOptions,
|
OutputOptions
|
||||||
)
|
)
|
||||||
from .output.writer import write_message, write_stream, MESSAGE_SEPARATOR_BYTES
|
from .output.models import ProcessingOptions
|
||||||
|
from .output.writer import write_message, write_stream, write_raw_data, MESSAGE_SEPARATOR_BYTES
|
||||||
from .plugins.registry import plugin_manager
|
from .plugins.registry import plugin_manager
|
||||||
from .status import ExitStatus, http_status_to_exit_status
|
from .status import ExitStatus, http_status_to_exit_status
|
||||||
from .utils import unwrap_context
|
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
|
# noinspection PyDefaultArgument
|
||||||
@ -30,14 +33,19 @@ def raw_main(
|
|||||||
parser: argparse.ArgumentParser,
|
parser: argparse.ArgumentParser,
|
||||||
main_program: Callable[[argparse.Namespace, Environment], ExitStatus],
|
main_program: Callable[[argparse.Namespace, Environment], ExitStatus],
|
||||||
args: List[Union[str, bytes]] = sys.argv,
|
args: List[Union[str, bytes]] = sys.argv,
|
||||||
env: Environment = Environment()
|
env: Environment = Environment(),
|
||||||
|
use_default_options: bool = True,
|
||||||
) -> ExitStatus:
|
) -> ExitStatus:
|
||||||
program_name, *args = args
|
program_name, *args = args
|
||||||
env.program_name = os.path.basename(program_name)
|
env.program_name = os.path.basename(program_name)
|
||||||
args = decode_raw_args(args, env.stdin_encoding)
|
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)
|
plugin_manager.load_installed_plugins(env.config.plugins_dir)
|
||||||
|
|
||||||
if env.config.default_options:
|
if use_default_options and env.config.default_options:
|
||||||
args = env.config.default_options + args
|
args = env.config.default_options + args
|
||||||
|
|
||||||
include_debug_info = '--debug' in args
|
include_debug_info = '--debug' in args
|
||||||
@ -70,7 +78,7 @@ def raw_main(
|
|||||||
args=args,
|
args=args,
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
except HTTPieSyntaxError as exc:
|
except NestedJSONSyntaxError as exc:
|
||||||
env.stderr.write(str(exc) + "\n")
|
env.stderr.write(str(exc) + "\n")
|
||||||
if include_traceback:
|
if include_traceback:
|
||||||
raise
|
raise
|
||||||
@ -87,6 +95,7 @@ def raw_main(
|
|||||||
raise
|
raise
|
||||||
exit_status = ExitStatus.ERROR
|
exit_status = ExitStatus.ERROR
|
||||||
else:
|
else:
|
||||||
|
check_updates(env)
|
||||||
try:
|
try:
|
||||||
exit_status = main_program(
|
exit_status = main_program(
|
||||||
args=parsed_args,
|
args=parsed_args,
|
||||||
@ -168,6 +177,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
|
|||||||
downloader = None
|
downloader = None
|
||||||
initial_request: Optional[requests.PreparedRequest] = None
|
initial_request: Optional[requests.PreparedRequest] = None
|
||||||
final_response: Optional[requests.Response] = None
|
final_response: Optional[requests.Response] = None
|
||||||
|
processing_options = ProcessingOptions.from_raw_args(args)
|
||||||
|
|
||||||
def separate():
|
def separate():
|
||||||
getattr(env.stdout, 'buffer', env.stdout).write(MESSAGE_SEPARATOR_BYTES)
|
getattr(env.stdout, 'buffer', env.stdout).write(MESSAGE_SEPARATOR_BYTES)
|
||||||
@ -182,17 +192,17 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
|
|||||||
and chunk
|
and chunk
|
||||||
)
|
)
|
||||||
if should_pipe_to_stdout:
|
if should_pipe_to_stdout:
|
||||||
msg = requests.PreparedRequest()
|
return write_raw_data(
|
||||||
msg.is_body_upload_chunk = True
|
env,
|
||||||
msg.body = chunk
|
chunk,
|
||||||
msg.headers = initial_request.headers
|
processing_options=processing_options,
|
||||||
msg_output_options = OutputOptions.from_message(msg, body=True, headers=False)
|
headers=initial_request.headers
|
||||||
write_message(requests_message=msg, env=env, args=args, output_options=msg_output_options)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if args.download:
|
if args.download:
|
||||||
args.follow = True # --download implies --follow.
|
args.follow = True # --download implies --follow.
|
||||||
downloader = Downloader(output_file=args.output_file, progress_file=env.stderr, resume=args.download_resume)
|
downloader = Downloader(env, output_file=args.output_file, resume=args.download_resume)
|
||||||
downloader.pre_request(args.headers)
|
downloader.pre_request(args.headers)
|
||||||
messages = collect_messages(env, args=args,
|
messages = collect_messages(env, args=args,
|
||||||
request_body_read_callback=request_body_read_callback)
|
request_body_read_callback=request_body_read_callback)
|
||||||
@ -220,10 +230,15 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
|
|||||||
if args.check_status or downloader:
|
if args.check_status or downloader:
|
||||||
exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)
|
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):
|
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='warning')
|
env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level=LogLevel.WARNING)
|
||||||
write_message(requests_message=message, env=env, args=args, output_options=output_options._replace(
|
write_message(
|
||||||
body=do_write_body
|
requests_message=message,
|
||||||
))
|
env=env,
|
||||||
|
output_options=output_options._replace(
|
||||||
|
body=do_write_body
|
||||||
|
),
|
||||||
|
processing_options=processing_options
|
||||||
|
)
|
||||||
prev_with_body = output_options.body
|
prev_with_body = output_options.body
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
|
@ -5,10 +5,8 @@ Download mode implementation.
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
from mailbox import Message
|
from mailbox import Message
|
||||||
from time import sleep, monotonic
|
from time import monotonic
|
||||||
from typing import IO, Optional, Tuple
|
from typing import IO, Optional, Tuple
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
@ -16,22 +14,11 @@ import requests
|
|||||||
|
|
||||||
from .models import HTTPResponse, OutputOptions
|
from .models import HTTPResponse, OutputOptions
|
||||||
from .output.streams import RawStream
|
from .output.streams import RawStream
|
||||||
from .utils import humanize_bytes
|
from .context import Environment
|
||||||
|
|
||||||
|
|
||||||
PARTIAL_CONTENT = 206
|
PARTIAL_CONTENT = 206
|
||||||
|
|
||||||
CLEAR_LINE = '\r\033[K'
|
|
||||||
PROGRESS = (
|
|
||||||
'{percentage: 6.2f} %'
|
|
||||||
' {downloaded: >10}'
|
|
||||||
' {speed: >10}/s'
|
|
||||||
' {eta: >8} ETA'
|
|
||||||
)
|
|
||||||
PROGRESS_NO_CONTENT_LENGTH = '{downloaded: >10} {speed: >10}/s'
|
|
||||||
SUMMARY = 'Done. {downloaded} in {time:0.5f}s ({speed}/s)\n'
|
|
||||||
SPINNER = '|/-\\'
|
|
||||||
|
|
||||||
|
|
||||||
class ContentRangeError(ValueError):
|
class ContentRangeError(ValueError):
|
||||||
pass
|
pass
|
||||||
@ -176,9 +163,9 @@ class Downloader:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
env: Environment,
|
||||||
output_file: IO = None,
|
output_file: IO = None,
|
||||||
resume: bool = False,
|
resume: bool = False
|
||||||
progress_file: IO = sys.stderr
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
:param resume: Should the download resume if partial download
|
:param resume: Should the download resume if partial download
|
||||||
@ -191,14 +178,10 @@ class Downloader:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
self.finished = False
|
self.finished = False
|
||||||
self.status = DownloadStatus()
|
self.status = DownloadStatus(env=env)
|
||||||
self._output_file = output_file
|
self._output_file = output_file
|
||||||
self._resume = resume
|
self._resume = resume
|
||||||
self._resumed_from = 0
|
self._resumed_from = 0
|
||||||
self._progress_reporter = ProgressReporterThread(
|
|
||||||
status=self.status,
|
|
||||||
output=progress_file
|
|
||||||
)
|
|
||||||
|
|
||||||
def pre_request(self, request_headers: dict):
|
def pre_request(self, request_headers: dict):
|
||||||
"""Called just before the HTTP request is sent.
|
"""Called just before the HTTP request is sent.
|
||||||
@ -234,7 +217,7 @@ class Downloader:
|
|||||||
assert not self.status.time_started
|
assert not self.status.time_started
|
||||||
|
|
||||||
# FIXME: some servers still might sent Content-Encoding: gzip
|
# FIXME: some servers still might sent Content-Encoding: gzip
|
||||||
# <https://github.com/httpie/httpie/issues/423>
|
# <https://github.com/httpie/cli/issues/423>
|
||||||
try:
|
try:
|
||||||
total_size = int(final_response.headers['Content-Length'])
|
total_size = int(final_response.headers['Content-Length'])
|
||||||
except (KeyError, ValueError, TypeError):
|
except (KeyError, ValueError, TypeError):
|
||||||
@ -261,11 +244,6 @@ class Downloader:
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass # stdout
|
pass # stdout
|
||||||
|
|
||||||
self.status.started(
|
|
||||||
resumed_from=self._resumed_from,
|
|
||||||
total_size=total_size
|
|
||||||
)
|
|
||||||
|
|
||||||
output_options = OutputOptions.from_message(final_response, headers=False, body=True)
|
output_options = OutputOptions.from_message(final_response, headers=False, body=True)
|
||||||
stream = RawStream(
|
stream = RawStream(
|
||||||
msg=HTTPResponse(final_response),
|
msg=HTTPResponse(final_response),
|
||||||
@ -273,11 +251,11 @@ class Downloader:
|
|||||||
on_body_chunk_downloaded=self.chunk_downloaded,
|
on_body_chunk_downloaded=self.chunk_downloaded,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._progress_reporter.output.write(
|
self.status.started(
|
||||||
f'Downloading {humanize_bytes(total_size) + " " if total_size is not None else ""}'
|
output_file=self._output_file,
|
||||||
f'to "{self._output_file.name}"\n'
|
resumed_from=self._resumed_from,
|
||||||
|
total_size=total_size
|
||||||
)
|
)
|
||||||
self._progress_reporter.start()
|
|
||||||
|
|
||||||
return stream, self._output_file
|
return stream, self._output_file
|
||||||
|
|
||||||
@ -287,7 +265,7 @@ class Downloader:
|
|||||||
self.status.finished()
|
self.status.finished()
|
||||||
|
|
||||||
def failed(self):
|
def failed(self):
|
||||||
self._progress_reporter.stop()
|
self.status.terminate()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def interrupted(self) -> bool:
|
def interrupted(self) -> bool:
|
||||||
@ -329,127 +307,71 @@ class Downloader:
|
|||||||
class DownloadStatus:
|
class DownloadStatus:
|
||||||
"""Holds details about the download status."""
|
"""Holds details about the download status."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, env):
|
||||||
|
self.env = env
|
||||||
self.downloaded = 0
|
self.downloaded = 0
|
||||||
self.total_size = None
|
self.total_size = None
|
||||||
self.resumed_from = 0
|
self.resumed_from = 0
|
||||||
self.time_started = None
|
self.time_started = None
|
||||||
self.time_finished = None
|
self.time_finished = None
|
||||||
|
|
||||||
def started(self, resumed_from=0, total_size=None):
|
def started(self, output_file, resumed_from=0, total_size=None):
|
||||||
assert self.time_started is None
|
assert self.time_started is None
|
||||||
self.total_size = total_size
|
self.total_size = total_size
|
||||||
self.downloaded = self.resumed_from = resumed_from
|
self.downloaded = self.resumed_from = resumed_from
|
||||||
self.time_started = monotonic()
|
self.time_started = monotonic()
|
||||||
|
self.start_display(output_file=output_file)
|
||||||
|
|
||||||
|
def start_display(self, output_file):
|
||||||
|
from httpie.output.ui.rich_progress import (
|
||||||
|
DummyDisplay,
|
||||||
|
StatusDisplay,
|
||||||
|
ProgressDisplay
|
||||||
|
)
|
||||||
|
|
||||||
|
message = f'Downloading to {output_file.name}'
|
||||||
|
if self.env.show_displays:
|
||||||
|
if self.total_size is None:
|
||||||
|
# Rich does not support progress bars without a total
|
||||||
|
# size given. Instead we use status objects.
|
||||||
|
self.display = StatusDisplay(self.env)
|
||||||
|
else:
|
||||||
|
self.display = ProgressDisplay(self.env)
|
||||||
|
else:
|
||||||
|
self.display = DummyDisplay(self.env)
|
||||||
|
|
||||||
|
self.display.start(
|
||||||
|
total=self.total_size,
|
||||||
|
at=self.downloaded,
|
||||||
|
description=message
|
||||||
|
)
|
||||||
|
|
||||||
def chunk_downloaded(self, size):
|
def chunk_downloaded(self, size):
|
||||||
assert self.time_finished is None
|
assert self.time_finished is None
|
||||||
self.downloaded += size
|
self.downloaded += size
|
||||||
|
self.display.update(size)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_finished(self):
|
def has_finished(self):
|
||||||
return self.time_finished is not None
|
return self.time_finished is not None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def time_spent(self):
|
||||||
|
if (
|
||||||
|
self.time_started is not None
|
||||||
|
and self.time_finished is not None
|
||||||
|
):
|
||||||
|
return self.time_finished - self.time_started
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def finished(self):
|
def finished(self):
|
||||||
assert self.time_started is not None
|
assert self.time_started is not None
|
||||||
assert self.time_finished is None
|
assert self.time_finished is None
|
||||||
self.time_finished = monotonic()
|
self.time_finished = monotonic()
|
||||||
|
if hasattr(self, 'display'):
|
||||||
|
self.display.stop(self.time_spent)
|
||||||
|
|
||||||
|
def terminate(self):
|
||||||
class ProgressReporterThread(threading.Thread):
|
if hasattr(self, 'display'):
|
||||||
"""
|
self.display.stop(self.time_spent)
|
||||||
Reports download progress based on its status.
|
|
||||||
|
|
||||||
Uses threading to periodically update the status (speed, ETA, etc.).
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
status: DownloadStatus,
|
|
||||||
output: IO,
|
|
||||||
tick=.1,
|
|
||||||
update_interval=1
|
|
||||||
):
|
|
||||||
super().__init__()
|
|
||||||
self.status = status
|
|
||||||
self.output = output
|
|
||||||
self._tick = tick
|
|
||||||
self._update_interval = update_interval
|
|
||||||
self._spinner_pos = 0
|
|
||||||
self._status_line = ''
|
|
||||||
self._prev_bytes = 0
|
|
||||||
self._prev_time = monotonic()
|
|
||||||
self._should_stop = threading.Event()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""Stop reporting on next tick."""
|
|
||||||
self._should_stop.set()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
while not self._should_stop.is_set():
|
|
||||||
if self.status.has_finished:
|
|
||||||
self.sum_up()
|
|
||||||
break
|
|
||||||
|
|
||||||
self.report_speed()
|
|
||||||
sleep(self._tick)
|
|
||||||
|
|
||||||
def report_speed(self):
|
|
||||||
now = monotonic()
|
|
||||||
if now - self._prev_time >= self._update_interval:
|
|
||||||
downloaded = self.status.downloaded
|
|
||||||
speed = ((downloaded - self._prev_bytes)
|
|
||||||
/ (now - self._prev_time))
|
|
||||||
|
|
||||||
if not self.status.total_size:
|
|
||||||
self._status_line = PROGRESS_NO_CONTENT_LENGTH.format(
|
|
||||||
downloaded=humanize_bytes(downloaded),
|
|
||||||
speed=humanize_bytes(speed),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
percentage = (downloaded / self.status.total_size * 100
|
|
||||||
if self.status.total_size
|
|
||||||
else 0)
|
|
||||||
|
|
||||||
if not speed:
|
|
||||||
eta = '-:--:--'
|
|
||||||
else:
|
|
||||||
s = int((self.status.total_size - downloaded) / speed)
|
|
||||||
h, s = divmod(s, 60 * 60)
|
|
||||||
m, s = divmod(s, 60)
|
|
||||||
eta = f'{h}:{m:0>2}:{s:0>2}'
|
|
||||||
|
|
||||||
self._status_line = PROGRESS.format(
|
|
||||||
percentage=percentage,
|
|
||||||
downloaded=humanize_bytes(downloaded),
|
|
||||||
speed=humanize_bytes(speed),
|
|
||||||
eta=eta,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._prev_time = now
|
|
||||||
self._prev_bytes = downloaded
|
|
||||||
|
|
||||||
self.output.write(
|
|
||||||
f'{CLEAR_LINE} {SPINNER[self._spinner_pos]} {self._status_line}'
|
|
||||||
)
|
|
||||||
self.output.flush()
|
|
||||||
|
|
||||||
self._spinner_pos = (self._spinner_pos + 1) % len(SPINNER)
|
|
||||||
|
|
||||||
def sum_up(self):
|
|
||||||
actually_downloaded = (
|
|
||||||
self.status.downloaded - self.status.resumed_from)
|
|
||||||
time_taken = self.status.time_finished - self.status.time_started
|
|
||||||
speed = actually_downloaded / time_taken if time_taken else actually_downloaded
|
|
||||||
|
|
||||||
self.output.write(CLEAR_LINE)
|
|
||||||
|
|
||||||
self.output.write(SUMMARY.format(
|
|
||||||
downloaded=humanize_bytes(actually_downloaded),
|
|
||||||
total=(self.status.total_size
|
|
||||||
and humanize_bytes(self.status.total_size)),
|
|
||||||
speed=humanize_bytes(speed),
|
|
||||||
time=time_taken,
|
|
||||||
))
|
|
||||||
self.output.flush()
|
|
||||||
|
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)
|
0
httpie/legacy/__init__.py
Normal file
0
httpie/legacy/__init__.py
Normal file
100
httpie/legacy/v3_1_0_session_cookie_format.py
Normal file
100
httpie/legacy/v3_1_0_session_cookie_format.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import argparse
|
||||||
|
from typing import Any, Type, List, Dict, TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from httpie.sessions import Session
|
||||||
|
|
||||||
|
|
||||||
|
INSECURE_COOKIE_JAR_WARNING = '''\
|
||||||
|
Outdated layout detected for the current session. Please consider updating it,
|
||||||
|
in order to not get affected by potential security problems.
|
||||||
|
|
||||||
|
For fixing the current session:
|
||||||
|
|
||||||
|
With binding all cookies to the current host (secure):
|
||||||
|
$ httpie cli sessions upgrade --bind-cookies {hostname} {session_id}
|
||||||
|
|
||||||
|
Without binding cookies (leaving them as is) (insecure):
|
||||||
|
$ httpie cli sessions upgrade {hostname} {session_id}
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
INSECURE_COOKIE_JAR_WARNING_FOR_NAMED_SESSIONS = '''\
|
||||||
|
|
||||||
|
For fixing all named sessions:
|
||||||
|
|
||||||
|
With binding all cookies to the current host (secure):
|
||||||
|
$ httpie cli sessions upgrade-all --bind-cookies
|
||||||
|
|
||||||
|
Without binding cookies (leaving them as is) (insecure):
|
||||||
|
$ httpie cli sessions upgrade-all
|
||||||
|
'''
|
||||||
|
|
||||||
|
INSECURE_COOKIE_SECURITY_LINK = '\nSee https://pie.co/docs/security for more information.'
|
||||||
|
|
||||||
|
|
||||||
|
def pre_process(session: 'Session', cookies: Any) -> List[Dict[str, Any]]:
|
||||||
|
"""Load the given cookies to the cookie jar while maintaining
|
||||||
|
support for the old cookie layout."""
|
||||||
|
|
||||||
|
is_old_style = isinstance(cookies, dict)
|
||||||
|
if is_old_style:
|
||||||
|
normalized_cookies = [
|
||||||
|
{
|
||||||
|
'name': key,
|
||||||
|
**value
|
||||||
|
}
|
||||||
|
for key, value in cookies.items()
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
normalized_cookies = cookies
|
||||||
|
|
||||||
|
should_issue_warning = is_old_style and any(
|
||||||
|
cookie.get('domain', '') == ''
|
||||||
|
for cookie in normalized_cookies
|
||||||
|
)
|
||||||
|
|
||||||
|
if should_issue_warning:
|
||||||
|
warning = INSECURE_COOKIE_JAR_WARNING.format(hostname=session.bound_host, session_id=session.session_id)
|
||||||
|
if not session.is_anonymous:
|
||||||
|
warning += INSECURE_COOKIE_JAR_WARNING_FOR_NAMED_SESSIONS
|
||||||
|
warning += INSECURE_COOKIE_SECURITY_LINK
|
||||||
|
session.warn_legacy_usage(warning)
|
||||||
|
|
||||||
|
return normalized_cookies
|
||||||
|
|
||||||
|
|
||||||
|
def post_process(
|
||||||
|
normalized_cookies: List[Dict[str, Any]],
|
||||||
|
*,
|
||||||
|
original_type: Type[Any]
|
||||||
|
) -> Any:
|
||||||
|
"""Convert the cookies to their original format for
|
||||||
|
maximum compatibility."""
|
||||||
|
|
||||||
|
if issubclass(original_type, dict):
|
||||||
|
return {
|
||||||
|
cookie.pop('name'): cookie
|
||||||
|
for cookie in normalized_cookies
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return normalized_cookies
|
||||||
|
|
||||||
|
|
||||||
|
def fix_layout(session: 'Session', hostname: str, args: argparse.Namespace) -> None:
|
||||||
|
if not isinstance(session['cookies'], dict):
|
||||||
|
return None
|
||||||
|
|
||||||
|
session['cookies'] = [
|
||||||
|
{
|
||||||
|
'name': key,
|
||||||
|
**value
|
||||||
|
}
|
||||||
|
for key, value in session['cookies'].items()
|
||||||
|
]
|
||||||
|
for cookie in session.cookies:
|
||||||
|
if cookie.domain == '':
|
||||||
|
if args.bind_cookies:
|
||||||
|
cookie.domain = hostname
|
||||||
|
else:
|
||||||
|
cookie._rest['is_explicit_none'] = True
|
73
httpie/legacy/v3_2_0_session_header_format.py
Normal file
73
httpie/legacy/v3_2_0_session_header_format.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
from typing import Any, Type, List, Dict, TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from httpie.sessions import Session
|
||||||
|
|
||||||
|
|
||||||
|
OLD_HEADER_STORE_WARNING = '''\
|
||||||
|
Outdated layout detected for the current session. Please consider updating it,
|
||||||
|
in order to use the latest features regarding the header layout.
|
||||||
|
|
||||||
|
For fixing the current session:
|
||||||
|
|
||||||
|
$ httpie cli sessions upgrade {hostname} {session_id}
|
||||||
|
'''
|
||||||
|
|
||||||
|
OLD_HEADER_STORE_WARNING_FOR_NAMED_SESSIONS = '''\
|
||||||
|
|
||||||
|
For fixing all named sessions:
|
||||||
|
|
||||||
|
$ httpie cli sessions upgrade-all
|
||||||
|
'''
|
||||||
|
|
||||||
|
OLD_HEADER_STORE_LINK = '\nSee $INSERT_LINK for more information.'
|
||||||
|
|
||||||
|
|
||||||
|
def pre_process(session: 'Session', headers: Any) -> List[Dict[str, Any]]:
|
||||||
|
"""Serialize the headers into a unified form and issue a warning if
|
||||||
|
the session file is using the old layout."""
|
||||||
|
|
||||||
|
is_old_style = isinstance(headers, dict)
|
||||||
|
if is_old_style:
|
||||||
|
normalized_headers = list(headers.items())
|
||||||
|
else:
|
||||||
|
normalized_headers = [
|
||||||
|
(item['name'], item['value'])
|
||||||
|
for item in headers
|
||||||
|
]
|
||||||
|
|
||||||
|
if is_old_style:
|
||||||
|
warning = OLD_HEADER_STORE_WARNING.format(hostname=session.bound_host, session_id=session.session_id)
|
||||||
|
if not session.is_anonymous:
|
||||||
|
warning += OLD_HEADER_STORE_WARNING_FOR_NAMED_SESSIONS
|
||||||
|
warning += OLD_HEADER_STORE_LINK
|
||||||
|
session.warn_legacy_usage(warning)
|
||||||
|
|
||||||
|
return normalized_headers
|
||||||
|
|
||||||
|
|
||||||
|
def post_process(
|
||||||
|
normalized_headers: List[Dict[str, Any]],
|
||||||
|
*,
|
||||||
|
original_type: Type[Any]
|
||||||
|
) -> Any:
|
||||||
|
"""Deserialize given header store into the original form it was
|
||||||
|
used in."""
|
||||||
|
|
||||||
|
if issubclass(original_type, dict):
|
||||||
|
# For the legacy behavior, preserve the last value.
|
||||||
|
return {
|
||||||
|
item['name']: item['value']
|
||||||
|
for item in normalized_headers
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return normalized_headers
|
||||||
|
|
||||||
|
|
||||||
|
def fix_layout(session: 'Session', *args, **kwargs) -> None:
|
||||||
|
from httpie.sessions import materialize_headers
|
||||||
|
|
||||||
|
if not isinstance(session['headers'], dict):
|
||||||
|
return None
|
||||||
|
|
||||||
|
session['headers'] = materialize_headers(session['headers'])
|
@ -37,7 +37,8 @@ def main(args: List[Union[str, bytes]] = sys.argv, env: Environment = Environmen
|
|||||||
parser=parser,
|
parser=parser,
|
||||||
main_program=main_program,
|
main_program=main_program,
|
||||||
args=args,
|
args=args,
|
||||||
env=env
|
env=env,
|
||||||
|
use_default_options=False,
|
||||||
)
|
)
|
||||||
except argparse.ArgumentError:
|
except argparse.ArgumentError:
|
||||||
program_args = args[1:]
|
program_args = args[1:]
|
||||||
@ -51,7 +52,6 @@ def program():
|
|||||||
try:
|
try:
|
||||||
exit_status = main()
|
exit_status = main()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
from httpie.status import ExitStatus
|
|
||||||
exit_status = ExitStatus.ERROR_CTRL_C
|
exit_status = ExitStatus.ERROR_CTRL_C
|
||||||
|
|
||||||
return exit_status
|
return exit_status
|
||||||
|
@ -1,39 +1,94 @@
|
|||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from httpie.cli.argparser import HTTPieManagerArgumentParser
|
from httpie.cli.argparser import HTTPieManagerArgumentParser
|
||||||
|
from httpie.cli.options import Qualifiers, ARGPARSE_QUALIFIER_MAP, map_qualifiers, parser_to_parser_spec
|
||||||
from httpie import __version__
|
from httpie import __version__
|
||||||
|
|
||||||
|
CLI_SESSION_UPGRADE_FLAGS = [
|
||||||
|
{
|
||||||
|
'flags': ['--bind-cookies'],
|
||||||
|
'action': 'store_true',
|
||||||
|
'default': False,
|
||||||
|
'help': 'Bind domainless cookies to the host that session belongs.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
COMMANDS = {
|
COMMANDS = {
|
||||||
'plugins': {
|
'cli': {
|
||||||
'help': 'Manage HTTPie plugins.',
|
'help': 'Manage HTTPie for Terminal',
|
||||||
'install': [
|
'export-args': [
|
||||||
'Install the given targets from PyPI '
|
'Export available options for the CLI',
|
||||||
'or from a local paths.',
|
|
||||||
{
|
{
|
||||||
'dest': 'targets',
|
'flags': ['-f', '--format'],
|
||||||
'nargs': '+',
|
'choices': ['json'],
|
||||||
'help': 'targets to install'
|
'help': 'Format to export in.',
|
||||||
|
'default': 'json'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'upgrade': [
|
'check-updates': [
|
||||||
'Upgrade the given plugins',
|
'Check for updates'
|
||||||
{
|
|
||||||
'dest': 'targets',
|
|
||||||
'nargs': '+',
|
|
||||||
'help': 'targets to upgrade'
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
'uninstall': [
|
'sessions': {
|
||||||
'Uninstall the given HTTPie plugins.',
|
'help': 'Manage HTTPie sessions',
|
||||||
{
|
'upgrade': [
|
||||||
'dest': 'targets',
|
'Upgrade the given HTTPie session with the latest '
|
||||||
'nargs': '+',
|
'layout. A list of changes between different session versions '
|
||||||
'help': 'targets to install'
|
'can be found in the official documentation.',
|
||||||
}
|
{
|
||||||
],
|
'dest': 'hostname',
|
||||||
'list': [
|
'metavar': 'HOSTNAME',
|
||||||
'List all installed HTTPie plugins.'
|
'help': 'The host this session belongs.'
|
||||||
],
|
},
|
||||||
},
|
{
|
||||||
|
'dest': 'session',
|
||||||
|
'metavar': 'SESSION_NAME_OR_PATH',
|
||||||
|
'help': 'The name or the path for the session that will be upgraded.'
|
||||||
|
},
|
||||||
|
*CLI_SESSION_UPGRADE_FLAGS
|
||||||
|
],
|
||||||
|
'upgrade-all': [
|
||||||
|
'Upgrade all named sessions with the latest layout. A list of '
|
||||||
|
'changes between different session versions can be found in the official '
|
||||||
|
'documentation.',
|
||||||
|
*CLI_SESSION_UPGRADE_FLAGS
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
COMMANDS['plugins'] = COMMANDS['cli']['plugins'] = {
|
||||||
|
'help': 'Manage HTTPie plugins.',
|
||||||
|
'install': [
|
||||||
|
'Install the given targets from PyPI '
|
||||||
|
'or from a local paths.',
|
||||||
|
{
|
||||||
|
'dest': 'targets',
|
||||||
|
'metavar': 'TARGET',
|
||||||
|
'nargs': Qualifiers.ONE_OR_MORE,
|
||||||
|
'help': 'targets to install'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'upgrade': [
|
||||||
|
'Upgrade the given plugins',
|
||||||
|
{
|
||||||
|
'dest': 'targets',
|
||||||
|
'metavar': 'TARGET',
|
||||||
|
'nargs': Qualifiers.ONE_OR_MORE,
|
||||||
|
'help': 'targets to upgrade'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'uninstall': [
|
||||||
|
'Uninstall the given HTTPie plugins.',
|
||||||
|
{
|
||||||
|
'dest': 'targets',
|
||||||
|
'metavar': 'TARGET',
|
||||||
|
'nargs': Qualifiers.ONE_OR_MORE,
|
||||||
|
'help': 'targets to install'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'list': [
|
||||||
|
'List all installed HTTPie plugins.'
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -47,22 +102,28 @@ def missing_subcommand(*args) -> str:
|
|||||||
return f'Please specify one of these: {subcommands}'
|
return f'Please specify one of these: {subcommands}'
|
||||||
|
|
||||||
|
|
||||||
def generate_subparsers(root, parent_parser, definitions):
|
def generate_subparsers(root, parent_parser, definitions, spec):
|
||||||
action_dest = '_'.join(parent_parser.prog.split()[1:] + ['action'])
|
action_dest = '_'.join(parent_parser.prog.split()[1:] + ['action'])
|
||||||
actions = parent_parser.add_subparsers(
|
actions = parent_parser.add_subparsers(
|
||||||
dest=action_dest
|
dest=action_dest
|
||||||
)
|
)
|
||||||
for command, properties in definitions.items():
|
for command, properties in definitions.items():
|
||||||
is_subparser = isinstance(properties, dict)
|
is_subparser = isinstance(properties, dict)
|
||||||
|
properties = properties.copy()
|
||||||
|
|
||||||
descr = properties.pop('help', None) if is_subparser else properties.pop(0)
|
descr = properties.pop('help', None) if is_subparser else properties.pop(0)
|
||||||
command_parser = actions.add_parser(command, description=descr)
|
command_parser = actions.add_parser(command, description=descr)
|
||||||
command_parser.root = root
|
command_parser.root = root
|
||||||
if is_subparser:
|
if is_subparser:
|
||||||
generate_subparsers(root, command_parser, properties)
|
generate_subparsers(root, command_parser, properties, spec)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
group = spec.add_group(parent_parser.prog + ' ' + command, description=descr)
|
||||||
for argument in properties:
|
for argument in properties:
|
||||||
command_parser.add_argument(**argument)
|
argument = argument.copy()
|
||||||
|
flags = argument.pop('flags', [])
|
||||||
|
command_parser.add_argument(*flags, **map_qualifiers(argument, ARGPARSE_QUALIFIER_MAP))
|
||||||
|
group.add_argument(*flags, **argument)
|
||||||
|
|
||||||
|
|
||||||
parser = HTTPieManagerArgumentParser(
|
parser = HTTPieManagerArgumentParser(
|
||||||
@ -109,4 +170,12 @@ parser.add_argument(
|
|||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
|
||||||
generate_subparsers(parser, parser, COMMANDS)
|
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)
|
||||||
|
68
httpie/manager/compat.py
Normal file
68
httpie/manager/compat.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import List, Optional
|
||||||
|
from httpie.compat import is_frozen
|
||||||
|
|
||||||
|
|
||||||
|
class PipError(Exception):
|
||||||
|
"""An exception that occurs when pip exits with an error status code."""
|
||||||
|
|
||||||
|
def __init__(self, stdout, stderr):
|
||||||
|
self.stdout = stdout
|
||||||
|
self.stderr = stderr
|
||||||
|
|
||||||
|
|
||||||
|
def _discover_system_pip() -> List[str]:
|
||||||
|
# When we are running inside of a frozen binary, we need the system
|
||||||
|
# pip to install plugins since there is no way for us to execute any
|
||||||
|
# code outside of the HTTPie.
|
||||||
|
#
|
||||||
|
# We explicitly depend on system pip, so the SystemError should not
|
||||||
|
# be executed (except for broken installations).
|
||||||
|
def _check_pip_version(pip_location: Optional[str]) -> bool:
|
||||||
|
if not pip_location:
|
||||||
|
return False
|
||||||
|
|
||||||
|
with suppress(subprocess.CalledProcessError):
|
||||||
|
stdout = subprocess.check_output([pip_location, "--version"], text=True)
|
||||||
|
return "python 3" in stdout
|
||||||
|
|
||||||
|
targets = [
|
||||||
|
"pip",
|
||||||
|
"pip3"
|
||||||
|
]
|
||||||
|
for target in targets:
|
||||||
|
pip_location = shutil.which(target)
|
||||||
|
if _check_pip_version(pip_location):
|
||||||
|
return pip_location
|
||||||
|
|
||||||
|
raise SystemError("Couldn't find 'pip' executable. Please ensure that pip in your system is available.")
|
||||||
|
|
||||||
|
|
||||||
|
def _run_pip_subprocess(pip_executable: List[str], args: List[str]) -> bytes:
|
||||||
|
|
||||||
|
cmd = [*pip_executable, *args]
|
||||||
|
try:
|
||||||
|
process = subprocess.run(
|
||||||
|
cmd,
|
||||||
|
check=True,
|
||||||
|
shell=False,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
raise PipError(error.stdout, error.stderr) from error
|
||||||
|
else:
|
||||||
|
return process.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def run_pip(args: List[str]) -> bytes:
|
||||||
|
if is_frozen:
|
||||||
|
pip_executable = [_discover_system_pip()]
|
||||||
|
else:
|
||||||
|
pip_executable = [sys.executable, '-m', 'pip']
|
||||||
|
|
||||||
|
return _run_pip_subprocess(pip_executable, args)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user