Compare commits

..

125 Commits

Author SHA1 Message Date
9d2e2afede Add failing reproduction test case 2022-06-07 14:50:56 +02:00
418b12bbd6 Cleanup 2022-06-07 14:31:15 +02:00
ecff53f2d5 Have naked $ make list all tasks 2022-06-07 14:29:19 +02:00
41da87f7c8 Install .[test] reqs in make install-reqs 2022-06-07 14:26:48 +02:00
4f172a61b4 Fix installation 2022-06-07 14:23:52 +02:00
542a2d35de Fix typos in comment lines (#1405)
* httpie/internal/daemons.py
* httpie/utils.py
2022-05-19 16:22:50 +03:00
d9e1dc08c9 Package man pages into the deb packages as well. (#1403) 2022-05-16 18:19:49 +03:00
3b734fb0bc Fix a misput backtick 2022-05-16 10:10:51 +03:00
8abe47969e Improve single-binary method wording (#1399) 2022-05-10 19:55:31 +03:00
8173cb0337 Typo fix (#1397) 2022-05-09 20:26:16 +03:00
7fd34fc8ce Fix-up standalone binary docs. (#1396) 2022-05-09 19:22:20 +03:00
80ae644464 updated fish completions for httpie 3.2.1 (#1394) 2022-05-09 18:24:48 +03:00
69fe5dbfd1 Update release-linux-standalone.yml 2022-05-09 11:46:19 +03:00
f09e7564e7 Standalone binary documentation. 2022-05-09 09:01:59 +03:00
dc5274e491 Use the proper directory name for the choco action. (#1392)
* Use the proper directory name for the choco action.

* Refresh the current environment to reflect the new installation.
2022-05-06 20:47:40 +03:00
ad2b86ccf4 Update the chocolatey spec (#1391) 2022-05-06 19:47:55 +03:00
11b2af0f59 Automatically attach debian packages and linux binaries to the release (#1390)
* Automatically attach debian packages and linux binaries to the release

* Use set-output syntax
2022-05-06 15:40:14 +03:00
b54239b525 Changelog for 3.2.1 2022-05-06 10:08:16 +03:00
b0b0f3dc53 Mask the stdout/stderr for the inner daemon process on MacOS (#1389) 2022-05-06 10:06:59 +03:00
9f7612cdeb Checking headers to determine auto-streaming (#1383) 2022-05-06 09:59:22 +03:00
5e76ebc5e1 Use make install to get the dependencies as well 2022-05-05 23:44:17 +03:00
343a521673 Create the virtual env for the build action. 2022-05-05 23:40:39 +03:00
2142ae60c3 Final release prep for 3.2.0 (#1387) 2022-05-05 23:32:35 +03:00
0b6a9b23c2 Add missing changelog entries (#1386) 2022-05-05 23:18:00 +03:00
9e1c0b98c7 Contributors for 3.2.0 (#1374) 2022-05-05 11:19:19 -07:00
003f2095d4 Automatic release update warnings. (#1336)
* Hide pretty help

* Automatic release update warnings.

* `httpie cli check-updates`

* adapt to the new loglevel construct

* Don't make the pie-colors the bold

* Apply review feedback.

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2022-05-05 11:18:20 -07:00
f9b5c2f696 Man page fixes (#1364)
- Highlighting for options (-x, --x) now doesn't strip the prefix (may be whitespace).
- Escape sequences are now cross-platform compatible (directly taken by groff/troff [man's renderer])
- Now we check for the section before displaying the man pages.
- On MacOS, there is HTTP(n) which is different from our HTTP(1). This used to conflict with it, and we showed the wrong page. Now we specifically ask foir HTTP(1).
- Errors that might happen (e.g non executable man command) is now suppressed. So in the worst case (if anything regarding man execution goes wrong), we'll always display the manual.
- Docs for man pages.
- HTTPie man pages.
- Epilog for the man pages (see also)
- Auto-generated comments.
2022-05-05 11:17:37 -07:00
76495cbdec Hide pretty help (#1384) 2022-05-05 11:17:24 -07:00
c4d7d05f3b Don't make bold the default for pie themes (#1385) 2022-05-05 11:17:12 -07:00
7a4fb5d966 Update installation instructions for debian (#1373) 2022-05-05 08:40:52 -07:00
f7c1bb269e Refactor palette (#1378)
* Refactor palette

* Modifiers / change static strings to colors

* Colors...

* Error-based tests

* Styling linting

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2022-05-05 08:17:05 -07:00
0f9fd76852 Deprecate --history-print (#1380) 2022-05-03 06:29:02 -07:00
af1d6b1853 Use sentence case for the group names in the parser (#1381) 2022-05-03 06:28:46 -07:00
419cc2c34a Skip on pyOpenSSL (#1376) 2022-04-28 05:18:20 -07:00
79a8ecd84b Disable PackIt CI on the PRs (#1375) 2022-04-28 11:59:08 +03:00
d262181bed Fix typos (user-facing and non-user-facing) (#1357)
* Fix typos (user-facing and non-user-facing

Found via `codespell -q 3 -L datas,medias,warmup`

* Fix source typo found in tests/
2022-04-16 02:06:34 +03:00
732878f1b4 Bump peter-evans/create-pull-request from 3 to 4 (#1355)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 3 to 4.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/v3...v4)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-15 23:35:46 +03:00
83803db14d Explain that we lost 54k stars in the README with a link to blog post 2022-04-14 18:27:18 +02:00
dd2c9513f3 Single binary executables (#1330)
* Single binary executables / DEB packages.

* Attach single binary executables to the releases
2022-04-14 08:11:12 -07:00
278dfc487d Don't block users with the warning thread. (#1350)
Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2022-04-14 08:00:53 -07:00
ff6f1887b0 [Major] UI Enhancements (#1321)
* Refactor tests to use a text-based standard output. (#1318)

* Implement new style `--help` (#1316)

* Implement man page generation (#1317)

* Implement rich progress bars. (#1324)

* Man page deployment & isolation. (#1325)

* Remove all unsorted usages in the CLI docs

* Implement isolated mode for man page generation

* Add a CI job for autogenerated files

* Distribute man pages through PyPI

* Pin the date for man pages. (#1326)

* Hide suppressed arguments from --help/man pages (#1329)

* Change download spinner to line (#1328)

* Regenerate autogenerated files when pushed against to master. (#1339)

* Highlight options (#1340)

* Additional man page enhancements (#1341)

* Group options by the parent category & highlight -o/--o

* Display (and underline) the METAVAR on man pages.

* Make help message processing more robust (#1342)

* Inherit `help` from `short_help`

* Don't mirror short_help directly.

* Fixup the serialization

* Use `pager` and `man` on `--manual` when applicable (#1343)

* Run `man $program` on --manual

* Page the output of `--manual` for systems that lack man pages

* Improvements over progress bars (separate bar, status line, etc.) (#1346)

* Redesign the --help layout.

* Make our usage of rich compatible with 9.10.0

* Add `HTTPIE_NO_MAN_PAGES`

* Make tests also patch os.get_terminal_size

* Generate CLI spec from HTTPie & Man Page Hook (#1354)

* Generate CLI spec from HTTPie & add man page hook

* Use the full command space for the option headers
2022-04-14 07:43:10 -07:00
86f4bf4d0a Add support for sending secure cookies over localhost (#1327)
* Add support for sending secure cookies over localhost

* Refactor

* Fix the CI

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2022-04-14 07:42:05 -07:00
e6d0bfec7c Use the raw request version when the original is not accessible (#1352) 2022-04-14 07:41:12 -07:00
9f1ec6d5cc Limit concurrency of our test workflow (#1353) 2022-04-14 07:38:28 -07:00
85ba9ad8ea Bump actions/stale from 4 to 5 (#1347)
Bumps [actions/stale](https://github.com/actions/stale) from 4 to 5.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-11 12:14:51 +03:00
d03e3f4e14 Implement support for multiple headers with the same name in sessions (#1335)
* Properly remove duplicate Cookie headers

* Implement support for multiple headers with the same name in sessions

* More testing

* Cleanup

* Remove duplicated test, cleanup

* Fix pycodestyle

* CHANGELOG

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2022-04-03 06:48:31 -07:00
c157948531 Add httpie cli plugins in favor of the new cli namespace. (#1320)
* Add `httpie cli plugins` in favor of the new cli namespace.

* Separate each task to individual modules.

* Move httpie.manager.plugins to httpie.manager.tasks.plugins

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2022-04-03 06:06:42 -07:00
33ea977b64 Don't send Content-Length for OPTIONS requests when there is no data. (#1319) 2022-04-03 06:02:41 -07:00
d1596dde12 Ping werkzeug to <2.1.0 (#1345) 2022-04-01 14:28:59 +03:00
af2ffb6999 Bump peter-evans/create-or-update-comment from 1 to 2 (#1332)
Bumps [peter-evans/create-or-update-comment](https://github.com/peter-evans/create-or-update-comment) from 1 to 2.
- [Release notes](https://github.com/peter-evans/create-or-update-comment/releases)
- [Commits](https://github.com/peter-evans/create-or-update-comment/compare/v1...v2)

---
updated-dependencies:
- dependency-name: peter-evans/create-or-update-comment
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-22 22:50:08 +03:00
0632c4d614 Bump peter-evans/find-comment from 1 to 2 (#1333)
Bumps [peter-evans/find-comment](https://github.com/peter-evans/find-comment) from 1 to 2.
- [Release notes](https://github.com/peter-evans/find-comment/releases)
- [Commits](https://github.com/peter-evans/find-comment/compare/v1...v2)

---
updated-dependencies:
- dependency-name: peter-evans/find-comment
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-22 22:49:25 +03:00
6787a2bd29 Fix/tweak docs 2022-03-14 16:34:17 +01:00
9d2864b966 Fix broken docs link (#1322) 2022-03-12 15:09:01 -08:00
a5288f0cd6 Integrate automatic releases. (#1315) 2022-03-09 15:26:51 +03:00
8efa7cb04d Add table headers for upgrade flags (#1314) 2022-03-08 16:43:09 +03:00
baec1b2202 Update chocolatey for release 2022-03-08 02:29:21 +03:00
266c6375c6 Release prep for 3.1.0 (#1313) 2022-03-08 01:50:09 +03:00
77af4c7a5c Decouple parser definition from argparse (#1293) 2022-03-08 01:34:04 +03:00
7509dd4e6c Fix documentation styling errors. 2022-03-07 23:29:48 +03:00
f08c1bee17 Change error messages to use a better format. 2022-03-07 23:29:48 +03:00
59d9e928f8 Tweak 2022-03-07 23:29:48 +03:00
0a873172c9 Tweak SECURITY and add a Security policy section to docs 2022-03-07 23:29:48 +03:00
614866eeb2 Polish sessions docs 2022-03-07 23:29:48 +03:00
395914fb4d Apply suggestions from the review 2022-03-07 23:29:48 +03:00
65ab7d5caa Implement new style cookies 2022-03-07 23:29:48 +03:00
b5623ccc87 Fix the tests with the latest layout 2022-03-07 19:16:51 +03:00
ec203b1fac Tweak compact help 2022-03-07 19:16:51 +03:00
350abe3033 Make the naked invocation display a compacted help 2022-03-07 19:16:51 +03:00
9241a09360 Mention about interactive prompt on key passphrases 2022-03-07 16:09:07 +03:00
15013fd609 Implement support for private key passphrases 2022-03-07 16:09:07 +03:00
98688b2f2d Style fix on the changelog 2022-03-07 16:01:29 +03:00
5ac05e9514 Add changelog entry 2022-03-07 16:01:29 +03:00
5c98253377 Update httpie/uploads.py 2022-03-07 16:01:29 +03:00
b0f5b8ab26 Prevent data race happening between select.select and file.read() 2022-03-07 16:01:29 +03:00
55087a901e Introduce a mode to suppress all warnings (#1283) 2022-03-07 15:40:35 +03:00
c901e70463 Replaced unmaintained OAuth plugin with new httpie-oauth1 plugin. (#1302) 2022-03-03 08:31:06 -08:00
25bd817bb2 Fix displaying of status code without a status message. (#1301)
Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2022-03-03 08:28:04 -08:00
6f77e144e4 Bump actions/checkout from 2 to 3 (#1311)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-03 08:16:56 -08:00
6bf39e469f Bump actions/setup-python from 2 to 3 (#1307)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 3.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-01 17:17:41 +03:00
30cd862fc0 Update commands for Arch (#1306) 2022-03-01 00:57:23 +03:00
ad613f29d2 Add a changelog entry for the top-level array regulation 2022-02-25 12:51:34 +03:00
225dccb218 Regulate top-level arrays (#1292)
* Redesign the starting path

* Do not cast `:=[1,2,3]` to a top-level array
2022-02-08 15:18:40 -08:00
cafa11665b Disable additional repos 2022-02-08 12:49:33 +03:00
0a9d3d3c54 Fix the packit syntax 2022-02-08 12:49:33 +03:00
e306667436 Leave a note for the local spec 2022-02-08 12:49:33 +03:00
384d3869f6 Update the local copy fore 3.0.2 2022-02-08 12:49:33 +03:00
5fd48e3137 Use the lastest fedora spec in the packit 2022-02-08 12:49:33 +03:00
37ef670876 Update copyright year 2022-02-05 22:09:44 +03:00
46e782bf75 Point package to 3.0.2 2022-02-05 22:09:44 +03:00
42edb1eb76 Use 3.0.0 blog post as the changelog 2022-02-05 22:09:44 +03:00
d45f413f12 Make the version point to 3.0.3.dev0 (#1291) 2022-02-03 01:47:06 -08:00
f1ea486025 Fix escaping of integer indexes with multiple backslashes (#1288) 2022-02-01 02:10:55 -08:00
7abddfe350 Mark stdin warning related tests with requires_external_processes (#1289)
* Mark test_stdin_read_warning with requires_installation

* Mark stdin tests with requires_external_processes

Co-authored-by: Nilushan Costa <19643850+nilushancosta@users.noreply.github.com>
2022-02-01 01:52:07 -08:00
86ba995ad8 2022 (#1259) 2022-01-26 17:45:03 +03:00
c03f081a7e Finish off the naming 2022-01-26 12:51:10 +03:00
a7d8187b21 Proper naming for the release runs 2022-01-26 12:50:22 +03:00
fc383e9b78 Add names to the CI runners 2022-01-26 12:49:27 +03:00
770df02291 Add level parameter to the snap releaser (#1282) 2022-01-26 12:44:24 +03:00
f756cad58d Update CHANGELOG.md 2022-01-24 16:54:50 -08:00
fde64d578d Update CHANGELOG.md 2022-01-24 10:32:24 -08:00
c8404493e5 Update CHANGELOG.md 2022-01-24 10:32:07 -08:00
559134de0a Release 3.0.2 (#1281) 2022-01-24 21:20:17 +03:00
813e8864a1 Dont apply default options on the httpie command (#1280)
* Mark tests with requires_installation

* Dont apply default options on the httpie command

* lint
2022-01-24 10:13:47 -08:00
45fcd746d7 docs: format the benchmark docs 2022-01-24 18:20:03 +03:00
d5e3611e85 fix lint errors 2022-01-24 18:17:55 +03:00
378a1f513e Document the pyOpenSSL option 2022-01-24 18:17:55 +03:00
df6843b15a docs: add --{local, target}-{repo, branch} / format 2022-01-24 18:17:55 +03:00
640901146f docs: document the --fresh option 2022-01-24 18:17:55 +03:00
6b5d96da72 Describe the usage for benchmarks 2022-01-24 18:17:55 +03:00
97bd9c2a89 docs: add requirements 2022-01-24 18:17:55 +03:00
708608e1d4 docs: mention about the runners 2022-01-24 18:17:55 +03:00
d56a1f216e docs: give a brief description 2022-01-24 18:17:55 +03:00
738a6bea57 docs: fix the title to benchmarking infrastructure 2022-01-24 18:17:55 +03:00
ec521c461b docs: add initial benchmark docs 2022-01-24 18:17:55 +03:00
212000199e docs: fix the nested json example (#1278) 2022-01-24 18:00:54 +03:00
700dbeddb0 Typos 2022-01-24 01:51:53 +01:00
30a4d29f77 Update CHANGELOG.md 2022-01-23 15:15:16 -08:00
aedcad7e2a Update CHANGELOG.md 2022-01-23 15:14:31 -08:00
202f59e04a Tweak nested JSON docs 2022-01-23 18:36:18 +01:00
ba0c1ab258 Tweak auth docs 2022-01-23 17:24:29 +01:00
217cf8ddae Document auto-stream 2022-01-23 17:17:58 +01:00
859e442083 Docs 2022-01-23 16:59:07 +01:00
4e59bbfae6 Docs 2022-01-23 16:52:31 +01:00
caa8fb9058 Tweak response meta docs
- expand response meta section
- add examples
- interlink sections
2022-01-23 14:35:20 +01:00
2797b7244c Update cached brew formula 2022-01-23 14:11:09 +01:00
155 changed files with 8301 additions and 1361 deletions

View File

@ -0,0 +1,28 @@
name: Update Autogenerated Files
on:
push:
branches:
- master
jobs:
regen-autogenerated-files:
runs-on: ubuntu-latest
if: github.event.pull_request.head.repo.full_name == github.repository
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: 3.9
- run: make regen-all
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v4
with:
commit-message: "[automated] Update auto-generated files"
title: "[automated] Update auto-generated files"
delete-branch: true
token: ${{ secrets.GITHUB_TOKEN }}

View File

@ -13,8 +13,8 @@ jobs:
if: github.event.label.name == 'benchmark'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: "3.9"
@ -30,7 +30,7 @@ jobs:
echo "::set-output name=body::$body"
- name: Find Comment
uses: peter-evans/find-comment@v1
uses: peter-evans/find-comment@v2
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
@ -38,7 +38,7 @@ jobs:
body-includes: '# Benchmarks'
- name: Create or update comment
uses: peter-evans/create-or-update-comment@v1
uses: peter-evans/create-or-update-comment@v2
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}

View File

@ -1,3 +1,5 @@
name: Code Style Check
on:
pull_request:
paths:
@ -11,8 +13,8 @@ jobs:
code-style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: 3.9
- run: make venv

View File

@ -1,3 +1,5 @@
name: Coverage
on:
pull_request:
paths:
@ -10,8 +12,8 @@ jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: "3.10"
- run: make install

View File

@ -1,3 +1,5 @@
name: Check Markdown Style
on:
pull_request:
paths:
@ -8,7 +10,7 @@ jobs:
doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:

View File

@ -1,3 +1,5 @@
name: Deploy Documentation
on:
push:
branches:

View File

@ -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
View 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@v1
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
View 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 }}

View 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@v3
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/httpie/releases/tags/${{ github.event.inputs.tag_name }}"
export UPLOAD_URL=`https --ignore-stdin GET $API_URL | jq -r ".upload_url"`
echo "::set-output name=UPLOAD_URL::$UPLOAD_URL"
- name: Publish Debian Package
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
View 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@v3
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 }}

View File

@ -1,3 +1,5 @@
name: Release on Snap
on:
workflow_dispatch:
inputs:
@ -7,16 +9,32 @@ on:
default: "master"
jobs:
snap:
snap-build-and-release:
name: Build & Release the Snap Package
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:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.branch }}
- uses: snapcore/action-build@v1
id: build
- uses: snapcore/action-publish@v1
with:
store_login: ${{ secrets.SNAP_STORE_LOGIN }}
snap: ${{ steps.build.outputs.snap }}
release: edge
release: ${{ matrix.level }}

View File

@ -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 }}

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
- uses: actions/stale@v5
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.'
stale-pr-label: 'stale'

View File

@ -1,3 +1,6 @@
name: Test Snap Package (Linux)
on:
pull_request:
paths:
@ -9,7 +12,7 @@ jobs:
snap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build
uses: snapcore/action-build@v1
id: snapcraft

View File

@ -1,3 +1,5 @@
name: Test Brew Package (MacOS)
on:
pull_request:
paths:
@ -9,7 +11,7 @@ jobs:
brew:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup brew
run: |
brew developer on

View File

@ -1,3 +1,8 @@
name: Tests
concurrency:
group: ${{ github.head_ref || github.run_id }}
cancel-in-progress: true
on:
push:
branches:
@ -24,8 +29,8 @@ jobs:
pyopenssl: [0, 1]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v3
- uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}
- name: Windows setup

4
.gitignore vendored
View File

@ -43,8 +43,8 @@ MANIFEST
# PyInstaller
# 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.
*.manifest
*.spec
*.manifest
# Installer logs
pip-log.txt
@ -151,3 +151,5 @@ dmypy.json
# Windows Chocolatey
*.nupkg
artifacts/

View File

@ -3,16 +3,10 @@
specfile_path: httpie.spec
actions:
# 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: "cp docs/packaging/linux-fedora/httpie.spec.txt httpie.spec"
post-upstream-clone: "wget https://src.fedoraproject.org/rpms/httpie/raw/rawhide/f/httpie.spec -O 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:
- 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
trigger: release
metadata:

View File

@ -3,12 +3,53 @@
This document records all notable changes to [HTTPie](https://httpie.io).
This project adheres to [Semantic Versioning](https://semver.org/).
## [3.2.1](https://github.com/httpie/httpie/compare/3.1.0...3.2.1) (2022-05-06)
- Improved support for determining auto-streaming when the `Content-Type` header includes encoding information. ([#1383](https://github.com/httpie/httpie/pull/1383))
- Fixed the display of the crash happening in the secondary process for update checks. ([#1388](https://github.com/httpie/httpie/issues/1388))
## [3.2.0](https://github.com/httpie/httpie/compare/3.1.0...3.2.0) (2022-05-05)
- Added a warning for notifying the user about the new updates. ([#1336](https://github.com/httpie/httpie/pull/1336))
- Added support for single binary executables. ([#1330](https://github.com/httpie/httpie/pull/1330))
- Added support for man pages (and auto generation of them from the parser declaration). ([#1317](https://github.com/httpie/httpie/pull/1317))
- Added `http --manual` for man pages & regular manual with pager. ([#1343](https://github.com/httpie/httpie/pull/1343))
- Added support for session persistence of repeated headers with the same name. ([#1335](https://github.com/httpie/httpie/pull/1335))
- Added support for sending `Secure` cookies to the `localhost` (and `.local` suffixed domains). ([#1308](https://github.com/httpie/httpie/issues/1308))
- Improved UI for the progress bars. ([#1324](https://github.com/httpie/httpie/pull/1324))
- Fixed redundant creation of `Content-Length` header on `OPTIONS` requests. ([#1310](https://github.com/httpie/httpie/issues/1310))
- Fixed blocking of warning thread on some use cases. ([#1349](https://github.com/httpie/httpie/issues/1349))
- Changed `httpie plugins` to the new `httpie cli` namespace as `httpie cli plugins` (`httpie plugins` continues to work as a hidden alias). ([#1320](https://github.com/httpie/httpie/issues/1320))
- Soft deprecated the `--history-print`. ([#1380](https://github.com/httpie/httpie/pull/1380))
## [3.1.0](https://github.com/httpie/httpie/compare/3.0.2...3.1.0) (2022-03-08)
- **SECURITY** Fixed the [vulnerability](https://github.com/httpie/httpie/security/advisories/GHSA-9w4w-cpc8-h2fq) that caused exposure of cookies on redirects to third party hosts. ([#1312](https://github.com/httpie/httpie/pull/1312))
- Fixed escaping of integer indexes with multiple backslashes in the nested JSON builder. ([#1285](https://github.com/httpie/httpie/issues/1285))
- Fixed displaying of status code without a status message on non-`auto` themes. ([#1300](https://github.com/httpie/httpie/issues/1300))
- Fixed redundant issuance of stdin detection warnings on some rare cases due to underlying implementation. ([#1303](https://github.com/httpie/httpie/pull/1303))
- Fixed double `--quiet` so that it will now suppress all python level warnings. ([#1271](https://github.com/httpie/httpie/issues/1271))
- Added support for specifying certificate private key passphrases through `--cert-key-pass` and prompts. ([#946](https://github.com/httpie/httpie/issues/946))
- Added `httpie cli export-args` command for exposing the parser specification for the `http`/`https` commands. ([#1293](https://github.com/httpie/httpie/pull/1293))
- Improved regulation of top-level arrays. ([#1292](https://github.com/httpie/httpie/commit/225dccb2186f14f871695b6c4e0bfbcdb2e3aa28))
- Improved UI layout for standalone invocations. ([#1296](https://github.com/httpie/httpie/pull/1296))
## [3.0.2](https://github.com/httpie/httpie/compare/3.0.1...3.0.2) (2022-01-24)
[Whats 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/httpie/pull/1280))
## [3.0.1](https://github.com/httpie/httpie/compare/3.0.0...3.0.1) (2022-01-23)
- Changed the value shown as time elapsed from time-to-read-headers to total exchange time ([#1277](https://github.com/httpie/httpie/issues/1277))
[Whats 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/httpie/issues/1277))
## [3.0.0](https://github.com/httpie/httpie/compare/2.6.0...3.0.0) (2022-01-21)
[Whats 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/httpie/issues/1177))
- 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))
@ -32,7 +73,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
## [2.6.0](https://github.com/httpie/httpie/compare/2.5.0...2.6.0) (2021-10-14)
[Whats new in HTTPie 2.6.0 →](https://httpie.io/blog/httpie-2.6.0)
[Whats 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/httpie/issues/1130))
- Added charset auto-detection when `Content-Type` doesnt include it. ([#1110](https://github.com/httpie/httpie/issues/1110), [#1168](https://github.com/httpie/httpie/issues/1168))
@ -44,7 +85,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
## [2.5.0](https://github.com/httpie/httpie/compare/2.4.0...2.5.0) (2021-09-06)
[Whats new in HTTPie 2.5.0 →](https://httpie.io/blog/httpie-2.5.0)
[Whats 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
an alternative to `stdin`. ([#534](https://github.com/httpie/httpie/issues/534))

View File

@ -59,8 +59,10 @@ $ git checkout -b my_topical_branch
#### Setup
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started. Just run
the following command, which:
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started.
You can run `$ make` to see all the available tasks.
To get started, run the command below, which:
- Creates an isolated Python virtual environment inside `./venv`
(via the standard library [venv](https://docs.python.org/3/library/venv.html) tool);
@ -70,7 +72,7 @@ the following command, which:
- and runs tests (It is the same as running `make install test`).
```bash
$ make
$ make all
```
#### Python virtual environment

View File

@ -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
modification, are permitted provided that the following conditions are met:

View File

@ -22,6 +22,26 @@ VENV_PYTHON=$(VENV_BIN)/python
export PATH := $(VENV_BIN):$(PATH)
default: list-tasks
###############################################################################
# Default task to get a list of tasks when `make' is run without args.
# <https://stackoverflow.com/questions/4219255>
###############################################################################
list-tasks:
@echo Available tasks:
@echo ----------------
@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'
@echo
###############################################################################
# Installation
###############################################################################
all: uninstall-httpie install test
@ -30,10 +50,10 @@ install: venv install-reqs
install-reqs:
@echo $(H1)Updating package tools$(H1END)
$(VENV_PIP) install --upgrade pip wheel
$(VENV_PIP) install --upgrade pip wheel build
@echo $(H1)Installing dev requirements$(H1END)
$(VENV_PIP) install --upgrade --editable '.[dev]'
$(VENV_PIP) install --upgrade '.[dev]' '.[test]'
@echo $(H1)Installing HTTPie$(H1END)
$(VENV_PIP) install --upgrade --editable .
@ -147,19 +167,17 @@ doc-check:
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
###############################################################################
build:
rm -rf build/
$(VENV_PYTHON) setup.py sdist bdist_wheel
rm -rf build/ dist/
mv httpie/internal/__build_channel__.py httpie/internal/__build_channel__.py.original
echo 'BUILD_CHANNEL = "pip"' > httpie/internal/__build_channel__.py
$(VENV_PYTHON) -m build --sdist --wheel --outdir dist/
mv httpie/internal/__build_channel__.py.original httpie/internal/__build_channel__.py
publish: test-all publish-no-test
@ -203,7 +221,7 @@ brew-test:
- brew uninstall httpie
@echo $(H1)Building from source…$(H1END)
- brew install --build-from-source ./docs/packaging/brew/httpie.rb
- brew install --HEAD --build-from-source ./docs/packaging/brew/httpie.rb
@echo $(H1)Verifying…$(H1END)
http --version
@ -211,3 +229,17 @@ brew-test:
@echo $(H1)Auditing…$(H1END)
brew audit --strict httpie
###############################################################################
# Regeneration
###############################################################################
regen-all: regen-man-pages regen-install-methods
regen-man-pages: install
@echo $(H1)Regenerate man pages$(H1END)
$(VENV_PYTHON) extras/scripts/generate_man_pages.py
regen-install-methods:
@echo $(H1)Updating installation instructions in the docs$(H1END)
$(VENV_PYTHON) docs/installation/generate.py

View File

@ -21,6 +21,14 @@ They use simple and natural syntax and provide formatted and colorized output.
<img src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-animation.gif" alt="HTTPie in action" width="100%"/>
## 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
![](docs/stardust.png)
## Getting started
- [Installation instructions →](https://httpie.io/docs#installation)

14
SECURITY.md Normal file
View 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.

View File

@ -162,6 +162,8 @@ Also works for other Debian-derived distributions like MX Linux, Linux Mint, dee
```bash
# Install httpie
$ curl -SsL https://packages.httpie.io/deb/KEY.gpg | apt-key add -
$ curl -SsL -o /etc/apt/sources.list.d/httpie.list https://packages.httpie.io/deb/httpie.list
$ apt update
$ apt install httpie
```
@ -205,12 +207,27 @@ Also works for other Arch-derived distributions like ArcoLinux, EndeavourOS, Art
```bash
# Install httpie
$ pacman -Sy httpie
$ pacman -Syu httpie
```
```bash
# Upgrade httpie
$ pacman -Syu httpie
$ pacman -Syu
```
#### Single binary executables
Get the standalone HTTPie Linux executables when you don't want to go through the full installation process
```bash
# Install httpie
$ https --download packages.httpie.io/binaries/linux/http-latest -o http
$ chmod +x ./http
```
```bash
# Upgrade httpie
$ https --download packages.httpie.io/binaries/linux/http-latest -o http
```
### FreeBSD
@ -260,7 +277,7 @@ Verify that now you have the [current development version identifier](https://gi
```bash
$ http --version
# 3.0.0
# 3.X.X.dev0
```
## Usage
@ -277,7 +294,7 @@ Synopsis:
$ http [flags] [METHOD] URL [ITEM [ITEM]]
```
See also `http --help`.
See also `http --help` (and for systems where man pages are available, you can use `man http`).
### Examples
@ -448,7 +465,7 @@ $ http https://api.github.com/search/repositories q==httpie per_page==1
GET /search/repositories?q=httpie&per_page=1 HTTP/1.1
```
You can even retrieve the `value` from a file by using the `param==@file` syntax. This would also effectively strip the newlines from the end. See [#file-based-separators] for more examples.
You can even retrieve the `value` from a file by using the `param==@file` syntax. This would also effectively strip the newlines from the end. See [file based separators](#file-based-separators) for more examples.
```bash
$ http pie.dev/get text==@files/text.txt
@ -681,7 +698,40 @@ Other JSON types, however, are not allowed with `--form` or `--multipart`.
"about": {
"mission": "Make APIs simple and intuitive",
"homepage": "httpie.io",
}
"stars": 54000
},
"apps": [
"Terminal",
"Desktop",
"Web",
"Mobile"
]
}
}
```
#### Introduction
Lets start with a simple example, and build a simple search query:
```bash
$ http --offline --print=B pie.dev/post \
category=tools \
search[type]=id \
search[id]:=1
```
In the example above, the `search[type]` is an instruction for creating an object called `search`, and setting the `type` field of it to the given value (`"id"`).
Also note that, just as the regular syntax, you can use the `:=` operator to directly pass raw JSON values (e.g, numbers in the case above).
```json
{
"category": "tools",
"search": {
"id": 1,
"type": "id"
}
}
```
@ -739,7 +789,7 @@ $ http --offline --print=B pie.dev/post \
category=tools \
search[type]=platforms \
search[platforms][]=Terminal \
search[platforms][1]=Desktop \
search[platforms][1]=Desktop \
search[platforms][3]=Mobile
```
@ -821,6 +871,47 @@ $ http PUT pie.dev/put \
You can also apply the nesting to the items by referencing their index:
```bash
http --offline --print=B pie.dev/post \
[0][type]=platform [0][name]=terminal \
[1][type]=platform [1][name]=desktop
```
```json
[
{
"type": "platform",
"name": "terminal"
},
{
"type": "platform",
"name": "desktop"
}
]
```
##### Escaping behavior
Nested JSON syntax uses the same [escaping rules](#escaping-rules) as
the terminal. There are 3 special characters, and 1 special token that you can escape.
If you want to send a bracket as is, escape it with a backslash (`\`):
```bash
$ http --offline --print=B pie.dev/post \
'foo\[bar\]:=1' \
'baz[\[]:=2' \
'baz[\]]:=3'
```
```json
{
"baz": {
"[": 2,
"]": 3
},
"foo[bar]": 1
}
```
If you want to send the literal backslash character (`\`), escape it with another backslash:
@ -1247,12 +1338,18 @@ https -A bearer -a token pie.dev/bearer
For example:
```bash
$ cat ~/.netrc
machine pie.dev
login httpie
password test
```
```bash
$ http pie.dev/basic-auth/httpie/test
HTTP/1.1 200 OK
[...]
```
This can be disabled with the `--ignore-netrc` option:
```bash
@ -1297,9 +1394,11 @@ Here are a few picks:
$ http --follow pie.dev/redirect/3
```
With `307 Temporary Redirect` and `308 Permanent Redirect`, the method and the body of the original request
are reused to perform the redirected request. Otherwise, a body-less `GET` request is performed.
### Showing intermediary redirect responses
If you wish to see the intermediary requests/responses,
then use the `--all` option:
@ -1407,6 +1506,21 @@ path of the key file with `--cert-key`:
(The actually available set of protocols may vary depending on your OpenSSL installation.)
```bash
# Specify the vulnerable SSL v3 protocol to talk to an outdated server:
$ http --ssl=ssl3 https://vulnerable.example.org
```
### SSL ciphers
You can specify the available ciphers with `--ciphers`.
It should be a string in the [OpenSSL cipher list format](https://www.openssl.org/docs/man1.1.0/man1/ciphers.html).
```bash
$ http --ciphers=ECDHE-RSA-AES128-GCM-SHA256 https://pie.dev/get
```
Note: these cipher strings do not change the negotiated version of SSL or TLS, they only affect the list of available cipher suites.
To see the default cipher string, run `http --help` and see the `--ciphers` section under SSL.
## Output options
@ -1442,7 +1556,7 @@ be printed via several options:
```bash
$ http --print=Hh PUT pie.dev/put hello=world
```
```
#### Response meta
@ -1453,13 +1567,13 @@ be printed via several options:
```bash
$ http --meta pie.dev/delay/1
### Verbose output
`--verbose` can often be useful for debugging the request and generating documentation examples:
```bash
$ http --verbose PUT pie.dev/put hello=world
PUT /put HTTP/1.1
```
```console
Elapsed time: 1.099171542s
```
The [extra verbose `-vv` output](#extra-verbose-output) includes the meta section by default. You can also show it in combination with other parts of the exchange via [`--print=m`](#what-parts-of-the-http-exchange-should-be-printed). For example, here we print it together with the response headers:
```bash
$ http --print=hm pie.dev/get
@ -1471,9 +1585,34 @@ $ http --print=Hh PUT pie.dev/put hello=world
```
Connection: keep-alive
Content-Type: application/json
Please note that it also includes time spent on formatting the output, which adds a small penalty. Also, if the body is not part of the output, [we dont spend time downloading it](#conditional-body-download).
If you [use `--style` with one of the Pie themes](#colors-and-formatting), youll see the time information color-coded (green/yellow/orange/red) based on how long the exchange took.
### Verbose output
`--verbose` can often be useful for debugging the request and generating documentation examples:
```bash
$ http --verbose PUT pie.dev/put hello=world
PUT /put HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/json
Host: pie.dev
User-Agent: HTTPie/0.2.7dev
{
"hello": "world"
}
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 477
Content-Type: application/json
Date: Sun, 05 Aug 2012 00:25:23 GMT
Server: gunicorn/0.13.4
{
@ -1505,9 +1644,9 @@ Server: gunicorn/0.13.4
```bash
# There will be no output, even in case of an unexpected response status code:
$ http -qq --check-status pie.dev/post enjoy='the silence without warnings'
$ http -qq --check-status pie.dev/post enjoy='the silence without warnings'
```
### Update warnings
When there is a new release available for your platform (for example; if you installed HTTPie through `pip`, it will check the latest version on `PyPI`), HTTPie will regularly warn you about the new update (once a week). If you want to disable this behavior, you can set `disable_update_warnings` to `true` in your [config](#config) file.
@ -1531,6 +1670,10 @@ If youd like to silence warnings as well, use `-q` or `--quiet` twice:
Lets say that there is an API that returns the whole resource when it is updated, but you are only interested in the response headers to see the status code after an update:
```bash
$ http --headers PATCH pie.dev/patch name='New Name'
```
Since you are only printing the HTTP headers here, the connection to the server is closed as soon as all the response headers have been received.
Therefore, bandwidth and time isnt wasted downloading the body which you dont care about.
The response headers are downloaded always, even if they are not part of the output
@ -1543,14 +1686,6 @@ $ http --all --follow pie.dev/redirect/3
`--raw='data'`, and `@/file/path`.
### Redirected Input
## Raw request body
In addition to crafting structured [JSON](#json) and [forms](#forms) requests with the [request items](#request-items) syntax, you can provide a raw request body that will be sent without further processing.
These two approaches for specifying request data (i.e., structured and raw) cannot be combined.
There are three methods for passing raw request data: piping via `stdin`,
`--raw='data'`, and `@/file/path`.
The universal method for passing request data is through redirected `stdin`
(standard input)—piping.
@ -1927,6 +2062,8 @@ You can use the `--stream, -S` flag to make two things happen:
```bash
# Create a new session:
$ http --session=./session.json pie.dev/headers API-Token:123
```
```bash
# Inspect / edit the generated session file:
$ cat session.json
@ -2033,38 +2170,88 @@ $ http --session-read-only=./ro-session.json pie.dev/headers Custom-Header:orig-
$ http --session=./session.json pie.dev/cookies
```
},
```json
{
"password": null,
"cookies": {
"pie": "apple"
"username": null
}
}
```
To make a cookie domain _unbound_ (i.e., to make it available to all hosts, including throughout a cross-domain redirect chain), you can set the `domain` field to `null` in the session file:
```json
{
"cookies": [
{
"domain": null,
"name": "unbound-cookie",
"value": "send-me-to-any-host"
}
]
}
```
"cookies": {
```bash
"expires": null,
"path": "/",
"secure": false,
$ http --session=./session.json pie.dev/cookies
```
```json
}
{
"cookies": {
"unbound-cookie": "send-me-to-any-host"
}
}
```
}
```
### Cookie storage behavior
For example, a cookie set through the command line will overwrite a cookie of the same name stored in the session file.
There are three possible sources of persisted cookies within a session. They have the following storage priority: 1—response; 2—command line; 3—session file.
1. Receive a response with a `Set-Cookie` header:
```bash
$ http --session=./session.json pie.dev/cookie/set?foo=bar
```
2. Send a cookie specified on the command line as seen in [cookies](#cookies):
```bash
$ http --session=./session.json pie.dev/headers Cookie:foo=bar
Expired cookies are never stored.
If a cookie in a session file expires, it will be removed before sending a new request.
If the server expires an existing cookie, it will also be removed from the session file.
## Config
HTTPie uses a simple `config.json` file.
The file doesnt exist by default, but you can create it manually.
### Config file directory
```
3. Manually set cookie parameters in the session file:
```json
{
"cookies": {
"foo": {
"expires": null,
"path": "/",
"secure": false,
"value": "bar"
}
}
}
```
In summary:
- Cookies set via the CLI overwrite cookies of the same name inside session files.
- Server-sent `Set-Cookie` header cookies overwrite any pre-existing ones with the same name.
Cookie expiration handling:
- When the server expires an existing cookie, HTTPie removes it from the session file.
- When a cookie in a session file expires, HTTPie removes it before sending a new request.
### Upgrading sessions
HTTPie may introduce changes in the session file format. When HTTPie detects an obsolete format, it shows a warning. You can upgrade your session files using the following commands:
Upgrade all existing [named sessions](#named-sessions) inside the `sessions` subfolder of your [config directory](https://httpie.io/docs/cli/config-file-directory):
```bash
$ httpie cli sessions upgrade-all
Upgraded 'api_auth' @ 'pie.dev' to v3.1.0
@ -2073,16 +2260,55 @@ To set a cookie within a Session there are three options:
Upgrading individual sessions requires you to specify the session's hostname. That allows HTTPie to find the correct file in the case of name sessions. Additionally, it allows it to correctly bind cookies when upgrading with [`--bind-cookies`](#session-upgrade-options).
The config directory can be changed by setting the `$HTTPIE_CONFIG_DIR` environment variable:
Upgrade a single [named session](#named-sessions):
```bash
$ export HTTPIE_CONFIG_DIR=/tmp/httpie
$ http pie.dev/get
```
$ httpie cli sessions upgrade pie.dev api_auth
Upgraded 'api_auth' @ 'pie.dev' to v3.1.0
```
Upgrade a single [anonymous session](#anonymous-sessions) using a file path:
```bash
$ httpie cli sessions upgrade pie.dev ./session.json
Upgraded 'session.json' @ 'pie.dev' to v3.1.0
```
#### Session upgrade options
These flags are available for both `sessions upgrade` and `sessions upgrade-all`:
| Option | Description |
|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--bind-cookies` | Bind all previously [unbound cookies](#host-based-cookie-policy) to the sessions host ([context](https://github.com/httpie/httpie/security/advisories/GHSA-9w4w-cpc8-h2fq)). |
## Config
HTTPie uses a simple `config.json` file.
The file doesnt exist by default, but you can create it manually.
### Config file directory
To see the exact location for your installation, run `http --debug` and look for `config_dir` in the output.
The default location of the configuration file on most platforms is `$XDG_CONFIG_HOME/httpie/config.json` (defaulting to `~/.config/httpie/config.json`).
For backward compatibility, if the directory `~/.httpie` exists, the configuration file there will be used instead.
On Windows, the config file is located at `%APPDATA%\httpie\config.json`.
The config directory can be changed by setting the `$HTTPIE_CONFIG_DIR` environment variable:
```bash
$ export HTTPIE_CONFIG_DIR=/tmp/httpie
$ http pie.dev/get
```
### Configurable options
Currently, HTTPie offers a single configurable option:
### Configurable options
Currently, HTTPie offers a single configurable option:
#### `default_options`
An `Array` (by default empty) of default options that should be applied to every invocation of HTTPie.
@ -2095,7 +2321,7 @@ To see the exact location for your installation, run `http --debug` and look for
```json
{
{
"default_options": [
"--style=fruity"
]
}
@ -2134,7 +2360,7 @@ However, it is not recommended modifying the default behavior in a way that woul
*) echo 'Other Error!' ;;
esac
fi
fi
```
### Best practices
@ -2175,9 +2401,10 @@ And since theres neither data nor `EOF`, it will get stuck. So unless your
```
#### `httpie cli export-args`
#### `httpie plugins install`
`httpie cli export-args` command can expose the parser specification of `http`/`https` commands
For installing plugins from [PyPI](https://pypi.org/) or from local paths, `httpie plugins install`
(like an API definition) to outside tools so that they can use this to build better interactions
over them (e.g., offer auto-complete).
Available formats to export in include:
@ -2190,7 +2417,36 @@ For managing these plugins; starting with 3.0, we are offering a new plugin mana
```bash
$ httpie cli export-args | jq '"Program: " + .spec.name + ", Version: " + .version'
"Program: http, Version: 0.0.1a0"
```
#### `httpie cli plugins`
`plugins` interface is a very simple plugin manager for installing, listing and uninstalling HTTPie plugins.
In the past `pip` was used to install/uninstall plugins, but on some environments (e.g., brew installed
packages) it wasnt working properly. The new interface is a very simple overlay on top of `pip` to allow
plugin installations on every installation method.
By default, the plugins (and their missing dependencies) will be stored under the configuration directory,
but this can be modified through `plugins_dir` variable on the config.
##### `httpie cli plugins install`
For installing plugins from [PyPI](https://pypi.org/) or from local paths, `httpie cli plugins install`
can be used.
```bash
$ httpie cli plugins install httpie-plugin
Installing httpie-plugin...
Successfully installed httpie-plugin-1.0.2
```
> Tip: Generally HTTPie plugins start with `httpie-` prefix. Try searching for it on [PyPI](https://pypi.org/search/?q=httpie-)
> to find out all plugins from the community.
##### `httpie cli plugins list`
List all installed plugins.
```bash
$ httpie cli plugins list
@ -2201,13 +2457,13 @@ plugin installations on every installation method.
httpie_converter (1.0.0)
httpie_iterm_converter (httpie.plugins.converter.v1)
httpie_konsole_konverter (httpie.plugins.converter.v1)
httpie_konsole_konverter (httpie.plugins.converter.v1)
```
##### `httpie cli plugins upgrade`
For upgrading already installed plugins, use `httpie plugins upgrade`.
```bash
$ httpie cli plugins upgrade httpie-plugin
```
@ -2215,12 +2471,12 @@ Successfully installed httpie-plugin-1.0.2
Uninstall plugins from the isolated plugins directory. If the plugin is not installed
through `httpie cli plugins install`, it wont uninstall it.
through `httpie plugins install`, it wont uninstall it.
```bash
$ httpie cli plugins uninstall httpie-plugin
```
## Meta
### Interface design
@ -2230,21 +2486,21 @@ httpie_converter (1.0.0)
For example, compare this HTTP request:
```http
```http
POST /post HTTP/1.1
Host: pie.dev
X-API-Key: 123
User-Agent: Bacon/1.0
Content-Type: application/x-www-form-urlencoded
Content-Type: application/x-www-form-urlencoded
name=value&name2=value2
```
```
with the HTTPie command that sends it:
```bash
$ http -f POST pie.dev/post \
X-API-Key:123 \
X-API-Key:123 \
User-Agent:Bacon/1.0 \
name=value \
name2=value2
```
@ -2320,6 +2576,10 @@ Helpers to convert from other client tools:
See [CONTRIBUTING](https://github.com/httpie/httpie/blob/master/CONTRIBUTING.md).
### Security policy
See [github.com/httpie/httpie/security/policy](https://github.com/httpie/httpie/security/policy).
### Change log
See [CHANGELOG](https://github.com/httpie/httpie/blob/master/CHANGELOG.md).

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,9 @@ all
# Because we use HTML to hide them on the website.
exclude_rule 'MD002'
# MD007 Allow unordered list indentation
exclude_rule 'MD007'
# MD013 Line length
exclude_rule 'MD013'

View File

@ -12,16 +12,18 @@ You are looking at the HTTPie packaging documentation, where you will find valua
The overall release process starts simple:
1. Do the [PyPI](https://pypi.org/project/httpie/) publication.
2. Then, handle company-related tasks.
3. Finally, follow OS-specific steps, described in documents below, to send patches downstream.
1. Bump the version identifiers in the following places:
- `httpie/__init__.py`
- `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
Let's do the release on [PyPi](https://pypi.org/project/httpie/).
That is done quite easily by manually triggering the [release workflow](https://github.com/httpie/httpie/actions/workflows/release.yml).
## Then, company-specific tasks
## 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).
@ -36,10 +38,9 @@ A more complete state of deployment can be found on [repology](https://repology.
| -------------------------------------------: | -------------- |
| [Arch Linux, and derived](linux-arch/) | trusted person |
| [CentOS, RHEL, and derived](linux-centos/) | trusted person |
| [Debian, Ubuntu, and derived](linux-debian/) | trusted person |
| [Fedora](linux-fedora/) | trusted person |
| :construction: [Homebrew, Linuxbrew](brew/) | **HTTPie** |
| :construction: [MacPorts](mac-ports/) | **HTTPie** |
| [Debian, Ubuntu, and derived](linux-debian/) | **HTTPie** |
| [Homebrew, Linuxbrew](brew/) | **HTTPie** |
| [Snapcraft](snapcraft/) | **HTTPie** |
| [Windows — Chocolatey](windows-chocolatey/) | **HTTPie** |

View File

@ -13,21 +13,19 @@ We will discuss setting up the environment, installing development tools, instal
## 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/httpie/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
make brew-deps
# 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'
```console
$ brew bump-formula-pr httpie --version={TARGET_VERSION}
```
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.
Then, open a pull request with those changes to the [downstream file](https://github.com/Homebrew/homebrew-core/blob/master/Formula/httpie.rb).
which will bump the formula, and create a PR against the package index.
## Hacking
:construction: Work in progress.
Make your changes, test the formula through the [`Test Brew Package`](https://github.com/httpie/httpie/actions/workflows/test-package-mac-brew.yml) action
and then finally submit your patch to [`homebrew-core`](https://github.com/Homebrew/homebrew-core`)

View File

@ -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()

View File

@ -3,19 +3,18 @@ class Httpie < Formula
desc "User-friendly cURL replacement (command-line HTTP client)"
homepage "https://httpie.io/"
url "https://files.pythonhosted.org/packages/64/ee/7b158899655231322f13ecd313d1a0546efe8b9e75167ec8b7fd9ddf7952/httpie-3.0.0.tar.gz"
sha256 "e719711aadf1ecd33278033b96dfef7f4e9e341d3a5d1f166785ac4b7fbdee29"
url "https://files.pythonhosted.org/packages/32/85/bb095699be20cc98731261cb80884e9458178f8fef2a38273530ce77c0a5/httpie-3.1.0.tar.gz"
sha256 "2e4a2040b84a912e65c01fb34f7aafe88cad2a3af2da8c685ca65080f376feda"
license "BSD-3-Clause"
head "https://github.com/httpie/httpie.git", branch: "master"
bottle do
sha256 cellar: :any_skip_relocation, arm64_monterey: "83aab05ffbcd4c3baa6de6158d57ebdaa67c148bef8c872527d90bdaebff0504"
sha256 cellar: :any_skip_relocation, arm64_big_sur: "3c3a5c2458d0658e14b663495e115297c573aa3466d292f12d02c3ec13a24bdf"
sha256 cellar: :any_skip_relocation, monterey: "f860e7d3b77dca4928a2c5e10c4cbd50d792330dfb99f7d736ca0da9fb9dd0d0"
sha256 cellar: :any_skip_relocation, big_sur: "377b0643aa1f6d310ba4cfc70d66a94cc458213db8d134940d3b10a32defacf1"
sha256 cellar: :any_skip_relocation, catalina: "6d306c30f6f1d7a551d88415efe12b7c3f25d0602f3579dc632771a463f78fa5"
sha256 cellar: :any_skip_relocation, mojave: "f66b8cdff9cb7b44a84197c3e3d81d810f7ff8f2188998b977ccadfc7e2ec893"
sha256 cellar: :any_skip_relocation, x86_64_linux: "53f036b0114814c28982e8c022dcf494e7024de088641d7076fd73d12a45a0e9"
sha256 cellar: :any_skip_relocation, arm64_monterey: "9bb6e8c1ef5ba8b019ddedd7e908dd2174da695351aa9a238dfb28b0f57ef005"
sha256 cellar: :any_skip_relocation, arm64_big_sur: "47ffccd3241155d863e1b4f6259d538a34d42a0cdeed8152bda257ee607b51be"
sha256 cellar: :any_skip_relocation, monterey: "dc4a04cb05a9cd1bfa6a632a0e4a21975905954af54ece41f9050c52474267be"
sha256 cellar: :any_skip_relocation, big_sur: "ae469e37864e967e0fd99fba15a78e719dcb351b462f98f3843c78ed1473df6d"
sha256 cellar: :any_skip_relocation, catalina: "291a3eaecb2a2cc845c1652686a9a14b21053d7e3a7d0115245b2150ca2e199e"
sha256 cellar: :any_skip_relocation, x86_64_linux: "710836e27c44c8e3ad181d668f4a9f78c4cb4c355d7b148a397599a7cd42713d"
end
depends_on "python@3.10"
@ -26,8 +25,8 @@ class Httpie < Formula
end
resource "charset-normalizer" do
url "https://files.pythonhosted.org/packages/48/44/76b179e0d1afe6e6a91fd5661c284f60238987f3b42b676d141d01cd5b97/charset-normalizer-2.0.10.tar.gz"
sha256 "876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd"
url "https://files.pythonhosted.org/packages/56/31/7bcaf657fafb3c6db8c787a865434290b726653c912085fbd371e9b92e1c/charset-normalizer-2.0.12.tar.gz"
sha256 "2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"
end
resource "defusedxml" do
@ -36,8 +35,13 @@ class Httpie < Formula
end
resource "idna" do
url "https://files.pythonhosted.org/packages/cb/38/4c4d00ddfa48abe616d7e572e02a04273603db446975ab46bbcd36552005/idna-3.2.tar.gz"
sha256 "467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
url "https://files.pythonhosted.org/packages/62/08/e3fc7c8161090f742f504f40b1bccbfc544d4a4e09eb774bf40aafce5436/idna-3.3.tar.gz"
sha256 "9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
end
resource "multidict" do
url "https://files.pythonhosted.org/packages/fa/a7/71c253cdb8a1528802bac7503bf82fe674367e4055b09c28846fdfa4ab90/multidict-6.0.2.tar.gz"
sha256 "5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"
end
resource "Pygments" do
@ -65,20 +69,14 @@ class Httpie < Formula
sha256 "0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"
end
resource "multidict" do
url "https://files.pythonhosted.org/packages/8e/7c/e12a69795b7b7d5071614af2c691c97fbf16a2a513c66ec52dd7d0a115bb/multidict-5.2.0.tar.gz"
sha256 "0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce"
end
def install
virtualenv_install_with_resources
end
test do
# shell_output() already checks the status code
shell_output("#{bin}/httpie -v")
shell_output("#{bin}/https -v")
shell_output("#{bin}/http -v")
assert_match version.to_s, shell_output("#{bin}/httpie --version")
assert_match version.to_s, shell_output("#{bin}/https --version")
assert_match version.to_s, shell_output("#{bin}/http --version")
raw_url = "https://raw.githubusercontent.com/Homebrew/homebrew-core/HEAD/Formula/httpie.rb"
assert_match "PYTHONPATH", shell_output("#{bin}/http --ignore-stdin #{raw_url}")

6
docs/packaging/brew/update.sh Executable file
View 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

View File

@ -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.
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
Open a new bug on the Debian Bug Tracking System by sending an email:
The [`Release as Standalone Linux Binary`](https://github.com/httpie/httpie/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>`
- Subject: `httpie: Version XXX available`
- 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)):
```email
Package: httpie
Severity: normal
<MESSAGE>
```
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
and trigger the [`Update Index`](https://github.com/httpie/debian.httpie.io/actions/workflows/update-index.yml) action. It will automatically
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.

View File

@ -1,5 +1,5 @@
Name: httpie
Version: 2.6.0
Version: 3.1.0
Release: 1%{?dist}
Summary: A Curl-like tool for humans
@ -78,6 +78,25 @@ help2man %{buildroot}%{_bindir}/httpie > %{buildroot}%{_mandir}/man1/httpie.1
%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
- Update to 2.6.0
- Fixes: rhbz#2014022

View 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

View File

@ -13,7 +13,16 @@ We will discuss setting up the environment, installing development tools, instal
## 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/httpie/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

View File

@ -13,13 +13,18 @@ We will discuss setting up the environment, installing development tools, instal
## 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/httpie/actions/workflows/release-choco.yml) action
to push it to the `Chocolatey` store or use the CLI:
```bash
# Replace 2.5.0 with the correct version
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
```bash

View File

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

BIN
docs/stardust.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

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

598
extras/man/http.1 Normal file
View File

@ -0,0 +1,598 @@
.\" This file is auto-generated from the parser declaration in httpie/cli/definition.py by extras/scripts/generate_man_pages.py.
.TH http 1 "2022-05-06" "HTTPie 3.2.1" "HTTPie Manual"
.SH NAME
http
.SH SYNOPSIS
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
For finding out all available authentication types in your system, try:
$ 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. By default, the following
is used:
ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:ECDH+AESGCM:DH+AESGCM:ECDH+AES:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!eNULL:!MD5:!DSS
.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/httpie/issues

100
extras/man/httpie.1 Normal file
View 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 "2022-05-06" "HTTPie 3.2.1" "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

598
extras/man/https.1 Normal file
View File

@ -0,0 +1,598 @@
.\" This file is auto-generated from the parser declaration in httpie/cli/definition.py by extras/scripts/generate_man_pages.py.
.TH https 1 "2022-05-06" "HTTPie 3.2.1" "HTTPie Manual"
.SH NAME
https
.SH SYNOPSIS
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
For finding out all available authentication types in your system, try:
$ 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. By default, the following
is used:
ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:ECDH+AESGCM:DH+AESGCM:ECDH+AES:DH+AES:RSA+AESGCM:RSA+AES:!aNULL:!eNULL:!MD5:!DSS
.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/httpie/issues

View 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"]

View File

@ -0,0 +1,52 @@
# Standalone Linux Packages
![packaging.png](https://user-images.githubusercontent.com/47358913/159950478-2d090d1b-69b9-4914-a1b4-d3e3d8e25fe0.png)
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/httpie/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/httpie/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/httpie/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).

View 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()

View 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

View 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)

View File

@ -0,0 +1,5 @@
from httpie.__main__ import main
if __name__ == '__main__':
import sys
sys.exit(main())

View File

@ -0,0 +1,5 @@
from httpie.manager.__main__ import main
if __name__ == '__main__':
import sys
sys.exit(main())

View 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/benchmarks/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/httpie`
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`.

View File

@ -21,7 +21,7 @@ Examples:
$ python extras/profiling/benchmarks.py --fast
# 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
$ python extras/profiling/benchmarks.py --debug-single-value

View File

@ -0,0 +1,183 @@
import re
from contextlib import contextmanager
from pathlib import Path
from typing import Optional, Iterator, Iterable
import httpie
from httpie.cli.definition import options as core_options
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
from httpie.utils import split
# Escape certain characters so they are rendered properly on
# all terminals.
# https://man7.org/linux/man-pages/man7/groff_char.7.html
ESCAPE_MAP = {
'"': '\[dq]',
"'": '\[aq]',
'~': '\(ti',
'': "\(ga",
'\\': '\e',
}
ESCAPE_MAP = {ord(key): value for key, value in ESCAPE_MAP.items()}
EXTRAS_DIR = Path(__file__).parent.parent
MAN_PAGE_PATH = EXTRAS_DIR / 'man'
PROJECT_ROOT = EXTRAS_DIR.parent
OPTION_HIGHLIGHT_RE = re.compile(
OptionsHighlighter.highlights[0]
)
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
# 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()

View File

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

View File

@ -10,7 +10,8 @@ from urllib.parse import urlsplit
from requests.utils import get_netrc_auth
from .argtypes import (
AuthCredentials, KeyValueArgType, PARSED_DEFAULT_FORMAT_OPTIONS,
AuthCredentials, SSLCredentials, KeyValueArgType,
PARSED_DEFAULT_FORMAT_OPTIONS,
parse_auth,
parse_format_options,
)
@ -47,12 +48,39 @@ class HTTPieHelpFormatter(RawDescriptionHelpFormatter):
text = dedent(text).strip() + '\n\n'
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
# for raw args + parsed args and keep things immutable.
class BaseHTTPieArgumentParser(argparse.ArgumentParser):
def __init__(self, *args, formatter_class=HTTPieHelpFormatter, **kwargs):
super().__init__(*args, formatter_class=formatter_class, **kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.env = None
self.args = None
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)
super().__init__(*args, **kwargs)
super().__init__(*args, formatter_class=formatter_class, **kwargs)
# noinspection PyMethodOverriding
def parse_args(
@ -127,6 +155,7 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
namespace=None
) -> argparse.Namespace:
self.env = env
self.env.args = namespace = namespace or argparse.Namespace()
self.args, no_options = super().parse_known_args(args, namespace)
if self.args.debug:
self.args.traceback = True
@ -148,6 +177,7 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
self._parse_items()
self._process_url()
self._process_auth()
self._process_ssl_cert()
if self.args.raw is not None:
self._body_from_input(self.args.raw)
@ -230,9 +260,24 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
self.env.stdout_isatty = False
if self.args.quiet:
self.env.quiet = self.args.quiet
self.env.stderr = self.env.devnull
if not (self.args.output_file_specified and not self.args.download):
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):
# TODO: refactor & simplify this method.
@ -512,3 +557,57 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
for options_group in format_options:
parsed_options = parse_format_options(options_group, defaults=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)

View File

@ -130,16 +130,11 @@ class KeyValueArgType:
return tokens
class AuthCredentials(KeyValueArg):
"""Represents parsed credentials."""
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}: '
class PromptMixin:
def _prompt_password(self, prompt: str) -> str:
prompt_text = f'http: {prompt}: '
try:
self.value = self._getpass(prompt_text)
return self._getpass(prompt_text)
except (EOFError, KeyboardInterrupt):
sys.stderr.write('\n')
sys.exit(0)
@ -150,6 +145,26 @@ class AuthCredentials(KeyValueArg):
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):
"""A key-value arg type that parses credentials."""

View File

@ -9,6 +9,7 @@ URL_SCHEME_RE = re.compile(r'^[a-z][a-z0-9.+-]*://', re.IGNORECASE)
HTTP_POST = 'POST'
HTTP_GET = 'GET'
HTTP_OPTIONS = 'OPTIONS'
# Various separators used in args
SEPARATOR_HEADER = ':'
@ -90,13 +91,19 @@ OUTPUT_OPTIONS = frozenset({
})
# Pretty
class PrettyOptions(enum.Enum):
STDOUT_TTY_ONLY = enum.auto()
PRETTY_MAP = {
'all': ['format', 'colors'],
'colors': ['colors'],
'format': ['format'],
'none': []
}
PRETTY_STDOUT_TTY_ONLY = object()
PRETTY_STDOUT_TTY_ONLY = PrettyOptions.STDOUT_TTY_ONLY
DEFAULT_FORMAT_OPTIONS = [
@ -125,9 +132,3 @@ class RequestType(enum.Enum):
FORM = enum.auto()
MULTIPART = enum.auto()
JSON = enum.auto()
OPEN_BRACKET = '['
CLOSE_BRACKET = ']'
BACKSLASH = '\\'
HIGHLIGHTER = '^'

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,16 @@ class HTTPHeadersDict(CIMultiDict, BaseMultiDict):
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):
pass
@ -82,3 +92,7 @@ class MultipartRequestDataDict(MultiValueOrderedDict):
class RequestFilesDict(RequestDataDict):
pass
class NestedJSONArray(list):
"""Denotes a top-level JSON array."""

View File

@ -9,7 +9,14 @@ from typing import (
Type,
Union,
)
from httpie.cli.constants import OPEN_BRACKET, CLOSE_BRACKET, BACKSLASH, HIGHLIGHTER
from .dicts import NestedJSONArray
EMPTY_STRING = ''
HIGHLIGHTER = '^'
OPEN_BRACKET = '['
CLOSE_BRACKET = ']'
BACKSLASH = '\\'
class HTTPieSyntaxError(ValueError):
@ -30,7 +37,7 @@ class HTTPieSyntaxError(ValueError):
if self.token is not None:
lines.append(self.source)
lines.append(
' ' * (self.token.start)
' ' * self.token.start
+ HIGHLIGHTER * (self.token.end - self.token.start)
)
return '\n'.join(lines)
@ -50,8 +57,15 @@ class TokenKind(Enum):
return 'a ' + self.name.lower()
OPERATORS = {OPEN_BRACKET: TokenKind.LEFT_BRACKET, CLOSE_BRACKET: TokenKind.RIGHT_BRACKET}
OPERATORS = {
OPEN_BRACKET: TokenKind.LEFT_BRACKET,
CLOSE_BRACKET: TokenKind.RIGHT_BRACKET,
}
SPECIAL_CHARS = OPERATORS.keys() | {BACKSLASH}
LITERAL_TOKENS = [
TokenKind.TEXT,
TokenKind.NUMBER,
]
class Token(NamedTuple):
@ -88,18 +102,18 @@ def tokenize(source: str) -> Iterator[Token]:
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
kind = TokenKind.TEXT
if not backslashes:
for variation, kind in [
(int, TokenKind.NUMBER),
(check_escaped_int, TokenKind.TEXT),
]:
try:
value = variation(value)
except ValueError:
continue
else:
break
yield Token(
kind, value, start=cursor - (len(buffer) + backslashes), end=cursor
@ -171,8 +185,8 @@ class Path:
def parse(source: str) -> Iterator[Path]:
"""
start: literal? path*
start: root_path path*
root_path: (literal | index_path | append_path)
literal: TEXT | NUMBER
path:
@ -215,16 +229,47 @@ def parse(source: str) -> Iterator[Path]:
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)
def parse_root():
tokens = []
if not can_advance():
return Path(
PathAction.KEY,
EMPTY_STRING,
is_root=True
)
yield root
# (literal | index_path | append_path)?
token = expect(*LITERAL_TOKENS, TokenKind.LEFT_BRACKET)
tokens.append(token)
if token.kind in LITERAL_TOKENS:
action = PathAction.KEY
value = str(token.value)
elif token.kind is TokenKind.LEFT_BRACKET:
token = expect(TokenKind.NUMBER, TokenKind.RIGHT_BRACKET)
tokens.append(token)
if token.kind is TokenKind.NUMBER:
action = PathAction.INDEX
value = token.value
tokens.append(expect(TokenKind.RIGHT_BRACKET))
elif token.kind is TokenKind.RIGHT_BRACKET:
action = PathAction.APPEND
value = None
else:
assert_cant_happen()
else:
assert_cant_happen()
return Path(
action,
value,
tokens=tokens,
is_root=True
)
yield parse_root()
# path*
while can_advance():
path_tokens = []
path_tokens.append(expect(TokenKind.LEFT_BRACKET))
@ -296,6 +341,10 @@ def interpret(context: Any, key: str, value: Any) -> Any:
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:
@ -337,8 +386,19 @@ def interpret(context: Any, key: str, value: Any) -> Any:
return context
def wrap_with_dict(context):
if context is None:
return {}
elif isinstance(context, list):
return {EMPTY_STRING: NestedJSONArray(context)}
else:
assert isinstance(context, dict)
return context
def interpret_nested_json(pairs):
context = {}
context = None
for key, value in pairs:
interpret(context, key, value)
return context
context = interpret(context, key, value)
return wrap_with_dict(context)

249
httpie/cli/options.py Normal file
View 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
)

View File

@ -4,20 +4,43 @@ from typing import Any, Callable, Generic, Iterator, Iterable, Optional, TypeVar
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]):
def __init__(
self,
*args,
getter: Callable[[], Iterable[T]],
help_formatter: Optional[Callable[[T], str]] = None,
help_formatter: Optional[Callable[[T, bool], str]] = None,
sort: bool = False,
cache: bool = True,
isolation_mode: bool = False,
**kwargs
) -> None:
self.getter = getter
self.help_formatter = help_formatter
self.sort = sort
self.cache = cache
self.isolation_mode = isolation_mode
self._help: Optional[str] = None
self._obj: Optional[Iterable[T]] = None
super().__init__(*args, **kwargs)
@ -33,7 +56,10 @@ class LazyChoices(argparse.Action, Generic[T]):
@property
def help(self) -> str:
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
@help.setter

View File

@ -13,12 +13,14 @@ import urllib3
from . import __version__
from .adapters import HTTPieHTTPAdapter
from .context import Environment
from .cli.dicts import HTTPHeadersDict
from .cli.constants import HTTP_OPTIONS
from .cli.nested_json import EMPTY_STRING
from .cli.dicts import HTTPHeadersDict, NestedJSONArray
from .encoding import UTF8
from .models import RequestsMessage
from .plugins.registry import plugin_manager
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 (
compress_request, prepare_request_body,
get_multipart_data_and_content_type,
@ -33,6 +35,8 @@ JSON_CONTENT_TYPE = 'application/json'
JSON_ACCEPT = f'{JSON_CONTENT_TYPE}, */*;q=0.5'
DEFAULT_UA = f'HTTPie/{__version__}'
IGNORE_CONTENT_LENGTH_METHODS = frozenset([HTTP_OPTIONS])
def collect_messages(
env: Environment,
@ -43,6 +47,7 @@ def collect_messages(
httpie_session_headers = None
if args.session or args.session_read_only:
httpie_session = get_httpie_session(
env=env,
config_dir=env.config.directory,
session_name=args.session or args.session_read_only,
host=args.headers.get('Host'),
@ -83,7 +88,7 @@ def collect_messages(
request = requests.Request(**request_kwargs)
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:
prepared_request.url = ensure_path_as_is(
orig_url=args.url,
@ -129,10 +134,7 @@ def collect_messages(
if httpie_session:
if httpie_session.is_new() or not args.session_read_only:
httpie_session.cookies = requests_session.cookies
httpie_session.remove_cookies(
# TODO: take path & domain into account?
cookie['name'] for cookie in expired_cookies
)
httpie_session.remove_cookies(expired_cookies)
httpie_session.save()
@ -201,9 +203,30 @@ def finalize_headers(headers: HTTPHeadersDict) -> HTTPHeadersDict:
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(
prepared_request: requests.PreparedRequest,
original_headers: HTTPHeadersDict
original_headers: HTTPHeadersDict,
prepared_request: requests.PreparedRequest
) -> None:
"""Update the given `prepared_request`'s headers with the original
ones. This allows the requests to be prepared as usual, and then later
@ -261,7 +284,14 @@ def make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict:
if args.cert:
cert = args.cert
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 {
'proxies': {p.key: p.value for p in args.proxy},
'stream': True,
@ -280,7 +310,8 @@ def json_dict_to_request_body(data: Dict[str, Any]) -> str:
# item in the object, with an en empty key.
if len(data) == 1:
[(key, value)] = data.items()
if key == '' and isinstance(value, list):
if isinstance(value, NestedJSONArray):
assert key == EMPTY_STRING
data = value
if data:

View File

@ -1,9 +1,21 @@
import sys
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_frozen = getattr(sys, 'frozen', False)
MIN_SUPPORTED_PY_VERSION = (3, 7)
MAX_SUPPORTED_PY_VERSION = (3, 11)
try:
from functools import cached_property

View File

@ -1,7 +1,7 @@
import json
import os
from pathlib import Path
from typing import Union
from typing import Any, Dict, Union
from . import __version__
from .compat import is_windows
@ -62,6 +62,21 @@ class ConfigFileError(Exception):
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):
name = None
helpurl = None
@ -77,26 +92,25 @@ class BaseConfigDict(dict):
def is_new(self) -> bool:
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):
config_type = type(self).__name__.lower()
try:
with self.path.open(encoding=UTF8) as f:
try:
data = json.load(f)
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}')
data = read_raw_config(config_type, self.path)
if data is not None:
data = self.pre_process_data(data)
self.update(data)
def save(self):
self['__meta__'] = {
'httpie': __version__
}
def save(self, *, bump_version: bool = False):
self.setdefault('__meta__', {})
if bump_version or 'httpie' not in self['__meta__']:
self['__meta__']['httpie'] = __version__
if self.helpurl:
self['__meta__']['help'] = self.helpurl
@ -106,13 +120,19 @@ class BaseConfigDict(dict):
self.ensure_directory()
json_string = json.dumps(
obj=self,
obj=self.post_process_data(self),
indent=4,
sort_keys=True,
ensure_ascii=True,
)
self.path.write_text(json_string + '\n', encoding=UTF8)
@property
def version(self):
return self.get(
'__meta__', {}
).get('httpie', __version__)
class Config(BaseConfigDict):
FILENAME = 'config.json'
@ -129,6 +149,24 @@ class Config(BaseConfigDict):
def default_options(self) -> list:
return self['default_options']
def _configured_path(self, config_option: str, default: str) -> None:
return Path(
self.get(config_option, self.directory / default)
).expanduser().resolve()
@property
def plugins_dir(self) -> Path:
return Path(self.get('plugins_dir', self.directory / 'plugins')).resolve()
return self._configured_path('plugins_dir', 'plugins')
@property
def version_info_file(self) -> Path:
return self._configured_path('version_info_file', 'version_info.json')
@property
def developer_mode(self) -> bool:
"""This is a special setting for the development environment. It is
different from the --debug mode in the terms that it might change
the behavior for certain parameters (e.g updater system) that
we usually ignore."""
return self.get('developer_mode')

View File

@ -1,8 +1,11 @@
import argparse
import sys
import os
import warnings
from contextlib import contextmanager
from pathlib import Path
from typing import Iterator, IO, Optional
from typing import Iterator, IO, Optional, TYPE_CHECKING
from enum import Enum
try:
@ -10,11 +13,34 @@ try:
except ImportError:
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 .encoding import UTF8
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:
@ -27,6 +53,7 @@ class Environment:
is used by the test suite to simulate various scenarios.
"""
args = argparse.Namespace()
is_windows: bool = is_windows
config_dir: Path = DEFAULT_CONFIG_DIR
stdin: Optional[IO] = sys.stdin # `None` when closed fd (#791)
@ -39,6 +66,10 @@ class Environment:
stderr_isatty: bool = stderr.isatty()
colors = 256
program_name: str = 'http'
# Whether to show progress bars / status spinners etc.
show_displays: bool = True
if not is_windows:
if curses:
try:
@ -87,6 +118,8 @@ class Environment:
self.stdout_encoding = getattr(
actual_stdout, 'encoding', None) or UTF8
self.quiet = kwargs.pop('quiet', 0)
def __str__(self):
defaults = dict(type(self).__dict__)
actual = dict(defaults)
@ -134,6 +167,51 @@ class Environment:
self.stdout = original_stdout
self.stderr = original_stderr
def log_error(self, msg, level='error'):
assert level in ['error', 'warning']
self._orig_stderr.write(f'\n{self.program_name}: {level}: {msg}\n\n')
def log_error(self, msg: str, level: LogLevel = LogLevel.ERROR) -> None:
if self.stdout_isatty and self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[level]:
stderr = self.stderr # Not directly /dev/null, since stderr might be mocked
else:
stderr = self._orig_stderr
rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty())
rich_console.print(
f'\n{self.program_name}: {level}: {msg}\n\n',
style=LOG_LEVEL_COLORS[level],
markup=False,
highlight=False,
soft_wrap=True
)
def apply_warnings_filter(self) -> None:
if self.quiet >= 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
View 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)

View File

@ -13,16 +13,19 @@ from . import __version__ as httpie_version
from .cli.constants import OUT_REQ_BODY
from .cli.nested_json import HTTPieSyntaxError
from .client import collect_messages
from .context import Environment
from .context import Environment, LogLevel
from .downloads import Downloader
from .models import (
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 .status import ExitStatus, http_status_to_exit_status
from .utils import unwrap_context
from .internal.update_warnings import check_updates
from .internal.daemon_runner import is_daemon_mode, run_daemon_task
# noinspection PyDefaultArgument
@ -30,14 +33,19 @@ def raw_main(
parser: argparse.ArgumentParser,
main_program: Callable[[argparse.Namespace, Environment], ExitStatus],
args: List[Union[str, bytes]] = sys.argv,
env: Environment = Environment()
env: Environment = Environment(),
use_default_options: bool = True,
) -> ExitStatus:
program_name, *args = args
env.program_name = os.path.basename(program_name)
args = decode_raw_args(args, env.stdin_encoding)
if is_daemon_mode(args):
return run_daemon_task(env, args)
plugin_manager.load_installed_plugins(env.config.plugins_dir)
if env.config.default_options:
if use_default_options and env.config.default_options:
args = env.config.default_options + args
include_debug_info = '--debug' in args
@ -87,6 +95,7 @@ def raw_main(
raise
exit_status = ExitStatus.ERROR
else:
check_updates(env)
try:
exit_status = main_program(
args=parsed_args,
@ -168,6 +177,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
downloader = None
initial_request: Optional[requests.PreparedRequest] = None
final_response: Optional[requests.Response] = None
processing_options = ProcessingOptions.from_raw_args(args)
def separate():
getattr(env.stdout, 'buffer', env.stdout).write(MESSAGE_SEPARATOR_BYTES)
@ -182,17 +192,17 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
and chunk
)
if should_pipe_to_stdout:
msg = requests.PreparedRequest()
msg.is_body_upload_chunk = True
msg.body = chunk
msg.headers = initial_request.headers
msg_output_options = OutputOptions.from_message(msg, body=True, headers=False)
write_message(requests_message=msg, env=env, args=args, output_options=msg_output_options)
return write_raw_data(
env,
chunk,
processing_options=processing_options,
headers=initial_request.headers
)
try:
if args.download:
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)
messages = collect_messages(env, args=args,
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:
exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)
if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet == 1):
env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level='warning')
write_message(requests_message=message, env=env, args=args, output_options=output_options._replace(
body=do_write_body
))
env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level=LogLevel.WARNING)
write_message(
requests_message=message,
env=env,
output_options=output_options._replace(
body=do_write_body
),
processing_options=processing_options
)
prev_with_body = output_options.body
# Cleanup

View File

@ -5,10 +5,8 @@ Download mode implementation.
import mimetypes
import os
import re
import sys
import threading
from mailbox import Message
from time import sleep, monotonic
from time import monotonic
from typing import IO, Optional, Tuple
from urllib.parse import urlsplit
@ -16,22 +14,11 @@ import requests
from .models import HTTPResponse, OutputOptions
from .output.streams import RawStream
from .utils import humanize_bytes
from .context import Environment
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):
pass
@ -176,9 +163,9 @@ class Downloader:
def __init__(
self,
env: Environment,
output_file: IO = None,
resume: bool = False,
progress_file: IO = sys.stderr
resume: bool = False
):
"""
:param resume: Should the download resume if partial download
@ -191,14 +178,10 @@ class Downloader:
"""
self.finished = False
self.status = DownloadStatus()
self.status = DownloadStatus(env=env)
self._output_file = output_file
self._resume = resume
self._resumed_from = 0
self._progress_reporter = ProgressReporterThread(
status=self.status,
output=progress_file
)
def pre_request(self, request_headers: dict):
"""Called just before the HTTP request is sent.
@ -261,11 +244,6 @@ class Downloader:
except OSError:
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)
stream = RawStream(
msg=HTTPResponse(final_response),
@ -273,11 +251,11 @@ class Downloader:
on_body_chunk_downloaded=self.chunk_downloaded,
)
self._progress_reporter.output.write(
f'Downloading {humanize_bytes(total_size) + " " if total_size is not None else ""}'
f'to "{self._output_file.name}"\n'
self.status.started(
output_file=self._output_file,
resumed_from=self._resumed_from,
total_size=total_size
)
self._progress_reporter.start()
return stream, self._output_file
@ -287,7 +265,7 @@ class Downloader:
self.status.finished()
def failed(self):
self._progress_reporter.stop()
self.status.terminate()
@property
def interrupted(self) -> bool:
@ -329,127 +307,71 @@ class Downloader:
class DownloadStatus:
"""Holds details about the download status."""
def __init__(self):
def __init__(self, env):
self.env = env
self.downloaded = 0
self.total_size = None
self.resumed_from = 0
self.time_started = 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
self.total_size = total_size
self.downloaded = self.resumed_from = resumed_from
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):
assert self.time_finished is None
self.downloaded += size
self.display.update(size)
@property
def has_finished(self):
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):
assert self.time_started is not None
assert self.time_finished is None
self.time_finished = monotonic()
if hasattr(self, 'display'):
self.display.stop(self.time_spent)
class ProgressReporterThread(threading.Thread):
"""
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()
def terminate(self):
if hasattr(self, 'display'):
self.display.stop(self.time_spent)

View 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'

View File

View 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
View 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)

View 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)

View File

View 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

View 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'])

View File

@ -37,7 +37,8 @@ def main(args: List[Union[str, bytes]] = sys.argv, env: Environment = Environmen
parser=parser,
main_program=main_program,
args=args,
env=env
env=env,
use_default_options=False,
)
except argparse.ArgumentError:
program_args = args[1:]

View File

@ -1,39 +1,94 @@
from textwrap import dedent
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__
CLI_SESSION_UPGRADE_FLAGS = [
{
'flags': ['--bind-cookies'],
'action': 'store_true',
'default': False,
'help': 'Bind domainless cookies to the host that session belongs.'
}
]
COMMANDS = {
'plugins': {
'help': 'Manage HTTPie plugins.',
'install': [
'Install the given targets from PyPI '
'or from a local paths.',
'cli': {
'help': 'Manage HTTPie for Terminal',
'export-args': [
'Export available options for the CLI',
{
'dest': 'targets',
'nargs': '+',
'help': 'targets to install'
'flags': ['-f', '--format'],
'choices': ['json'],
'help': 'Format to export in.',
'default': 'json'
}
],
'upgrade': [
'Upgrade the given plugins',
{
'dest': 'targets',
'nargs': '+',
'help': 'targets to upgrade'
}
'check-updates': [
'Check for updates'
],
'uninstall': [
'Uninstall the given HTTPie plugins.',
{
'dest': 'targets',
'nargs': '+',
'help': 'targets to install'
}
],
'list': [
'List all installed HTTPie plugins.'
],
},
'sessions': {
'help': 'Manage HTTPie 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.',
{
'dest': 'hostname',
'metavar': 'HOSTNAME',
'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}'
def generate_subparsers(root, parent_parser, definitions):
def generate_subparsers(root, parent_parser, definitions, spec):
action_dest = '_'.join(parent_parser.prog.split()[1:] + ['action'])
actions = parent_parser.add_subparsers(
dest=action_dest
)
for command, properties in definitions.items():
is_subparser = isinstance(properties, dict)
properties = properties.copy()
descr = properties.pop('help', None) if is_subparser else properties.pop(0)
command_parser = actions.add_parser(command, description=descr)
command_parser.root = root
if is_subparser:
generate_subparsers(root, command_parser, properties)
generate_subparsers(root, command_parser, properties, spec)
continue
group = spec.add_group(parent_parser.prog + ' ' + command, description=descr)
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(
@ -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)

69
httpie/manager/compat.py Normal file
View File

@ -0,0 +1,69 @@
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:
import subprocess
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)

View File

@ -1,9 +1,10 @@
import argparse
from typing import Optional
from httpie.context import Environment
from httpie.manager.plugins import PluginInstaller
from httpie.status import ExitStatus
from httpie.manager.cli import missing_subcommand, parser
from httpie.manager.tasks import CLI_TASKS
MSG_COMMAND_CONFUSION = '''\
This command is only for managing HTTPie plugins.
@ -22,12 +23,20 @@ MSG_NAKED_INVOCATION = f'''\
'''.rstrip("\n").format(args='POST pie.dev/post hello=world')
def dispatch_cli_task(env: Environment, action: Optional[str], args: argparse.Namespace) -> ExitStatus:
if action is None:
parser.error(missing_subcommand('cli'))
return CLI_TASKS[action](env, args)
def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
if args.action is None:
parser.error(MSG_NAKED_INVOCATION)
if args.action == 'plugins':
plugins = PluginInstaller(env, debug=args.debug)
return plugins.run(args.plugins_action, args)
return dispatch_cli_task(env, args.action, args)
elif args.action == 'cli':
return dispatch_cli_task(env, args.cli_action, args)
return ExitStatus.SUCCESS

View File

@ -0,0 +1,11 @@
from httpie.manager.tasks.sessions import cli_sessions
from httpie.manager.tasks.export_args import cli_export_args
from httpie.manager.tasks.plugins import cli_plugins
from httpie.manager.tasks.check_updates import cli_check_updates
CLI_TASKS = {
'sessions': cli_sessions,
'export-args': cli_export_args,
'plugins': cli_plugins,
'check-updates': cli_check_updates
}

View File

@ -0,0 +1,10 @@
import argparse
from httpie.context import Environment
from httpie.status import ExitStatus
from httpie.internal.update_warnings import fetch_updates, get_update_status
def cli_check_updates(env: Environment, args: argparse.Namespace) -> ExitStatus:
fetch_updates(env, lazy=False)
env.stdout.write(get_update_status(env))
return ExitStatus.SUCCESS

View File

@ -0,0 +1,27 @@
import argparse
import json
from httpie.cli.definition import options
from httpie.cli.options import to_data
from httpie.output.writer import write_raw_data
from httpie.status import ExitStatus
from httpie.context import Environment
FORMAT_TO_CONTENT_TYPE = {
'json': 'application/json'
}
def cli_export_args(env: Environment, args: argparse.Namespace) -> ExitStatus:
if args.format == 'json':
data = json.dumps(to_data(options))
else:
raise NotImplementedError(f'Unexpected format value: {args.format}')
write_raw_data(
env,
data,
stream_kwargs={'mime_overwrite': FORMAT_TO_CONTENT_TYPE[args.format]},
)
return ExitStatus.SUCCESS

View File

@ -1,20 +1,19 @@
import argparse
import os
import subprocess
import sys
import textwrap
import re
import shutil
from collections import defaultdict
from contextlib import suppress
from pathlib import Path
from typing import Tuple, Optional, List
from typing import List, Optional, Tuple
from httpie.manager.compat import PipError, run_pip
from httpie.manager.cli import parser, missing_subcommand
from httpie.compat import importlib_metadata, get_dist_name
from httpie.compat import get_dist_name, importlib_metadata
from httpie.context import Environment
from httpie.status import ExitStatus
from httpie.utils import as_site
from httpie.utils import get_site_paths
PEP_503 = re.compile(r"[-_.]+")
@ -58,46 +57,37 @@ class PluginInstaller:
self.env.stderr.write(message + '\n')
return ExitStatus.ERROR
def pip(self, *args, **kwargs) -> subprocess.CompletedProcess:
options = {
'check': True,
'shell': False,
'stdout': self.env.stdout,
'stderr': subprocess.PIPE,
}
options.update(kwargs)
cmd = [sys.executable, '-m', 'pip', *args]
return subprocess.run(
cmd,
**options
)
def _install(self, targets: List[str], mode='install', **process_options) -> Tuple[
Optional[bytes], ExitStatus
def _install(self, targets: List[str], mode='install') -> Tuple[
bytes, ExitStatus
]:
pip_args = [
'install',
'--prefer-binary',
f'--prefix={self.dir}',
'--no-warn-script-location',
]
if mode == 'upgrade':
pip_args.append('--upgrade')
pip_args.extend(targets)
try:
process = self.pip(
*pip_args,
*targets,
**process_options,
)
except subprocess.CalledProcessError as error:
stdout = run_pip(pip_args)
except PipError as pip_error:
error = pip_error
stdout = pip_error.stdout
else:
error = None
self.env.stdout.write(stdout.decode())
if error:
reason = None
if error.stderr:
stderr = error.stderr.decode()
if self.debug:
self.env.stderr.write('Command failed: ')
self.env.stderr.write(' '.join(error.cmd) + '\n')
self.env.stderr.write('pip ' + ' '.join(pip_args) + '\n')
self.env.stderr.write(textwrap.indent(' ', stderr))
last_line = stderr.strip().splitlines()[-1]
@ -108,7 +98,6 @@ class PluginInstaller:
stdout = error.stdout
exit_status = self.fail(mode, ', '.join(targets), reason)
else:
stdout = process.stdout
exit_status = ExitStatus.SUCCESS
return stdout, exit_status
@ -124,10 +113,11 @@ class PluginInstaller:
# existing metadata for old versions manually.
# [0]: https://github.com/pypa/pip/issues/10727
result_deps = defaultdict(list)
for child in as_site(self.dir).iterdir():
if child.suffix in {'.dist-info', '.egg-info'}:
name, _, version = child.stem.rpartition('-')
result_deps[name].append((version, child))
for site_dir in get_site_paths(self.dir):
for child in site_dir.iterdir():
if child.suffix in {'.dist-info', '.egg-info'}:
name, _, version = child.stem.rpartition('-')
result_deps[name].append((version, child))
for target in targets:
name, _, version = target.rpartition('-')
@ -145,15 +135,12 @@ class PluginInstaller:
raw_stdout, exit_status = self._install(
targets,
mode='upgrade',
stdout=subprocess.PIPE
mode='upgrade'
)
if not raw_stdout:
return exit_status
stdout = raw_stdout.decode()
self.env.stdout.write(stdout)
installation_line = stdout.splitlines()[-1]
if installation_line.startswith('Successfully installed'):
self._clear_metadata(installation_line.split()[2:])
@ -178,7 +165,7 @@ class PluginInstaller:
return self.fail('uninstall', target, 'couldn\'t locate the package')
# TODO: Consider handling failures here (e.g if it fails,
# just rever the operation and leave the site-packages
# just revert the operation and leave the site-packages
# in a proper shape).
for file in files:
with suppress(FileNotFoundError):
@ -248,3 +235,14 @@ class PluginInstaller:
status = self.list()
return status or ExitStatus.SUCCESS
def cli_plugins(env: Environment, args: argparse.Namespace) -> ExitStatus:
plugins = PluginInstaller(env, debug=args.debug)
try:
action = args.cli_plugins_action
except AttributeError:
action = args.plugins_action
return plugins.run(action, args)

View File

@ -0,0 +1,86 @@
import argparse
from httpie.sessions import SESSIONS_DIR_NAME, get_httpie_session
from httpie.status import ExitStatus
from httpie.context import Environment
from httpie.legacy import v3_1_0_session_cookie_format, v3_2_0_session_header_format
from httpie.manager.cli import missing_subcommand, parser
from httpie.utils import is_version_greater
FIXERS_TO_VERSIONS = {
'3.1.0': v3_1_0_session_cookie_format.fix_layout,
'3.2.0': v3_2_0_session_header_format.fix_layout,
}
def cli_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus:
action = args.cli_sessions_action
if action is None:
parser.error(missing_subcommand('cli', 'sessions'))
if action == 'upgrade':
return cli_upgrade_session(env, args)
elif action == 'upgrade-all':
return cli_upgrade_all_sessions(env, args)
else:
raise ValueError(f'Unexpected action: {action}')
def upgrade_session(env: Environment, args: argparse.Namespace, hostname: str, session_name: str):
session = get_httpie_session(
env=env,
config_dir=env.config.directory,
session_name=session_name,
host=hostname,
url=hostname,
suppress_legacy_warnings=True
)
session_name = session.path.stem
if session.is_new():
env.log_error(f'{session_name!r} @ {hostname!r} does not exist.')
return ExitStatus.ERROR
fixers = [
fixer
for version, fixer in FIXERS_TO_VERSIONS.items()
if is_version_greater(version, session.version)
]
if len(fixers) == 0:
env.stdout.write(f'{session_name!r} @ {hostname!r} is already up to date.\n')
return ExitStatus.SUCCESS
for fixer in fixers:
fixer(session, hostname, args)
session.save(bump_version=True)
env.stdout.write(f'Upgraded {session_name!r} @ {hostname!r} to v{session.version}\n')
return ExitStatus.SUCCESS
def cli_upgrade_session(env: Environment, args: argparse.Namespace) -> ExitStatus:
return upgrade_session(
env,
args=args,
hostname=args.hostname,
session_name=args.session
)
def cli_upgrade_all_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus:
session_dir_path = env.config_dir / SESSIONS_DIR_NAME
status = ExitStatus.SUCCESS
for host_path in session_dir_path.iterdir():
hostname = host_path.name
for session_path in host_path.glob("*.json"):
session_name = session_path.stem
status |= upgrade_session(
env,
args=args,
hostname=hostname,
session_name=session_name
)
return status

View File

@ -71,7 +71,11 @@ class HTTPResponse(HTTPMessage):
@property
def headers(self):
try:
raw_version = self._orig.raw._original_response.version
raw = self._orig.raw
if getattr(raw, '_original_response', None):
raw_version = raw._original_response.version
else:
raw_version = raw.version
except AttributeError:
# Assume HTTP/1.1
raw_version = 11
@ -79,7 +83,7 @@ class HTTPResponse(HTTPMessage):
9: '0.9',
10: '1.0',
11: '1.1',
20: '2',
20: '2.0',
}[raw_version]
original = self._orig

View File

@ -17,14 +17,15 @@ from pygments.util import ClassNotFound
from ..lexers.json import EnhancedJsonLexer
from ..lexers.metadata import MetadataLexer
from ..ui.palette import SHADE_NAMES, get_color
from ..ui.palette import AUTO_STYLE, SHADE_TO_PIE_STYLE, PieColor, ColorString, get_color
from ...context import Environment
from ...plugins import FormatterPlugin
AUTO_STYLE = 'auto' # Follows terminal ANSI color styles
DEFAULT_STYLE = AUTO_STYLE
SOLARIZED_STYLE = 'solarized' # Bundled here
PYGMENTS_BOLD = ColorString('bold')
PYGMENTS_ITALIC = ColorString('italic')
BUNDLED_STYLES = {
SOLARIZED_STYLE,
@ -33,7 +34,7 @@ BUNDLED_STYLES = {
def get_available_styles():
return BUNDLED_STYLES | set(pygments.styles.get_all_styles())
return sorted(BUNDLED_STYLES | set(pygments.styles.get_all_styles()))
class ColorFormatter(FormatterPlugin):
@ -254,11 +255,11 @@ class Solarized256Style(pygments.style.Style):
pygments.token.Comment.Preproc: GREEN,
pygments.token.Comment.Special: GREEN,
pygments.token.Generic.Deleted: CYAN,
pygments.token.Generic.Emph: 'italic',
pygments.token.Generic.Emph: PYGMENTS_ITALIC,
pygments.token.Generic.Error: RED,
pygments.token.Generic.Heading: ORANGE,
pygments.token.Generic.Inserted: GREEN,
pygments.token.Generic.Strong: 'bold',
pygments.token.Generic.Strong: PYGMENTS_BOLD,
pygments.token.Generic.Subheading: ORANGE,
pygments.token.Token: BASE1,
pygments.token.Token.Other: ORANGE,
@ -267,86 +268,86 @@ class Solarized256Style(pygments.style.Style):
PIE_HEADER_STYLE = {
# HTTP line / Headers / Etc.
pygments.token.Name.Namespace: 'bold primary',
pygments.token.Keyword.Reserved: 'bold grey',
pygments.token.Operator: 'bold grey',
pygments.token.Number: 'bold grey',
pygments.token.Name.Function.Magic: 'bold green',
pygments.token.Name.Exception: 'bold green',
pygments.token.Name.Attribute: 'blue',
pygments.token.String: 'primary',
pygments.token.Name.Namespace: PYGMENTS_BOLD | PieColor.PRIMARY,
pygments.token.Keyword.Reserved: PYGMENTS_BOLD | PieColor.GREY,
pygments.token.Operator: PYGMENTS_BOLD | PieColor.GREY,
pygments.token.Number: PYGMENTS_BOLD | PieColor.GREY,
pygments.token.Name.Function.Magic: PYGMENTS_BOLD | PieColor.GREEN,
pygments.token.Name.Exception: PYGMENTS_BOLD | PieColor.GREEN,
pygments.token.Name.Attribute: PieColor.BLUE,
pygments.token.String: PieColor.PRIMARY,
# HTTP Methods
pygments.token.Name.Function: 'bold grey',
pygments.token.Name.Function.HTTP.GET: 'bold green',
pygments.token.Name.Function.HTTP.HEAD: 'bold green',
pygments.token.Name.Function.HTTP.POST: 'bold yellow',
pygments.token.Name.Function.HTTP.PUT: 'bold orange',
pygments.token.Name.Function.HTTP.PATCH: 'bold orange',
pygments.token.Name.Function.HTTP.DELETE: 'bold red',
pygments.token.Name.Function: PYGMENTS_BOLD | PieColor.GREY,
pygments.token.Name.Function.HTTP.GET: PYGMENTS_BOLD | PieColor.GREEN,
pygments.token.Name.Function.HTTP.HEAD: PYGMENTS_BOLD | PieColor.GREEN,
pygments.token.Name.Function.HTTP.POST: PYGMENTS_BOLD | PieColor.YELLOW,
pygments.token.Name.Function.HTTP.PUT: PYGMENTS_BOLD | PieColor.ORANGE,
pygments.token.Name.Function.HTTP.PATCH: PYGMENTS_BOLD | PieColor.ORANGE,
pygments.token.Name.Function.HTTP.DELETE: PYGMENTS_BOLD | PieColor.RED,
# HTTP status codes
pygments.token.Number.HTTP.INFO: 'bold aqua',
pygments.token.Number.HTTP.OK: 'bold green',
pygments.token.Number.HTTP.REDIRECT: 'bold yellow',
pygments.token.Number.HTTP.CLIENT_ERR: 'bold orange',
pygments.token.Number.HTTP.SERVER_ERR: 'bold red',
pygments.token.Number.HTTP.INFO: PYGMENTS_BOLD | PieColor.AQUA,
pygments.token.Number.HTTP.OK: PYGMENTS_BOLD | PieColor.GREEN,
pygments.token.Number.HTTP.REDIRECT: PYGMENTS_BOLD | PieColor.YELLOW,
pygments.token.Number.HTTP.CLIENT_ERR: PYGMENTS_BOLD | PieColor.ORANGE,
pygments.token.Number.HTTP.SERVER_ERR: PYGMENTS_BOLD | PieColor.RED,
# Metadata
pygments.token.Name.Decorator: 'grey',
pygments.token.Number.SPEED.FAST: 'bold green',
pygments.token.Number.SPEED.AVG: 'bold yellow',
pygments.token.Number.SPEED.SLOW: 'bold orange',
pygments.token.Number.SPEED.VERY_SLOW: 'bold red',
pygments.token.Name.Decorator: PieColor.GREY,
pygments.token.Number.SPEED.FAST: PYGMENTS_BOLD | PieColor.GREEN,
pygments.token.Number.SPEED.AVG: PYGMENTS_BOLD | PieColor.YELLOW,
pygments.token.Number.SPEED.SLOW: PYGMENTS_BOLD | PieColor.ORANGE,
pygments.token.Number.SPEED.VERY_SLOW: PYGMENTS_BOLD | PieColor.RED,
}
PIE_BODY_STYLE = {
# {}[]:
pygments.token.Punctuation: 'grey',
pygments.token.Punctuation: PieColor.GREY,
# Keys
pygments.token.Name.Tag: 'pink',
pygments.token.Name.Tag: PieColor.PINK,
# Values
pygments.token.Literal.String: 'green',
pygments.token.Literal.String.Double: 'green',
pygments.token.Literal.Number: 'aqua',
pygments.token.Keyword: 'orange',
pygments.token.Literal.String: PieColor.GREEN,
pygments.token.Literal.String.Double: PieColor.GREEN,
pygments.token.Literal.Number: PieColor.AQUA,
pygments.token.Keyword: PieColor.ORANGE,
# Other stuff
pygments.token.Text: 'primary',
pygments.token.Name.Attribute: 'primary',
pygments.token.Name.Builtin: 'blue',
pygments.token.Name.Builtin.Pseudo: 'blue',
pygments.token.Name.Class: 'blue',
pygments.token.Name.Constant: 'orange',
pygments.token.Name.Decorator: 'blue',
pygments.token.Name.Entity: 'orange',
pygments.token.Name.Exception: 'yellow',
pygments.token.Name.Function: 'blue',
pygments.token.Name.Variable: 'blue',
pygments.token.String: 'aqua',
pygments.token.String.Backtick: 'secondary',
pygments.token.String.Char: 'aqua',
pygments.token.String.Doc: 'aqua',
pygments.token.String.Escape: 'red',
pygments.token.String.Heredoc: 'aqua',
pygments.token.String.Regex: 'red',
pygments.token.Number: 'aqua',
pygments.token.Operator: 'primary',
pygments.token.Operator.Word: 'green',
pygments.token.Comment: 'secondary',
pygments.token.Comment.Preproc: 'green',
pygments.token.Comment.Special: 'green',
pygments.token.Generic.Deleted: 'aqua',
pygments.token.Generic.Emph: 'italic',
pygments.token.Generic.Error: 'red',
pygments.token.Generic.Heading: 'orange',
pygments.token.Generic.Inserted: 'green',
pygments.token.Generic.Strong: 'bold',
pygments.token.Generic.Subheading: 'orange',
pygments.token.Token: 'primary',
pygments.token.Token.Other: 'orange',
pygments.token.Text: PieColor.PRIMARY,
pygments.token.Name.Attribute: PieColor.PRIMARY,
pygments.token.Name.Builtin: PieColor.BLUE,
pygments.token.Name.Builtin.Pseudo: PieColor.BLUE,
pygments.token.Name.Class: PieColor.BLUE,
pygments.token.Name.Constant: PieColor.ORANGE,
pygments.token.Name.Decorator: PieColor.BLUE,
pygments.token.Name.Entity: PieColor.ORANGE,
pygments.token.Name.Exception: PieColor.YELLOW,
pygments.token.Name.Function: PieColor.BLUE,
pygments.token.Name.Variable: PieColor.BLUE,
pygments.token.String: PieColor.AQUA,
pygments.token.String.Backtick: PieColor.SECONDARY,
pygments.token.String.Char: PieColor.AQUA,
pygments.token.String.Doc: PieColor.AQUA,
pygments.token.String.Escape: PieColor.RED,
pygments.token.String.Heredoc: PieColor.AQUA,
pygments.token.String.Regex: PieColor.RED,
pygments.token.Number: PieColor.AQUA,
pygments.token.Operator: PieColor.PRIMARY,
pygments.token.Operator.Word: PieColor.GREEN,
pygments.token.Comment: PieColor.SECONDARY,
pygments.token.Comment.Preproc: PieColor.GREEN,
pygments.token.Comment.Special: PieColor.GREEN,
pygments.token.Generic.Deleted: PieColor.AQUA,
pygments.token.Generic.Emph: PYGMENTS_ITALIC,
pygments.token.Generic.Error: PieColor.RED,
pygments.token.Generic.Heading: PieColor.ORANGE,
pygments.token.Generic.Inserted: PieColor.GREEN,
pygments.token.Generic.Strong: PYGMENTS_BOLD,
pygments.token.Generic.Subheading: PieColor.ORANGE,
pygments.token.Token: PieColor.PRIMARY,
pygments.token.Token.Other: PieColor.ORANGE,
}
@ -370,7 +371,7 @@ def make_style(name, raw_styles, shade):
def make_styles():
styles = {}
for shade, name in SHADE_NAMES.items():
for shade, name in SHADE_TO_PIE_STYLE.items():
styles[name] = [
make_style(name, style_map, shade)
for style_name, style_map in [

View File

@ -2,7 +2,7 @@ import re
import pygments
from httpie.output.lexers.common import precise
RE_STATUS_LINE = re.compile(r'(\d{3})( +)(.+)')
RE_STATUS_LINE = re.compile(r'(\d{3})( +)?(.+)?')
STATUS_TYPES = {
'1': pygments.token.Number.HTTP.INFO,

44
httpie/output/models.py Normal file
View File

@ -0,0 +1,44 @@
import argparse
from typing import Any, Dict, Union, List, NamedTuple, Optional
from httpie.context import Environment
from httpie.cli.constants import PrettyOptions, PRETTY_MAP, PRETTY_STDOUT_TTY_ONLY
from httpie.cli.argtypes import PARSED_DEFAULT_FORMAT_OPTIONS
from httpie.output.formatters.colors import AUTO_STYLE
class ProcessingOptions(NamedTuple):
"""Represents a set of stylistic options
that are used when deciding which stream
should be used."""
debug: bool = False
traceback: bool = False
stream: bool = False
style: str = AUTO_STYLE
prettify: Union[List[str], PrettyOptions] = PRETTY_STDOUT_TTY_ONLY
response_mime: Optional[str] = None
response_charset: Optional[str] = None
json: bool = False
format_options: Dict[str, Any] = PARSED_DEFAULT_FORMAT_OPTIONS
def get_prettify(self, env: Environment) -> List[str]:
if self.prettify is PRETTY_STDOUT_TTY_ONLY:
return PRETTY_MAP['all' if env.stdout_isatty else 'none']
else:
return self.prettify
@classmethod
def from_raw_args(cls, options: argparse.Namespace) -> 'ProcessingOptions':
fetched_options = {
option: getattr(options, option)
for option in cls._fields
}
return cls(**fetched_options)
@property
def show_traceback(self):
return self.debug or self.traceback

View File

@ -34,7 +34,8 @@ class BaseStream(metaclass=ABCMeta):
self,
msg: HTTPMessage,
output_options: OutputOptions,
on_body_chunk_downloaded: Callable[[bytes], None] = None
on_body_chunk_downloaded: Callable[[bytes], None] = None,
**kwargs
):
"""
:param msg: a :class:`models.HTTPMessage` subclass
@ -45,6 +46,7 @@ class BaseStream(metaclass=ABCMeta):
self.msg = msg
self.output_options = output_options
self.on_body_chunk_downloaded = on_body_chunk_downloaded
self.extra_options = kwargs
def get_headers(self) -> bytes:
"""Return the headers' bytes."""

View File

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

View File

@ -1,17 +1,118 @@
from typing import Optional
STYLE_PIE = 'pie'
STYLE_PIE_DARK = 'pie-dark'
STYLE_PIE_LIGHT = 'pie-light'
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Optional, List
PYGMENTS_BRIGHT_BLACK = 'ansibrightblack'
AUTO_STYLE = 'auto' # Follows terminal ANSI color styles
class Styles(Enum):
PIE = auto()
ANSI = auto()
class PieStyle(str, Enum):
UNIVERSAL = 'pie'
DARK = 'pie-dark'
LIGHT = 'pie-light'
PIE_STYLE_TO_SHADE = {
PieStyle.DARK: '500',
PieStyle.UNIVERSAL: '600',
PieStyle.LIGHT: '700',
}
SHADE_TO_PIE_STYLE = {
shade: style for style, shade in PIE_STYLE_TO_SHADE.items()
}
class ColorString(str):
def __or__(self, other: str) -> 'ColorString':
"""Combine a style with a property.
E.g: PieColor.BLUE | BOLD | ITALIC
"""
if isinstance(other, str):
# In case of PieColor.BLUE | SOMETHING
# we just create a new string.
return ColorString(self + ' ' + other)
elif isinstance(other, GenericColor):
# If we see a GenericColor, then we'll wrap it
# in with the desired property in a different class.
return _StyledGenericColor(other, styles=self.split())
elif isinstance(other, _StyledGenericColor):
# And if it is already wrapped, we'll just extend the
# list of properties.
other.styles.extend(self.split())
return other
else:
return NotImplemented
class PieColor(ColorString, Enum):
"""Styles that are available only in Pie themes."""
PRIMARY = 'primary'
SECONDARY = 'secondary'
WHITE = 'white'
BLACK = 'black'
GREY = 'grey'
AQUA = 'aqua'
PURPLE = 'purple'
ORANGE = 'orange'
RED = 'red'
BLUE = 'blue'
PINK = 'pink'
GREEN = 'green'
YELLOW = 'yellow'
class GenericColor(Enum):
"""Generic colors that are safe to use everywhere."""
# <https://rich.readthedocs.io/en/stable/appendix/colors.html>
WHITE = {Styles.PIE: PieColor.WHITE, Styles.ANSI: 'white'}
BLACK = {Styles.PIE: PieColor.BLACK, Styles.ANSI: 'black'}
GREEN = {Styles.PIE: PieColor.GREEN, Styles.ANSI: 'green'}
ORANGE = {Styles.PIE: PieColor.ORANGE, Styles.ANSI: 'yellow'}
YELLOW = {Styles.PIE: PieColor.YELLOW, Styles.ANSI: 'bright_yellow'}
BLUE = {Styles.PIE: PieColor.BLUE, Styles.ANSI: 'blue'}
PINK = {Styles.PIE: PieColor.PINK, Styles.ANSI: 'bright_magenta'}
PURPLE = {Styles.PIE: PieColor.PURPLE, Styles.ANSI: 'magenta'}
RED = {Styles.PIE: PieColor.RED, Styles.ANSI: 'red'}
AQUA = {Styles.PIE: PieColor.AQUA, Styles.ANSI: 'cyan'}
GREY = {Styles.PIE: PieColor.GREY, Styles.ANSI: 'bright_black'}
def apply_style(
self, style: Styles, *, style_name: Optional[str] = None
) -> str:
"""Apply the given style to a particular value."""
exposed_color = self.value[style]
if style is Styles.PIE:
assert style_name is not None
shade = PIE_STYLE_TO_SHADE[PieStyle(style_name)]
return get_color(exposed_color, shade)
else:
return exposed_color
@dataclass
class _StyledGenericColor:
color: 'GenericColor'
styles: List[str] = field(default_factory=list)
# noinspection PyDictCreation
COLOR_PALETTE = {
# Copy the brand palette
'transparent': 'transparent',
'current': 'currentColor',
'white': '#F5F5F0',
'black': '#1C1818',
'grey': {
PieColor.WHITE: '#F5F5F0',
PieColor.BLACK: '#1C1818',
PieColor.GREY: {
'50': '#F5F5F0',
'100': '#EDEDEB',
'200': '#D1D1CF',
@ -24,7 +125,7 @@ COLOR_PALETTE = {
'900': '#1C1818',
'DEFAULT': '#7D7D7D',
},
'aqua': {
PieColor.AQUA: {
'50': '#E8F0F5',
'100': '#D6E3ED',
'200': '#C4D9E5',
@ -37,7 +138,7 @@ COLOR_PALETTE = {
'900': '#455966',
'DEFAULT': '#8CB4CD',
},
'purple': {
PieColor.PURPLE: {
'50': '#F0E0FC',
'100': '#E3C7FA',
'200': '#D9ADF7',
@ -50,7 +151,7 @@ COLOR_PALETTE = {
'900': '#5C2982',
'DEFAULT': '#B464F0',
},
'orange': {
PieColor.ORANGE: {
'50': '#FFEDDB',
'100': '#FFDEBF',
'200': '#FFCFA3',
@ -63,7 +164,7 @@ COLOR_PALETTE = {
'900': '#C75E0A',
'DEFAULT': '#FFA24E',
},
'red': {
PieColor.RED: {
'50': '#FFE0DE',
'100': '#FFC7C4',
'200': '#FFB0AB',
@ -76,7 +177,7 @@ COLOR_PALETTE = {
'900': '#910A00',
'DEFAULT': '#FF665B',
},
'blue': {
PieColor.BLUE: {
'50': '#DBE3FA',
'100': '#BFCFF5',
'200': '#A1B8F2',
@ -89,7 +190,7 @@ COLOR_PALETTE = {
'900': '#2B478F',
'DEFAULT': '#4B78E6',
},
'pink': {
PieColor.PINK: {
'50': '#FFEBFF',
'100': '#FCDBFC',
'200': '#FCCCFC',
@ -102,7 +203,7 @@ COLOR_PALETTE = {
'900': '#8C3D8A',
'DEFAULT': '#FA9BFA',
},
'green': {
PieColor.GREEN: {
'50': '#E3F7E8',
'100': '#CCF2D6',
'200': '#B5EDC4',
@ -115,7 +216,7 @@ COLOR_PALETTE = {
'900': '#307842',
'DEFAULT': '#73DC8C',
},
'yellow': {
PieColor.YELLOW: {
'50': '#F7F7DB',
'100': '#F2F2BF',
'200': '#EDEDA6',
@ -129,38 +230,39 @@ COLOR_PALETTE = {
'DEFAULT': '#DBDE52',
},
}
# Grey is the same no matter shade for the colors
COLOR_PALETTE['grey'] = {
shade: COLOR_PALETTE['grey']['500'] for shade in COLOR_PALETTE['grey'].keys()
}
COLOR_PALETTE['primary'] = {
'700': COLOR_PALETTE['black'],
'600': 'ansibrightblack',
'500': COLOR_PALETTE['white'],
}
COLOR_PALETTE['secondary'] = {'700': '#37523C', '600': '#6c6969', '500': '#6c6969'}
COLOR_PALETTE.update(
{
# Terminal-specific palette customizations.
PieColor.GREY: {
# Grey is the same no matter shade for the colors
shade: COLOR_PALETTE[PieColor.GREY]['500']
for shade in COLOR_PALETTE[PieColor.GREY].keys()
},
PieColor.PRIMARY: {
'700': COLOR_PALETTE[PieColor.BLACK],
'600': PYGMENTS_BRIGHT_BLACK,
'500': COLOR_PALETTE[PieColor.WHITE],
},
PieColor.SECONDARY: {
'700': '#37523C',
'600': '#6c6969',
'500': '#6c6969',
},
}
)
SHADE_NAMES = {
'500': STYLE_PIE_DARK,
'600': STYLE_PIE,
'700': STYLE_PIE_LIGHT
}
SHADES = [
'50',
*map(str, range(100, 1000, 100))
]
def boldify(color: PieColor) -> str:
return f'bold {color}'
def get_color(color: str, shade: str) -> Optional[str]:
if color not in COLOR_PALETTE:
# noinspection PyDefaultArgument
def get_color(
color: PieColor, shade: str, *, palette=COLOR_PALETTE
) -> Optional[str]:
if color not in palette:
return None
color_code = COLOR_PALETTE[color]
color_code = palette[color]
if isinstance(color_code, dict) and shade in color_code:
return color_code[shade]
else:

View File

@ -0,0 +1,230 @@
import re
import textwrap
from typing import AbstractSet, Iterable, Optional, Tuple
from rich.console import RenderableType
from rich.highlighter import RegexHighlighter
from rich.padding import Padding
from rich.table import Table
from rich.text import Text
from httpie.cli.constants import SEPARATOR_GROUP_ALL_ITEMS
from httpie.cli.options import Argument, ParserSpec, Qualifiers
from httpie.output.ui.palette import GenericColor
SEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS))
STYLE_METAVAR = GenericColor.YELLOW
STYLE_SWITCH = GenericColor.GREEN
STYLE_PROGRAM_NAME = GenericColor.GREEN # .boldify()
STYLE_USAGE_OPTIONAL = GenericColor.GREY
STYLE_USAGE_REGULAR = GenericColor.WHITE
STYLE_USAGE_ERROR = GenericColor.RED
STYLE_USAGE_MISSING = GenericColor.YELLOW
STYLE_BOLD = 'bold'
MAX_CHOICE_CHARS = 80
LEFT_PADDING_2 = (0, 0, 0, 2)
LEFT_PADDING_3 = (0, 0, 0, 3)
LEFT_PADDING_4 = (0, 0, 0, 4)
LEFT_PADDING_5 = (0, 0, 0, 4)
LEFT_INDENT_2 = (1, 0, 0, 2)
LEFT_INDENT_3 = (1, 0, 0, 3)
LEFT_INDENT_BOTTOM_3 = (0, 0, 1, 3)
MORE_INFO_COMMANDS = """
To learn more, you can try:
-> running 'http --manual'
-> visiting our full documentation at https://httpie.io/docs/cli
"""
class OptionsHighlighter(RegexHighlighter):
highlights = [
r'(^|\W)(?P<option>\-{1,2}[\w|-]+)(?![a-zA-Z0-9])',
r'(?P<bold>HTTPie)',
]
options_highlighter = OptionsHighlighter()
def unpack_argument(
argument: Argument,
) -> Tuple[Text, Text]:
opt1 = opt2 = ''
style = None
if argument.aliases:
if len(argument.aliases) >= 2:
opt2, opt1 = argument.aliases
else:
(opt1,) = argument.aliases
else:
opt1 = argument.metavar
style = STYLE_USAGE_REGULAR
return Text(opt1, style=style), Text(opt2)
def to_usage(
spec: ParserSpec,
*,
program_name: Optional[str] = None,
whitelist: AbstractSet[str] = frozenset()
) -> RenderableType:
shown_arguments = [
argument
for group in spec.groups
for argument in group.arguments
if (not argument.aliases or whitelist.intersection(argument.aliases))
]
# Sort the shown_arguments so that --dash options are
# shown first
shown_arguments.sort(key=lambda argument: argument.aliases, reverse=True)
text = Text(program_name or spec.program, style=STYLE_BOLD)
for argument in shown_arguments:
text.append(' ')
is_whitelisted = whitelist.intersection(argument.aliases)
if argument.aliases:
name = '/'.join(sorted(argument.aliases, key=len))
else:
name = argument.metavar
nargs = argument.configuration.get('nargs')
if nargs is Qualifiers.OPTIONAL:
text.append('[' + name + ']', style=STYLE_USAGE_OPTIONAL)
elif nargs is Qualifiers.ZERO_OR_MORE:
text.append(
'[' + name + ' ...]',
style=STYLE_USAGE_OPTIONAL,
)
else:
text.append(
name,
style=STYLE_USAGE_ERROR
if is_whitelisted
else STYLE_USAGE_REGULAR,
)
raw_form = argument.serialize()
if raw_form.get('choices'):
text.append(' ')
text.append(
'{' + ', '.join(raw_form['choices']) + '}',
style=STYLE_USAGE_MISSING,
)
return text
# This part is loosely based on the rich-click's help message
# generation.
def to_help_message(
spec: ParserSpec,
) -> Iterable[RenderableType]:
yield Padding(
options_highlighter(spec.description),
LEFT_INDENT_2,
)
yield Padding(
Text('Usage', style=STYLE_SWITCH),
LEFT_INDENT_2,
)
yield Padding(to_usage(spec), LEFT_INDENT_3)
group_rows = {}
for group in spec.groups:
options_rows = []
for argument in group.arguments:
if argument.is_hidden:
continue
opt1, opt2 = unpack_argument(argument)
if opt2:
opt1.append('/')
opt1.append(opt2)
# Column for a metavar, if we have one
metavar = Text(style=STYLE_METAVAR)
metavar.append(argument.configuration.get('metavar', ''))
if opt1 == metavar:
metavar = Text('')
raw_form = argument.serialize()
desc = raw_form.get('short_description', '')
if raw_form.get('choices'):
desc += ' (choices: '
desc += textwrap.shorten(
', '.join(raw_form.get('choices')),
MAX_CHOICE_CHARS,
)
desc += ')'
rows = [
Padding(
options_highlighter(opt1),
LEFT_PADDING_2,
),
metavar,
options_highlighter(desc),
]
options_rows.append(rows)
if argument.configuration.get('nested_options'):
options_rows.extend(
[
(
Padding(
Text(
key,
style=STYLE_USAGE_OPTIONAL,
),
LEFT_PADDING_4,
),
value,
dec,
)
for key, value, dec in argument.nested_options
]
)
group_rows[group.name] = options_rows
options_table = Table(highlight=False, box=None, show_header=False)
for group_name, options_rows in group_rows.items():
options_table.add_row(Text(), Text(), Text())
options_table.add_row(
Text(group_name, style=STYLE_SWITCH),
Text(),
Text(),
)
options_table.add_row(Text(), Text(), Text())
for row in options_rows:
options_table.add_row(*row)
yield Padding(
Text('Options', style=STYLE_SWITCH),
LEFT_INDENT_2,
)
yield Padding(options_table, LEFT_PADDING_2)
yield Padding(
Text('More Information', style=STYLE_SWITCH),
LEFT_INDENT_2,
)
yield Padding(
MORE_INFO_COMMANDS.rstrip('\n'),
LEFT_PADDING_3
)
yield Padding(
spec.epilog.rstrip('\n'),
LEFT_INDENT_BOTTOM_3,
)

View File

@ -0,0 +1,73 @@
from collections import ChainMap
from typing import TYPE_CHECKING, Any, Optional
if TYPE_CHECKING:
from rich.theme import Theme
from httpie.output.ui.palette import GenericColor, PieStyle, Styles, ColorString, _StyledGenericColor # noqa
RICH_BOLD = ColorString('bold')
# Rich-specific color code declarations
# <https://github.com/Textualize/rich/blob/fcd684dd3a482977cab620e71ccaebb94bf13ac9/rich/default_styles.py>
CUSTOM_STYLES = {
'progress.description': RICH_BOLD | GenericColor.WHITE,
'progress.data.speed': RICH_BOLD | GenericColor.GREEN,
'progress.percentage': RICH_BOLD | GenericColor.AQUA,
'progress.download': RICH_BOLD | GenericColor.AQUA,
'progress.remaining': RICH_BOLD | GenericColor.ORANGE,
'bar.complete': RICH_BOLD | GenericColor.PURPLE,
'bar.finished': RICH_BOLD | GenericColor.GREEN,
'bar.pulse': RICH_BOLD | GenericColor.PURPLE,
'option': RICH_BOLD | GenericColor.PINK,
}
class _GenericColorCaster(dict):
"""
Translate GenericColor to a regular string on the attribute access
phase.
"""
def _translate(self, key: Any) -> Any:
if isinstance(key, GenericColor):
return key.name.lower()
else:
return key
def __getitem__(self, key: Any) -> Any:
return super().__getitem__(self._translate(key))
def get(self, key: Any) -> Any:
return super().get(self._translate(key))
def _make_rich_color_theme(style_name: Optional[str] = None) -> 'Theme':
from rich.style import Style
from rich.theme import Theme
try:
PieStyle(style_name)
except ValueError:
style = Styles.ANSI
else:
style = Styles.PIE
theme = Theme()
for color, color_set in ChainMap(
GenericColor.__members__, CUSTOM_STYLES
).items():
if isinstance(color_set, _StyledGenericColor):
properties = dict.fromkeys(color_set.styles, True)
color_set = color_set.color
else:
properties = {}
theme.styles[color.lower()] = Style(
color=color_set.apply_style(style, style_name=style_name),
**properties,
)
# E.g translate GenericColor.BLUE into blue on key access
theme.styles = _GenericColorCaster(theme.styles)
return theme

Some files were not shown because too many files have changed in this diff Show More