Compare commits

...

128 Commits
3.1.0 ... 3.2.4

Author SHA1 Message Date
2105caa49b 3.2.4 2024-11-01 18:29:56 +01:00
8560d1196d Update test.yml 2024-11-01 18:01:03 +01:00
2ef4a57d8c Fix/refactor default cert loading 2024-11-01 17:47:27 +01:00
ff742581f4 Cleanup default cert loading 2024-11-01 17:38:39 +01:00
fd30c4ef62 Explicitly load default certificates when creating SSL context (#1583) (#1596)
* Explicitly load default certificates when creating SSL context (#1583)

Requests prior to 2.32.3 always loaded the default (system-wide)
set of trusted certificates into custom SSL contexts. 2.32.3 no
longer does. This has broken a lot of users, but the fix is
moving slowly upstream due to security considerations - see
https://github.com/psf/requests/issues/6730 and
https://github.com/psf/requests/pull/6731 .

As suggested at
https://github.com/psf/requests/pull/6710#issuecomment-2137802782
this can be worked around by explicitly loading the default
certificates into the context. We check the method exists before
calling it just to be safe, it was added in Python 3.4.

Signed-off-by: Adam Williamson <awilliam@redhat.com>

* Drop the upper bound on the requests dependency again

As we can now work with requests 2.32.3+, we no longer need this
pin.

Signed-off-by: Adam Williamson <awilliam@redhat.com>

---------

Signed-off-by: Adam Williamson <awilliam@redhat.com>
2024-11-01 09:37:11 -07:00
cee82c825e Fix link to CurliPie tool (#1582)
* Fix link to CurliPie tool

* Use the link to web version for CurliPie

Co-authored-by: Jan Brasna <1784648+janbrasna@users.noreply.github.com>

---------

Co-authored-by: Jan Brasna <1784648+janbrasna@users.noreply.github.com>
2024-10-31 09:13:24 -07:00
9eb8699873 Added an alias for HTTPS in fish completions (#1598)
Ensure that fish completion is working with the `https` command. It just wraps the identical completions from `http` to `https`.
2024-10-31 09:10:54 -07:00
3037327410 Update the raw json fields example (#1606) 2024-10-31 09:02:17 -07:00
50e1564600 Update the Forms section of README.md (#1593)
The Forms section of README.md was missing what the data fields are serialized as.
2024-10-28 20:28:36 +01:00
f4cf43ecdd Cleanup 2024-07-10 16:24:03 +02:00
7f03c52d22 Fix SSL connections by pinning the requests version to 2.31.0
Close #1583
Close #1581
2024-07-10 16:20:38 +02:00
5c068f8102 Re-add conditional colorama dependency
#1553
2024-03-18 16:53:23 +01:00
10b7d317d0 Migrate setup.py to setup.cfg (#1553)
* migrate setup.py to setup.cfg

No man pages

* fix Makefile build

* silence flake8 F811 for BaseCLIResponse.command

* also include man pages

* restore a stub setup.py

* remove pytest-lazy fixtures
2024-03-18 16:37:09 +01:00
3de7c82077 Cleanup 2024-03-04 18:12:18 +01:00
db16bbee96 Drop dependency on the abandoned python-lazy-fixture II. 2024-03-04 18:05:26 +01:00
3524ccf0ba Drop dependency on the abandoned python-lazy-fixture 2024-03-04 16:28:03 +01:00
8ac44b57ce Ensure support for Python 3.11/3.12 (#1540)
These changes:
  * enable testing for Python 3.11/3.12 on CI
  * update classifiers at setup.py
2024-03-04 15:57:45 +01:00
2db28ef692 docs: fix typo (#1548) 2024-03-04 15:37:15 +01:00
7a234d60da [automated] Update generated content (#1566)
Co-authored-by: jkbrzt <jkbrzt@users.noreply.github.com>
2024-03-04 15:36:44 +01:00
a842a932cc docs: Update the url for offline mode (#1556) 2024-03-04 15:35:32 +01:00
b934eec7fc Replace redirection with tee command with sudo for file creation (#1557) 2024-03-04 15:34:57 +01:00
9e8e3691c8 Update README.md 2023-10-24 10:23:30 -07:00
e52a60e67c [automated] Update generated content (#1524)
Co-authored-by: jakubroztocil <jakubroztocil@users.noreply.github.com>
2023-08-06 14:35:57 +02:00
8aa654d1ef Update README.md 2023-08-06 14:15:10 +02:00
011402152c Rename repo from httpie/httpie to httpie/cli 2023-08-06 14:04:32 +02:00
30a6f73ec8 README.md: fix the file based separators internal link (#1510)
The link to "file based separators" in the "Reading headers from a file" section when rendered to HTML does not work. The link is now fixed by providing a URL and link text.
2023-05-24 08:22:56 -07:00
ec4fb84254 Document restriction on top-level JSON types explicitly (#1496)
* Document restriction on top-level JSON types explicitly

* Update docs/README.md

---------

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2023-05-23 05:11:41 -07:00
c8c135ffff [automated] Update generated content (#1509)
Co-authored-by: jakubroztocil <jakubroztocil@users.noreply.github.com>
2023-05-22 11:57:31 -07:00
2da955fb06 Man page clean-up (#1508)
Ensure we don’t include dynamic content in the static man pages.
2023-05-22 11:56:30 -07:00
c2677eeccf [automated] Update auto-generated files (#1507)
Co-authored-by: jakubroztocil <jakubroztocil@users.noreply.github.com>
2023-05-22 11:40:01 -07:00
5325a9bc07 Remove skipping
Always skipped
2023-05-22 19:01:36 +02:00
2e3272b5ba Fixed installation steps for Debian & Ubuntu (#1473) (#1475)
* Fixed installation steps for Debian & Ubuntu

* Fixed incorrect path
2023-05-22 09:50:25 -07:00
5dc30bc438 Ensure sudo for apt 2023-05-22 18:44:07 +02:00
442aa673ac Fix Choco changelog link 2023-05-22 18:04:06 +02:00
3e290e5dba Fix Snap publish action 2023-05-22 17:51:22 +02:00
2a9cd226aa Bump version for Chocolatey 2023-05-22 16:21:25 +02:00
3b58a4a4a2 Trigger docs deploy 2023-05-20 00:01:37 +02:00
7512ca7e47 Fix docs deploy 2023-05-20 00:01:10 +02:00
cc697db730 Add a changelog entry for #1502 2023-05-19 23:59:08 +02:00
cbe53ed79a Avoid override of headers by urllib3 when unset (#1502)
* Pass SKIP_HEADER const when header is unset

* Hide SKIP_HEADER constant when displaying headers

* Test that omits User-Agent
2023-05-19 23:53:26 +02:00
3664644722 Fix issue link 2023-05-19 23:44:52 +02:00
29de4ce115 v3.2.2 2023-05-19 23:41:26 +02:00
879fedc10a Flake8 2023-05-19 23:41:16 +02:00
18bb49b268 Skip a test failing in CI 2023-05-19 23:29:09 +02:00
fcd3f7ece6 Generate default ciphers using approach from #1501 2023-05-19 22:26:33 +02:00
8e56e9fc64 Fix a failing test 2023-05-19 21:51:52 +02:00
44d3cff03f Fix log level display on newer Python 2023-05-19 21:51:32 +02:00
d021b94b5d Clean up DEFAULT_SSL_CIPHERS comments 2023-05-19 21:50:58 +02:00
4e29a6d561 fix(urllib3): 🐛 could not find urllib3 DEFAULT_CIPHERS (#1505) 2023-05-19 21:18:55 +02:00
Sid
1ae4152e1e docs: improve documentation for installation of unstable version (#1490)
* docs: improve documentation for installation of unstable version

I am trying to rephrase the instructions to make it clear, concise and beginner friendly.

Summary of changes:
* rephrased the instructions to install unstable version of HTTPie
* rephrased the instructions to verify the installation

* fix(docs): remove trailing spaces

* docs: fix 'pip' formatting

Enclosed 'pip' with backticks to display it as inline code

* docs: better description for pip installation (unstable version)

* Update docs/README.md

---------

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2023-05-09 11:23:29 +02:00
47e9b99ba1 Bump actions/stale from 7 to 8 (#1492)
Bumps [actions/stale](https://github.com/actions/stale) from 7 to 8.
- [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/v7...v8)

---
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>
2023-03-28 18:30:30 -07:00
Sid
265841f866 docs: improve clarity of sentences (#1489)
* docs: improve clarity of sentences

Improved clarity by rephrasing sentences in the best practices section.

* docs: improve best practices section

* use appropriate formatting for stdin
* include EOF in abbreviated form

* docs: clarify sentence

* change 'know that' -> 'note that'
* use neither nor for better clarity
2023-03-22 13:03:18 -07:00
b16392fbb9 Remove redundant imports (#1466) 2023-01-15 11:35:36 -08:00
e73c3e6c24 Fix failing tests with responses ≥ 0.22.0
Close #1461
Close #1467

Thanks, @alexshpilkin!
2023-01-15 17:43:17 +01:00
f0563deb7f Bump mislav/bump-homebrew-formula-action from 1 to 2 (#1453)
Bumps [mislav/bump-homebrew-formula-action](https://github.com/mislav/bump-homebrew-formula-action) from 1 to 2.
- [Release notes](https://github.com/mislav/bump-homebrew-formula-action/releases)
- [Commits](https://github.com/mislav/bump-homebrew-formula-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: mislav/bump-homebrew-formula-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-04 03:18:15 -08:00
4894b4c0fc Bump actions/stale from 6 to 7 (#1459)
Bumps [actions/stale](https://github.com/actions/stale) from 6 to 7.
- [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/v6...v7)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-04 03:17:54 -08:00
3a123c4125 Fix ci status badge error (#1464) 2023-01-04 03:17:18 -08:00
621042a048 Update README.md 2022-10-01 04:00:56 -07:00
0689b55e1d Clean up and refactor nested JSON parsing & interpreting (#1440) 2022-10-01 03:38:19 -07:00
a7321d8ac4 🔥 Remove $ from code fenced examples on readme (#1435)
* 🔥 Remove $ from code fenced examples on readme

* 🚨 FIx markdownlint errors

README.md:8: MD009 Trailing spaces
README.md:10: MD009 Trailing spaces
2022-10-01 03:37:50 -07:00
d9a73cd8eb Fix typos (#1431)
Found via `codespell -L datas`.
2022-10-01 03:34:41 -07:00
930cd9081a Use grep -E instead of egrep (#1436) 2022-10-01 03:32:17 -07:00
3549ee8342 Bump actions/stale from 5 to 6 (#1437)
Bumps [actions/stale](https://github.com/actions/stale) from 5 to 6.
- [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/v5...v6)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-01 03:31:54 -07:00
810bb1c77b Update README.md 2022-08-10 09:13:49 -07:00
767f3c3a19 Update README.md 2022-08-10 07:58:09 -07:00
1236793272 Update README.md 2022-08-10 07:48:57 -07:00
c3a2f87dd2 Update README.md 2022-08-10 07:41:13 -07:00
1121d695a8 Update README.md 2022-08-10 07:38:40 -07:00
5794a070e1 place the logo in the middle in README.md (#1393) 2022-07-12 08:33:57 -07:00
4736a16698 docs: Fix a few typos (#1419) 2022-07-03 14:54:34 +02:00
3ad408add7 Fix paths to run benchmarking script (#1416) 2022-06-19 00:20:22 -07:00
91cdb22a4b Update Requests documentation links (#1414) 2022-06-17 14:04:42 -07:00
c995fd9b24 Bump actions/setup-python from 3 to 4 (#1412)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3 to 4.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v3...v4)

---
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-06-09 10:04:06 -07: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
168 changed files with 6696 additions and 2183 deletions

View File

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

View File

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

22
.github/workflows/content.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Update Generated Content
on:
push:
branches:
- master
jobs:
update-content:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.9
- run: make content
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v4
with:
commit-message: "[automated] Update generated content"
title: "[automated] Update generated content"
delete-branch: true
token: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

@ -17,6 +17,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Install HTTPie - name: Install HTTPie
run: sudo snap install --edge httpie run: sudo pip install httpie
- name: Trigger new documentation build - name: Trigger new documentation build
run: http --ignore-stdin POST ${{ secrets.DOCS_UPDATE_VERCEL_HOOK }} run: http --ignore-stdin POST ${{ secrets.DOCS_UPDATE_VERCEL_HOOK }}

View File

@ -1,33 +0,0 @@
name: Update & Install Docs
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@v3
- uses: actions/setup-python@v3
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@v2
with:
formula-name: httpie
tag-name: ${{ github.events.inputs.branch }}
env:
COMMITTER_TOKEN: ${{ secrets.BREW_UPDATE_TOKEN }}

61
.github/workflows/release-choco.yml vendored Normal file
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@v4
with:
python-version: 3.9
- name: Build Artifacts
run: |
cd extras/packaging/linux
./get_release_artifacts.sh
- uses: actions/upload-artifact@v3
with:
name: http
path: extras/packaging/linux/artifacts/dist/http
- uses: actions/upload-artifact@v3
with:
name: httpie.deb
path: extras/packaging/linux/artifacts/dist/*.deb
- uses: actions/upload-artifact@v3
with:
name: httpie.rpm
path: extras/packaging/linux/artifacts/dist/*.rpm
- name: Determine the release upload upload_url
id: release_id
run: |
pip install httpie
export API_URL="api.github.com/repos/httpie/cli/releases/tags/${{ github.event.inputs.tag_name }}"
export UPLOAD_URL=`https --ignore-stdin GET $API_URL | jq -r ".upload_url"`
echo "::set-output name=UPLOAD_URL::$UPLOAD_URL"
- name: Publish Debian Package
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.release_id.outputs.UPLOAD_URL }}
asset_path: extras/packaging/linux/artifacts/dist/httpie_${{ github.event.inputs.tag_name }}_amd64.deb
asset_name: httpie-${{ github.event.inputs.tag_name }}.deb
asset_content_type: binary/octet-stream
- name: Publish Single Executable
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.release_id.outputs.UPLOAD_URL }}
asset_path: extras/packaging/linux/artifacts/dist/http
asset_name: http
asset_content_type: binary/octet-stream

30
.github/workflows/release-pypi.yml vendored Normal file
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@v4
with:
python-version: 3.9
- name: Build a binary wheel and a source tarball
run: make install && make build
- name: Release on PyPI
uses: pypa/gh-action-pypi-publish@master
with:
password: ${{ secrets.PYPI_TOKEN }}

View File

@ -1,4 +1,4 @@
name: Release snap name: Release on Snap
on: on:
workflow_dispatch: workflow_dispatch:
@ -7,22 +7,35 @@ on:
description: "The branch, tag or SHA to release from" description: "The branch, tag or SHA to release from"
required: true required: true
default: "master" default: "master"
level:
description: "Release level: stable, candidate, beta, edge"
required: true
default: "edge"
jobs: jobs:
snap: snap-build-and-release:
name: Build & Release the Snap Package
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
# If any of the stages fail, then we'll stop the action
# to give release manager time to investigate the underlying
# issue.
fail-fast: true
matrix:
level: [edge, beta, candidate, stable]
# Set the concurrency level for this version, so
# that we'll release one by one.
concurrency: ${{ github.event.inputs.branch }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
ref: ${{ github.event.inputs.branch }} ref: ${{ github.event.inputs.branch }}
- uses: snapcore/action-build@v1 - uses: snapcore/action-build@v1
id: build id: build
- uses: snapcore/action-publish@v1 - uses: snapcore/action-publish@v1
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
with: with:
store_login: ${{ secrets.SNAP_STORE_LOGIN }}
snap: ${{ steps.build.outputs.snap }} snap: ${{ steps.build.outputs.snap }}
release: ${{ github.event.inputs.level }} release: ${{ matrix.level }}

View File

@ -1,33 +0,0 @@
name: Release on PyPI
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@v3
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@v3
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 runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v4 - uses: actions/stale@v8
with: with:
close-pr-message: 'Thanks for the pull request, but since it was stale for more than a 30 days we are closing it. If you want to work back on it, feel free to re-open it or create a new one.' close-pr-message: 'Thanks for the pull request, but since it was stale for more than a 30 days we are closing it. If you want to work back on it, feel free to re-open it or create a new one.'
stale-pr-label: 'stale' stale-pr-label: 'stale'

View File

@ -1,4 +1,7 @@
name: Tests name: Tests
concurrency:
group: ${{ github.head_ref || github.run_id }}
cancel-in-progress: true
on: on:
push: push:
@ -21,13 +24,19 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-13, windows-latest]
python-version: [3.7, 3.8, 3.9, "3.10"] python-version:
- '3.12'
- '3.11'
- '3.10'
- '3.9'
- '3.8'
- '3.7'
pyopenssl: [0, 1] pyopenssl: [0, 1]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- uses: actions/setup-python@v3 - uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Windows setup - name: Windows setup

4
.gitignore vendored
View File

@ -43,8 +43,8 @@ MANIFEST
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it. # before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec *.spec
*.manifest
# Installer logs # Installer logs
pip-log.txt pip-log.txt
@ -151,3 +151,5 @@ dmypy.json
# Windows Chocolatey # Windows Chocolatey
*.nupkg *.nupkg
artifacts/

View File

@ -7,11 +7,6 @@ actions:
# Use this when the latest spec is not up-to-date. # Use this when the latest spec is not up-to-date.
# post-upstream-clone: "cp docs/packaging/linux-fedora/httpie.spec.txt httpie.spec" # post-upstream-clone: "cp docs/packaging/linux-fedora/httpie.spec.txt httpie.spec"
jobs: jobs:
- job: copr_build
trigger: pull_request
metadata:
targets:
- fedora-all
- job: propose_downstream - job: propose_downstream
trigger: release trigger: release
metadata: metadata:

View File

@ -4,7 +4,7 @@
## Patches, features, ideas ## Patches, features, ideas
[Complete list of contributors on GitHub](https://github.com/httpie/httpie/graphs/contributors) [Complete list of contributors on GitHub](https://github.com/httpie/cli/graphs/contributors)
- [Cláudia T. Delgado](https://github.com/claudiatd) - [Cláudia T. Delgado](https://github.com/claudiatd)
- [Hank Gay](https://github.com/gthank) - [Hank Gay](https://github.com/gthank)

View File

@ -3,124 +3,156 @@
This document records all notable changes to [HTTPie](https://httpie.io). This document records all notable changes to [HTTPie](https://httpie.io).
This project adheres to [Semantic Versioning](https://semver.org/). This project adheres to [Semantic Versioning](https://semver.org/).
## [3.1.0](https://github.com/httpie/httpie/compare/3.0.2...3.1.0) (2022-03-08) ## [3.2.4](https://github.com/httpie/cli/compare/3.2.3...3.2.4) (2024-11-01)
- **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)) - Fix default certs loading and unpin `requests`. ([#1596](https://github.com/httpie/cli/issues/1596))
- 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) ## [3.2.3](https://github.com/httpie/cli/compare/3.2.2...3.2.3) (2024-07-10)
- Fix SSL connections by pinning the `requests` version to `2.31.0`. (#1583, #1581)
- Make it possible to [unset](https://httpie.io/docs/cli/default-request-headers) the `User-Agent` and `Accept-Encoding` request headers. ([#1502](https://github.com/httpie/cli/issues/1502))
## [3.2.2](https://github.com/httpie/cli/compare/3.2.1...3.2.2) (2023-05-19)
- Fixed compatibility with urllib3 2.0.0. ([#1499](https://github.com/httpie/cli/issues/1499))
## [3.2.1](https://github.com/httpie/cli/compare/3.1.0...3.2.1) (2022-05-06)
- Improved support for determining auto-streaming when the `Content-Type` header includes encoding information. ([#1383](https://github.com/httpie/cli/pull/1383))
- Fixed the display of the crash happening in the secondary process for update checks. ([#1388](https://github.com/httpie/cli/issues/1388))
## [3.2.0](https://github.com/httpie/cli/compare/3.1.0...3.2.0) (2022-05-05)
- Added a warning for notifying the user about the new updates. ([#1336](https://github.com/httpie/cli/pull/1336))
- Added support for single binary executables. ([#1330](https://github.com/httpie/cli/pull/1330))
- Added support for man pages (and auto generation of them from the parser declaration). ([#1317](https://github.com/httpie/cli/pull/1317))
- Added `http --manual` for man pages & regular manual with pager. ([#1343](https://github.com/httpie/cli/pull/1343))
- Added support for session persistence of repeated headers with the same name. ([#1335](https://github.com/httpie/cli/pull/1335))
- Added support for sending `Secure` cookies to the `localhost` (and `.local` suffixed domains). ([#1308](https://github.com/httpie/cli/issues/1308))
- Improved UI for the progress bars. ([#1324](https://github.com/httpie/cli/pull/1324))
- Fixed redundant creation of `Content-Length` header on `OPTIONS` requests. ([#1310](https://github.com/httpie/cli/issues/1310))
- Fixed blocking of warning thread on some use cases. ([#1349](https://github.com/httpie/cli/issues/1349))
- Changed `httpie plugins` to the new `httpie cli` namespace as `httpie cli plugins` (`httpie plugins` continues to work as a hidden alias). ([#1320](https://github.com/httpie/cli/issues/1320))
- Soft deprecated the `--history-print`. ([#1380](https://github.com/httpie/cli/pull/1380))
## [3.1.0](https://github.com/httpie/cli/compare/3.0.2...3.1.0) (2022-03-08)
- **SECURITY** Fixed the [vulnerability](https://github.com/httpie/cli/security/advisories/GHSA-9w4w-cpc8-h2fq) that caused exposure of cookies on redirects to third party hosts. ([#1312](https://github.com/httpie/cli/pull/1312))
- Fixed escaping of integer indexes with multiple backslashes in the nested JSON builder. ([#1285](https://github.com/httpie/cli/issues/1285))
- Fixed displaying of status code without a status message on non-`auto` themes. ([#1300](https://github.com/httpie/cli/issues/1300))
- Fixed redundant issuance of stdin detection warnings on some rare cases due to underlying implementation. ([#1303](https://github.com/httpie/cli/pull/1303))
- Fixed double `--quiet` so that it will now suppress all python level warnings. ([#1271](https://github.com/httpie/cli/issues/1271))
- Added support for specifying certificate private key passphrases through `--cert-key-pass` and prompts. ([#946](https://github.com/httpie/cli/issues/946))
- Added `httpie cli export-args` command for exposing the parser specification for the `http`/`https` commands. ([#1293](https://github.com/httpie/cli/pull/1293))
- Improved regulation of top-level arrays. ([#1292](https://github.com/httpie/cli/commit/225dccb2186f14f871695b6c4e0bfbcdb2e3aa28))
- Improved UI layout for standalone invocations. ([#1296](https://github.com/httpie/cli/pull/1296))
## [3.0.2](https://github.com/httpie/cli/compare/3.0.1...3.0.2) (2022-01-24)
[Whats new in HTTPie for Terminal 3.0 →](https://httpie.io/blog/httpie-3.0.0) [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)) - Fixed usage of `httpie` when there is a presence of a config with `default_options`. ([#1280](https://github.com/httpie/cli/pull/1280))
## [3.0.1](https://github.com/httpie/httpie/compare/3.0.0...3.0.1) (2022-01-23) ## [3.0.1](https://github.com/httpie/cli/compare/3.0.0...3.0.1) (2022-01-23)
[Whats new in HTTPie for Terminal 3.0 →](https://httpie.io/blog/httpie-3.0.0) [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)) - Changed the value shown as time elapsed from time-to-read-headers to total exchange time. ([#1277](https://github.com/httpie/cli/issues/1277))
## [3.0.0](https://github.com/httpie/httpie/compare/2.6.0...3.0.0) (2022-01-21) ## [3.0.0](https://github.com/httpie/cli/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) [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)) - Dropped support for Python 3.6. ([#1177](https://github.com/httpie/cli/issues/1177))
- Improved startup time by 40%. ([#1211](https://github.com/httpie/httpie/pull/1211)) - Improved startup time by 40%. ([#1211](https://github.com/httpie/cli/pull/1211))
- Added support for nested JSON syntax. ([#1169](https://github.com/httpie/httpie/issues/1169)) - Added support for nested JSON syntax. ([#1169](https://github.com/httpie/cli/issues/1169))
- Added `httpie plugins` interface for plugin management. ([#566](https://github.com/httpie/httpie/issues/566)) - Added `httpie plugins` interface for plugin management. ([#566](https://github.com/httpie/cli/issues/566))
- Added support for Bearer authentication via `--auth-type=bearer` ([#1215](https://github.com/httpie/httpie/issues/1215)). - Added support for Bearer authentication via `--auth-type=bearer` ([#1215](https://github.com/httpie/cli/issues/1215)).
- Added support for quick conversions of pasted URLs into HTTPie calls by adding a space after the protocol name (`$ https ://pie.dev``https://pie.dev`). ([#1195](https://github.com/httpie/httpie/issues/1195)) - Added support for quick conversions of pasted URLs into HTTPie calls by adding a space after the protocol name (`$ https ://pie.dev``https://pie.dev`). ([#1195](https://github.com/httpie/cli/issues/1195))
- Added support for _sending_ multiple HTTP header lines with the same name. ([#130](https://github.com/httpie/httpie/issues/130)) - Added support for _sending_ multiple HTTP header lines with the same name. ([#130](https://github.com/httpie/cli/issues/130))
- Added support for _receiving_ multiple HTTP headers lines with the same name. ([#1207](https://github.com/httpie/httpie/issues/1207)) - Added support for _receiving_ multiple HTTP headers lines with the same name. ([#1207](https://github.com/httpie/cli/issues/1207))
- Added support for basic JSON types on `--form`/`--multipart` when using JSON only operators (`:=`/`:=@`). ([#1212](https://github.com/httpie/httpie/issues/1212)) - Added support for basic JSON types on `--form`/`--multipart` when using JSON only operators (`:=`/`:=@`). ([#1212](https://github.com/httpie/cli/issues/1212))
- Added support for automatically enabling `--stream` when `Content-Type` is `text/event-stream`. ([#376](https://github.com/httpie/httpie/issues/376)) - Added support for automatically enabling `--stream` when `Content-Type` is `text/event-stream`. ([#376](https://github.com/httpie/cli/issues/376))
- Added support for displaying the total elapsed time through `--meta`/`-vv` or `--print=m`. ([#243](https://github.com/httpie/httpie/issues/243)) - Added support for displaying the total elapsed time through `--meta`/`-vv` or `--print=m`. ([#243](https://github.com/httpie/cli/issues/243))
- Added new `pie-dark`/`pie-light` (and `pie`) styles that match with [HTTPie for Web and Desktop](https://httpie.io/product). ([#1237](https://github.com/httpie/httpie/issues/1237)) - Added new `pie-dark`/`pie-light` (and `pie`) styles that match with [HTTPie for Web and Desktop](https://httpie.io/product). ([#1237](https://github.com/httpie/cli/issues/1237))
- Added support for better error handling on DNS failures. ([#1248](https://github.com/httpie/httpie/issues/1248)) - Added support for better error handling on DNS failures. ([#1248](https://github.com/httpie/cli/issues/1248))
- Added support for storing prompted passwords in the local sessions. ([#1098](https://github.com/httpie/httpie/issues/1098)) - Added support for storing prompted passwords in the local sessions. ([#1098](https://github.com/httpie/cli/issues/1098))
- Added warnings about the `--ignore-stdin`, when there is no incoming data from stdin. ([#1255](https://github.com/httpie/httpie/issues/1255)) - Added warnings about the `--ignore-stdin`, when there is no incoming data from stdin. ([#1255](https://github.com/httpie/cli/issues/1255))
- Fixed crashing due to broken plugins. ([#1204](https://github.com/httpie/httpie/issues/1204)) - Fixed crashing due to broken plugins. ([#1204](https://github.com/httpie/cli/issues/1204))
- Fixed auto addition of XML declaration to every formatted XML response. ([#1156](https://github.com/httpie/httpie/issues/1156)) - Fixed auto addition of XML declaration to every formatted XML response. ([#1156](https://github.com/httpie/cli/issues/1156))
- Fixed highlighting when `Content-Type` specifies `charset`. ([#1242](https://github.com/httpie/httpie/issues/1242)) - Fixed highlighting when `Content-Type` specifies `charset`. ([#1242](https://github.com/httpie/cli/issues/1242))
- Fixed an unexpected crash when `--raw` is used with `--chunked`. ([#1253](https://github.com/httpie/httpie/issues/1253)) - Fixed an unexpected crash when `--raw` is used with `--chunked`. ([#1253](https://github.com/httpie/cli/issues/1253))
- Changed the default Windows theme from `fruity` to `auto`. ([#1266](https://github.com/httpie/httpie/issues/1266)) - Changed the default Windows theme from `fruity` to `auto`. ([#1266](https://github.com/httpie/cli/issues/1266))
## [2.6.0](https://github.com/httpie/httpie/compare/2.5.0...2.6.0) (2021-10-14) ## [2.6.0](https://github.com/httpie/cli/compare/2.5.0...2.6.0) (2021-10-14)
[Whats new in HTTPie for Terminal 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 support for formatting & coloring of JSON bodies preceded by non-JSON data (e.g., an XXSI prefix). ([#1130](https://github.com/httpie/cli/issues/1130))
- Added charset auto-detection when `Content-Type` doesnt include it. ([#1110](https://github.com/httpie/httpie/issues/1110), [#1168](https://github.com/httpie/httpie/issues/1168)) - Added charset auto-detection when `Content-Type` doesnt include it. ([#1110](https://github.com/httpie/cli/issues/1110), [#1168](https://github.com/httpie/cli/issues/1168))
- Added `--response-charset` to allow overriding the response encoding for terminal display purposes. ([#1168](https://github.com/httpie/httpie/issues/1168)) - Added `--response-charset` to allow overriding the response encoding for terminal display purposes. ([#1168](https://github.com/httpie/cli/issues/1168))
- Added `--response-mime` to allow overriding the response mime type for coloring and formatting for the terminal. ([#1168](https://github.com/httpie/httpie/issues/1168)) - Added `--response-mime` to allow overriding the response mime type for coloring and formatting for the terminal. ([#1168](https://github.com/httpie/cli/issues/1168))
- Added the ability to silence warnings through using `-q` or `--quiet` twice (e.g. `-qq`) ([#1175](https://github.com/httpie/httpie/issues/1175)) - Added the ability to silence warnings through using `-q` or `--quiet` twice (e.g. `-qq`) ([#1175](https://github.com/httpie/cli/issues/1175))
- Added installed plugin list to `--debug` output. ([#1165](https://github.com/httpie/httpie/issues/1165)) - Added installed plugin list to `--debug` output. ([#1165](https://github.com/httpie/cli/issues/1165))
- Fixed duplicate keys preservation in JSON data. ([#1163](https://github.com/httpie/httpie/issues/1163)) - Fixed duplicate keys preservation in JSON data. ([#1163](https://github.com/httpie/cli/issues/1163))
## [2.5.0](https://github.com/httpie/httpie/compare/2.4.0...2.5.0) (2021-09-06) ## [2.5.0](https://github.com/httpie/cli/compare/2.4.0...2.5.0) (2021-09-06)
[Whats new in HTTPie for Terminal 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 - Added `--raw` to allow specifying the raw request body without extra processing as
an alternative to `stdin`. ([#534](https://github.com/httpie/httpie/issues/534)) an alternative to `stdin`. ([#534](https://github.com/httpie/cli/issues/534))
- Added support for XML formatting. ([#1129](https://github.com/httpie/httpie/issues/1129)) - Added support for XML formatting. ([#1129](https://github.com/httpie/cli/issues/1129))
- Added internal support for file-like object responses to improve adapter plugin support. ([#1094](https://github.com/httpie/httpie/issues/1094)) - Added internal support for file-like object responses to improve adapter plugin support. ([#1094](https://github.com/httpie/cli/issues/1094))
- Fixed `--continue --download` with a single byte to be downloaded left. ([#1032](https://github.com/httpie/httpie/issues/1032)) - Fixed `--continue --download` with a single byte to be downloaded left. ([#1032](https://github.com/httpie/cli/issues/1032))
- Fixed `--verbose` HTTP 307 redirects with streamed request body. ([#1088](https://github.com/httpie/httpie/issues/1088)) - Fixed `--verbose` HTTP 307 redirects with streamed request body. ([#1088](https://github.com/httpie/cli/issues/1088))
- Fixed handling of session files with `Cookie:` followed by other headers. ([#1126](https://github.com/httpie/httpie/issues/1126)) - Fixed handling of session files with `Cookie:` followed by other headers. ([#1126](https://github.com/httpie/cli/issues/1126))
## [2.4.0](https://github.com/httpie/httpie/compare/2.3.0...2.4.0) (2021-02-06) ## [2.4.0](https://github.com/httpie/cli/compare/2.3.0...2.4.0) (2021-02-06)
- Added support for `--session` cookie expiration based on `Set-Cookie: max-age=<n>`. ([#1029](https://github.com/httpie/httpie/issues/1029)) - Added support for `--session` cookie expiration based on `Set-Cookie: max-age=<n>`. ([#1029](https://github.com/httpie/cli/issues/1029))
- Show a `--check-status` warning with `--quiet` as well, not only when the output is redirected. ([#1026](https://github.com/httpie/httpie/issues/1026)) - Show a `--check-status` warning with `--quiet` as well, not only when the output is redirected. ([#1026](https://github.com/httpie/cli/issues/1026))
- Fixed upload with `--session` ([#1020](https://github.com/httpie/httpie/issues/1020)). - Fixed upload with `--session` ([#1020](https://github.com/httpie/cli/issues/1020)).
- Fixed a missing blank line between request and response ([#1006](https://github.com/httpie/httpie/issues/1006)). - Fixed a missing blank line between request and response ([#1006](https://github.com/httpie/cli/issues/1006)).
## [2.3.0](https://github.com/httpie/httpie/compare/2.2.0...2.3.0) (2020-10-25) ## [2.3.0](https://github.com/httpie/cli/compare/2.2.0...2.3.0) (2020-10-25)
- Added support for streamed uploads ([#201](https://github.com/httpie/httpie/issues/201)). - Added support for streamed uploads ([#201](https://github.com/httpie/cli/issues/201)).
- Added support for multipart upload streaming ([#684](https://github.com/httpie/httpie/issues/684)). - Added support for multipart upload streaming ([#684](https://github.com/httpie/cli/issues/684)).
- Added support for body-from-file upload streaming (`http pie.dev/post @file`). - Added support for body-from-file upload streaming (`http pie.dev/post @file`).
- Added `--chunked` to enable chunked transfer encoding ([#753](https://github.com/httpie/httpie/issues/753)). - Added `--chunked` to enable chunked transfer encoding ([#753](https://github.com/httpie/cli/issues/753)).
- Added `--multipart` to allow `multipart/form-data` encoding for non-file `--form` requests as well. - Added `--multipart` to allow `multipart/form-data` encoding for non-file `--form` requests as well.
- Added support for preserving field order in multipart requests ([#903](https://github.com/httpie/httpie/issues/903)). - Added support for preserving field order in multipart requests ([#903](https://github.com/httpie/cli/issues/903)).
- Added `--boundary` to allow a custom boundary string for `multipart/form-data` requests. - Added `--boundary` to allow a custom boundary string for `multipart/form-data` requests.
- Added support for combining cookies specified on the CLI and in a session file ([#932](https://github.com/httpie/httpie/issues/932)). - Added support for combining cookies specified on the CLI and in a session file ([#932](https://github.com/httpie/cli/issues/932)).
- Added out of the box SOCKS support with no extra installation ([#904](https://github.com/httpie/httpie/issues/904)). - Added out of the box SOCKS support with no extra installation ([#904](https://github.com/httpie/cli/issues/904)).
- Added `--quiet, -q` flag to enforce silent behaviour. - Added `--quiet, -q` flag to enforce silent behaviour.
- Fixed the handling of invalid `expires` dates in `Set-Cookie` headers ([#963](https://github.com/httpie/httpie/issues/963)). - Fixed the handling of invalid `expires` dates in `Set-Cookie` headers ([#963](https://github.com/httpie/cli/issues/963)).
- Removed Tox testing entirely ([#943](https://github.com/httpie/httpie/issues/943)). - Removed Tox testing entirely ([#943](https://github.com/httpie/cli/issues/943)).
## [2.2.0](https://github.com/httpie/httpie/compare/2.1.0...2.2.0) (2020-06-18) ## [2.2.0](https://github.com/httpie/cli/compare/2.1.0...2.2.0) (2020-06-18)
- Added support for custom content types for uploaded files ([#668](https://github.com/httpie/httpie/issues/668)). - Added support for custom content types for uploaded files ([#668](https://github.com/httpie/cli/issues/668)).
- Added support for `$XDG_CONFIG_HOME` ([#920](https://github.com/httpie/httpie/issues/920)). - Added support for `$XDG_CONFIG_HOME` ([#920](https://github.com/httpie/cli/issues/920)).
- Added support for `Set-Cookie`-triggered cookie expiration ([#853](https://github.com/httpie/httpie/issues/853)). - Added support for `Set-Cookie`-triggered cookie expiration ([#853](https://github.com/httpie/cli/issues/853)).
- Added `--format-options` to allow disabling sorting, etc. ([#128](https://github.com/httpie/httpie/issues/128)) - Added `--format-options` to allow disabling sorting, etc. ([#128](https://github.com/httpie/cli/issues/128))
- Added `--sorted` and `--unsorted` shortcuts for (un)setting all sorting-related `--format-options`. ([#128](https://github.com/httpie/httpie/issues/128)) - Added `--sorted` and `--unsorted` shortcuts for (un)setting all sorting-related `--format-options`. ([#128](https://github.com/httpie/cli/issues/128))
- Added `--ciphers` to allow configuring OpenSSL ciphers ([#870](https://github.com/httpie/httpie/issues/870)). - Added `--ciphers` to allow configuring OpenSSL ciphers ([#870](https://github.com/httpie/cli/issues/870)).
- Added `netrc` support for auth plugins. Enabled for `--auth-type=basic` - Added `netrc` support for auth plugins. Enabled for `--auth-type=basic`
and `digest`, 3rd parties may opt in ([#718](https://github.com/httpie/httpie/issues/718), [#719](https://github.com/httpie/httpie/issues/719), [#852](https://github.com/httpie/httpie/issues/852), [#934](https://github.com/httpie/httpie/issues/934)). and `digest`, 3rd parties may opt in ([#718](https://github.com/httpie/cli/issues/718), [#719](https://github.com/httpie/cli/issues/719), [#852](https://github.com/httpie/cli/issues/852), [#934](https://github.com/httpie/cli/issues/934)).
- Fixed built-in plugins-related circular imports ([#925](https://github.com/httpie/httpie/issues/925)). - Fixed built-in plugins-related circular imports ([#925](https://github.com/httpie/cli/issues/925)).
## [2.1.0](https://github.com/httpie/httpie/compare/2.0.0...2.1.0) (2020-04-18) ## [2.1.0](https://github.com/httpie/cli/compare/2.0.0...2.1.0) (2020-04-18)
- Added `--path-as-is` to bypass dot segment (`/../` or `/./`) - Added `--path-as-is` to bypass dot segment (`/../` or `/./`)
URL squashing ([#895](https://github.com/httpie/httpie/issues/895)). URL squashing ([#895](https://github.com/httpie/cli/issues/895)).
- Changed the default `Accept` header value for JSON requests from - Changed the default `Accept` header value for JSON requests from
`application/json, */*` to `application/json, */*;q=0.5` `application/json, */*` to `application/json, */*;q=0.5`
to clearly indicate preference ([#488](https://github.com/httpie/httpie/issues/488)). to clearly indicate preference ([#488](https://github.com/httpie/cli/issues/488)).
- Fixed `--form` file upload mixed with redirected `stdin` error handling - Fixed `--form` file upload mixed with redirected `stdin` error handling
([#840](https://github.com/httpie/httpie/issues/840)). ([#840](https://github.com/httpie/cli/issues/840)).
## [2.0.0](https://github.com/httpie/httpie/compare/1.0.3...2.0.0) (2020-01-12) ## [2.0.0](https://github.com/httpie/cli/compare/1.0.3...2.0.0) (2020-01-12)
- Removed Python 2.7 support ([EOL Jan 2020](https://www.python.org/doc/sunset-python-2/). - Removed Python 2.7 support ([EOL Jan 2020](https://www.python.org/doc/sunset-python-2/).
- Added `--offline` to allow building an HTTP request and printing it but not - Added `--offline` to allow building an HTTP request and printing it but not
@ -143,7 +175,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Fixed an error when `stdin` was a closed fd. - Fixed an error when `stdin` was a closed fd.
- Improved `--debug` output formatting. - Improved `--debug` output formatting.
## [1.0.3](https://github.com/httpie/httpie/compare/1.0.2...1.0.3) (2019-08-26) ## [1.0.3](https://github.com/httpie/cli/compare/1.0.2...1.0.3) (2019-08-26)
- Fixed CVE-2019-10751 — the way the output filename is generated for - Fixed CVE-2019-10751 — the way the output filename is generated for
`--download` requests without `--output` resulting in a redirect has `--download` requests without `--output` resulting in a redirect has
@ -169,15 +201,15 @@ This project adheres to [Semantic Versioning](https://semver.org/).
Reported by Raul Onitza and Giulio Comi. Reported by Raul Onitza and Giulio Comi.
## [1.0.2](https://github.com/httpie/httpie/compare/1.0.1...1.0.2) (2018-11-14) ## [1.0.2](https://github.com/httpie/cli/compare/1.0.1...1.0.2) (2018-11-14)
- Fixed tests for installation with pyOpenSSL. - Fixed tests for installation with pyOpenSSL.
## [1.0.1](https://github.com/httpie/httpie/compare/1.0.0...1.0.1) (2018-11-14) ## [1.0.1](https://github.com/httpie/cli/compare/1.0.0...1.0.1) (2018-11-14)
- Removed external URL calls from tests. - Removed external URL calls from tests.
## [1.0.0](https://github.com/httpie/httpie/compare/0.9.9...1.0.0) (2018-11-02) ## [1.0.0](https://github.com/httpie/cli/compare/0.9.9...1.0.0) (2018-11-02)
- Added `--style=auto` which follows the terminal ANSI color styles. - Added `--style=auto` which follows the terminal ANSI color styles.
- Added support for selecting TLS 1.3 via `--ssl=tls1.3` - Added support for selecting TLS 1.3 via `--ssl=tls1.3`
@ -188,11 +220,11 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Fixed default headers being incorrectly case-sensitive. - Fixed default headers being incorrectly case-sensitive.
- Removed Python 2.6 support. - Removed Python 2.6 support.
## [0.9.9](https://github.com/httpie/httpie/compare/0.9.8...0.9.9) (2016-12-08) ## [0.9.9](https://github.com/httpie/cli/compare/0.9.8...0.9.9) (2016-12-08)
- Fixed README. - Fixed README.
## [0.9.8](https://github.com/httpie/httpie/compare/0.9.6...0.9.8) (2016-12-08) ## [0.9.8](https://github.com/httpie/cli/compare/0.9.6...0.9.8) (2016-12-08)
- Extended auth plugin API. - Extended auth plugin API.
- Added exit status code `7` for plugin errors. - Added exit status code `7` for plugin errors.
@ -201,7 +233,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Improved `CTRL-C` interrupt handling. - Improved `CTRL-C` interrupt handling.
- Added the standard exit status code `130` for keyboard interrupts. - Added the standard exit status code `130` for keyboard interrupts.
## [0.9.6](https://github.com/httpie/httpie/compare/0.9.4...0.9.6) (2016-08-13) ## [0.9.6](https://github.com/httpie/cli/compare/0.9.4...0.9.6) (2016-08-13)
- Added Python 3 as a dependency for Homebrew installations - Added Python 3 as a dependency for Homebrew installations
to ensure some of the newer HTTP features work out of the box to ensure some of the newer HTTP features work out of the box
@ -220,7 +252,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Changed the pre-processing of request HTTP headers so that any leading - Changed the pre-processing of request HTTP headers so that any leading
and trailing whitespace is removed. and trailing whitespace is removed.
## [0.9.4](https://github.com/httpie/httpie/compare/0.9.3...0.9.4) (2016-07-01) ## [0.9.4](https://github.com/httpie/cli/compare/0.9.3...0.9.4) (2016-07-01)
- Added `Content-Type` of files uploaded in `multipart/form-data` requests - Added `Content-Type` of files uploaded in `multipart/form-data` requests
- Added `--ssl=<PROTOCOL>` to specify the desired SSL/TLS protocol version - Added `--ssl=<PROTOCOL>` to specify the desired SSL/TLS protocol version
@ -244,7 +276,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Fixed the handling of `Content-Type` with multiple `+subtype` parts - Fixed the handling of `Content-Type` with multiple `+subtype` parts
- Removed the XML formatter as the implementation suffered from multiple issues - Removed the XML formatter as the implementation suffered from multiple issues
## [0.9.3](https://github.com/httpie/httpie/compare/0.9.2...0.9.3) (2016-01-01) ## [0.9.3](https://github.com/httpie/cli/compare/0.9.2...0.9.3) (2016-01-01)
- Changed the default color `--style` from `solarized` to `monokai` - Changed the default color `--style` from `solarized` to `monokai`
- Added basic Bash autocomplete support (need to be installed manually) - Added basic Bash autocomplete support (need to be installed manually)
@ -254,19 +286,19 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Fixed colors and formatting on Windows - Fixed colors and formatting on Windows
- Fixed `--auth` prompt on Windows - Fixed `--auth` prompt on Windows
## [0.9.2](https://github.com/httpie/httpie/compare/0.9.1...0.9.2) (2015-02-24) ## [0.9.2](https://github.com/httpie/cli/compare/0.9.1...0.9.2) (2015-02-24)
- Fixed compatibility with Requests 2.5.1 - Fixed compatibility with Requests 2.5.1
- Changed the default JSON `Content-Type` to `application/json` as UTF-8 - Changed the default JSON `Content-Type` to `application/json` as UTF-8
is the default JSON encoding is the default JSON encoding
## [0.9.1](https://github.com/httpie/httpie/compare/0.9.0...0.9.1) (2015-02-07) ## [0.9.1](https://github.com/httpie/cli/compare/0.9.0...0.9.1) (2015-02-07)
- Added support for Requests transport adapter plugins - Added support for Requests transport adapter plugins
(see [httpie-unixsocket](https://github.com/httpie/httpie-unixsocket) (see [httpie-unixsocket](https://github.com/httpie/httpie-unixsocket)
and [httpie-http2](https://github.com/httpie/httpie-http2)) and [httpie-http2](https://github.com/httpie/httpie-http2))
## [0.9.0](https://github.com/httpie/httpie/compare/0.8.0...0.9.0) (2015-01-31) ## [0.9.0](https://github.com/httpie/cli/compare/0.8.0...0.9.0) (2015-01-31)
- Added `--cert` and `--cert-key` parameters to specify a client side - Added `--cert` and `--cert-key` parameters to specify a client side
certificate and private key for SSL certificate and private key for SSL
@ -285,7 +317,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Fixed `--output=/dev/null` on Linux - Fixed `--output=/dev/null` on Linux
- Miscellaneous bugfixes - Miscellaneous bugfixes
## [0.8.0](https://github.com/httpie/httpie/compare/0.7.1...0.8.0) (2014-01-25) ## [0.8.0](https://github.com/httpie/cli/compare/0.7.1...0.8.0) (2014-01-25)
- Added `field=@file.txt` and `field:=@file.json` for embedding - Added `field=@file.txt` and `field:=@file.json` for embedding
the contents of text and JSON files into request data the contents of text and JSON files into request data
@ -293,7 +325,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Fixed request `Host` header value output so that it doesn't contain - Fixed request `Host` header value output so that it doesn't contain
credentials, if included in the URL credentials, if included in the URL
## [0.7.1](https://github.com/httpie/httpie/compare/0.6.0...0.7.1) (2013-09-24) ## [0.7.1](https://github.com/httpie/cli/compare/0.6.0...0.7.1) (2013-09-24)
- Added `--ignore-stdin` - Added `--ignore-stdin`
- Added support for auth plugins - Added support for auth plugins
@ -301,27 +333,27 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Improved `Content-Disposition` parsing for `--download` mode - Improved `Content-Disposition` parsing for `--download` mode
- Update to Requests 2.0.0 - Update to Requests 2.0.0
## [0.6.0](https://github.com/httpie/httpie/compare/0.5.1...0.6.0) (2013-06-03) ## [0.6.0](https://github.com/httpie/cli/compare/0.5.1...0.6.0) (2013-06-03)
- XML data is now formatted - XML data is now formatted
- `--session` and `--session-read-only` now also accept paths to - `--session` and `--session-read-only` now also accept paths to
session files (eg. `http --session=/tmp/session.json example.org`) session files (eg. `http --session=/tmp/session.json example.org`)
## [0.5.1](https://github.com/httpie/httpie/compare/0.5.0...0.5.1) (2013-05-13) ## [0.5.1](https://github.com/httpie/cli/compare/0.5.0...0.5.1) (2013-05-13)
- `Content-*` and `If-*` request headers are not stored in sessions - `Content-*` and `If-*` request headers are not stored in sessions
anymore as they are request-specific anymore as they are request-specific
## [0.5.0](https://github.com/httpie/httpie/compare/0.4.1...0.5.0) (2013-04-27) ## [0.5.0](https://github.com/httpie/cli/compare/0.4.1...0.5.0) (2013-04-27)
- Added a download mode via `--download` - Added a download mode via `--download`
- Fixes miscellaneous bugs - Fixes miscellaneous bugs
## [0.4.1](https://github.com/httpie/httpie/compare/0.4.0...0.4.1) (2013-02-26) ## [0.4.1](https://github.com/httpie/cli/compare/0.4.0...0.4.1) (2013-02-26)
- Fixed `setup.py` - Fixed `setup.py`
## [0.4.0](https://github.com/httpie/httpie/compare/0.3.0...0.4.0) (2013-02-22) ## [0.4.0](https://github.com/httpie/cli/compare/0.3.0...0.4.0) (2013-02-22)
- Added Python 3.3 compatibility - Added Python 3.3 compatibility
- Added Requests >= v1.0.4 compatibility - Added Requests >= v1.0.4 compatibility
@ -330,7 +362,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Mutually exclusive arguments can be specified multiple times. The - Mutually exclusive arguments can be specified multiple times. The
last value is used last value is used
## [0.3.0](https://github.com/httpie/httpie/compare/0.2.7...0.3.0) (2012-09-21) ## [0.3.0](https://github.com/httpie/cli/compare/0.2.7...0.3.0) (2012-09-21)
- Allow output redirection on Windows - Allow output redirection on Windows
- Added configuration file - Added configuration file
@ -345,7 +377,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
(`--pretty=all`, `--pretty=colors` and `--pretty=format`) (`--pretty=all`, `--pretty=colors` and `--pretty=format`)
`--ugly` has bee removed in favor of `--pretty=none` `--ugly` has bee removed in favor of `--pretty=none`
## [0.2.7](https://github.com/httpie/httpie/compare/0.2.5...0.2.7) (2012-08-07) ## [0.2.7](https://github.com/httpie/cli/compare/0.2.5...0.2.7) (2012-08-07)
- Added compatibility with Requests 0.13.6 - Added compatibility with Requests 0.13.6
- Added streamed terminal output. `--stream, -S` can be used to enable - Added streamed terminal output. `--stream, -S` can be used to enable
@ -362,7 +394,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Fixed printing of `multipart/form-data` requests - Fixed printing of `multipart/form-data` requests
- Renamed `--traceback` to `--debug` - Renamed `--traceback` to `--debug`
## [0.2.6](https://github.com/httpie/httpie/compare/0.2.5...0.2.6) (2012-07-26) ## [0.2.6](https://github.com/httpie/cli/compare/0.2.5...0.2.6) (2012-07-26)
- The short option for `--headers` is now `-h` (`-t` has been - The short option for `--headers` is now `-h` (`-t` has been
removed, for usage use `--help`) removed, for usage use `--help`)
@ -377,7 +409,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Added query string parameters (`param==value`) - Added query string parameters (`param==value`)
- Added support for terminal colors under Windows - Added support for terminal colors under Windows
## [0.2.5](https://github.com/httpie/httpie/compare/0.2.2...0.2.5) (2012-07-17) ## [0.2.5](https://github.com/httpie/cli/compare/0.2.2...0.2.5) (2012-07-17)
- Unicode characters in prettified JSON now don't get escaped for - Unicode characters in prettified JSON now don't get escaped for
improved readability improved readability
@ -388,20 +420,20 @@ This project adheres to [Semantic Versioning](https://semver.org/).
`--verbose` `--verbose`
- Fixed Content-Type for requests with no data - Fixed Content-Type for requests with no data
## [0.2.2](https://github.com/httpie/httpie/compare/0.2.1...0.2.2) (2012-06-24) ## [0.2.2](https://github.com/httpie/cli/compare/0.2.1...0.2.2) (2012-06-24)
- The `METHOD` positional argument can now be omitted (defaults to - The `METHOD` positional argument can now be omitted (defaults to
`GET`, or to `POST` with data) `GET`, or to `POST` with data)
- Fixed --verbose --form - Fixed --verbose --form
- Added support for Tox - Added support for Tox
## [0.2.1](https://github.com/httpie/httpie/compare/0.2.0...0.2.1) (2012-06-13) ## [0.2.1](https://github.com/httpie/cli/compare/0.2.0...0.2.1) (2012-06-13)
- Added compatibility with `requests-0.12.1` - Added compatibility with `requests-0.12.1`
- Dropped custom JSON and HTTP lexers in favor of the ones newly included - Dropped custom JSON and HTTP lexers in favor of the ones newly included
in `pygments-1.5` in `pygments-1.5`
## [0.2.0](https://github.com/httpie/httpie/compare/0.1.6...0.2.0) (2012-04-25) ## [0.2.0](https://github.com/httpie/cli/compare/0.1.6...0.2.0) (2012-04-25)
- Added Python 3 support - Added Python 3 support
- Added the ability to print the HTTP request as well as the response - Added the ability to print the HTTP request as well as the response
@ -413,18 +445,18 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Added support for field name escaping - Added support for field name escaping
- Many bug fixes - Many bug fixes
## [0.1.6](https://github.com/httpie/httpie/compare/0.1.5...0.1.6) (2012-03-04) ## [0.1.6](https://github.com/httpie/cli/compare/0.1.5...0.1.6) (2012-03-04)
- Fixed `setup.py` - Fixed `setup.py`
## [0.1.5](https://github.com/httpie/httpie/compare/0.1.4...0.1.5) (2012-03-04) ## [0.1.5](https://github.com/httpie/cli/compare/0.1.4...0.1.5) (2012-03-04)
- Many improvements and bug fixes - Many improvements and bug fixes
## [0.1.4](https://github.com/httpie/httpie/compare/b966efa...0.1.4) (2012-02-28) ## [0.1.4](https://github.com/httpie/cli/compare/b966efa...0.1.4) (2012-02-28)
- Many improvements and bug fixes - Many improvements and bug fixes
## [0.1.0](https://github.com/httpie/httpie/commit/b966efa) (2012-02-25) ## [0.1.0](https://github.com/httpie/cli/commit/b966efa) (2012-02-25)
- Initial public release - Initial public release

View File

@ -19,7 +19,7 @@ $ http --debug <COMPLETE ARGUMENT LIST THAT TRIGGERS THE ERROR>
## 2. Contributing Code and Docs ## 2. Contributing Code and Docs
Before working on a new feature or a bug, please browse [existing issues](https://github.com/httpie/httpie/issues) Before working on a new feature or a bug, please browse [existing issues](https://github.com/httpie/cli/issues)
to see whether it has previously been discussed. to see whether it has previously been discussed.
If your change alters HTTPies behaviour or interface, it's a good idea to If your change alters HTTPies behaviour or interface, it's a good idea to
@ -38,13 +38,13 @@ for existing-yet-previously-untested behavior will very likely be merged.
Therefore, docs and tests improvements are a great candidate for your first Therefore, docs and tests improvements are a great candidate for your first
contribution. contribution.
Consider also adding a [CHANGELOG](https://github.com/httpie/httpie/blob/master/CHANGELOG.md) entry for your changes. Consider also adding a [CHANGELOG](https://github.com/httpie/cli/blob/master/CHANGELOG.md) entry for your changes.
### Development Environment ### Development Environment
#### Getting the code #### Getting the code
Go to <https://github.com/httpie/httpie> and fork the project repository. Go to <https://github.com/httpie/cli> and fork the project repository.
```bash ```bash
# Clone your fork # Clone your fork
@ -59,8 +59,10 @@ $ git checkout -b my_topical_branch
#### Setup #### Setup
The [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) contains a bunch of tasks to get you started. Just run The [Makefile](https://github.com/httpie/cli/blob/master/Makefile) contains a bunch of tasks to get you started.
the following command, which: You can run `$ make` to see all the available tasks.
To get started, run the command below, which:
- Creates an isolated Python virtual environment inside `./venv` - Creates an isolated Python virtual environment inside `./venv`
(via the standard library [venv](https://docs.python.org/3/library/venv.html) tool); (via the standard library [venv](https://docs.python.org/3/library/venv.html) tool);
@ -70,7 +72,7 @@ the following command, which:
- and runs tests (It is the same as running `make install test`). - and runs tests (It is the same as running `make install test`).
```bash ```bash
$ make $ make all
``` ```
#### Python virtual environment #### Python virtual environment
@ -110,7 +112,7 @@ and that `make pycodestyle` passes.
Please add tests for any new features and bug fixes. Please add tests for any new features and bug fixes.
When you open a Pull Request, [GitHub Actions](https://github.com/httpie/httpie/actions) will automatically run HTTPies [test suite](https://github.com/httpie/httpie/tree/master/tests) against your code, so please make sure all checks pass. When you open a Pull Request, [GitHub Actions](https://github.com/httpie/cli/actions) will automatically run HTTPies [test suite](https://github.com/httpie/cli/tree/master/tests) against your code, so please make sure all checks pass.
#### Running tests locally #### Running tests locally
@ -142,7 +144,7 @@ $ python -m pytest tests/test_uploads.py::TestMultipartFormDataFileUpload
$ python -m pytest tests/test_uploads.py::TestMultipartFormDataFileUpload::test_upload_ok $ python -m pytest tests/test_uploads.py::TestMultipartFormDataFileUpload::test_upload_ok
``` ```
See [Makefile](https://github.com/httpie/httpie/blob/master/Makefile) for additional development utilities. See [Makefile](https://github.com/httpie/cli/blob/master/Makefile) for additional development utilities.
#### Running benchmarks #### Running benchmarks
@ -152,7 +154,7 @@ with the master branch of your repository (or a fresh checkout of HTTPie master,
`--fresh`) and report the results back. `--fresh`) and report the results back.
```bash ```bash
$ python extras/benchmarks/run.py $ python extras/profiling/run.py
``` ```
The benchmarks can also be run on the CI. Since it is a long process, it requires manual The benchmarks can also be run on the CI. Since it is a long process, it requires manual
@ -207,4 +209,4 @@ $ python -m pytest
______________________________________________________________________ ______________________________________________________________________
Finally, feel free to add yourself to [AUTHORS](https://github.com/httpie/httpie/blob/master/AUTHORS.md)! Finally, feel free to add yourself to [AUTHORS](https://github.com/httpie/cli/blob/master/AUTHORS.md)!

View File

@ -4,5 +4,5 @@ include CHANGELOG.md
include AUTHORS.md include AUTHORS.md
include docs/README.md include docs/README.md
# <https://github.com/httpie/httpie/issues/182> # <https://github.com/httpie/cli/issues/182>
recursive-include tests/ * recursive-include tests/ *

View File

@ -22,6 +22,26 @@ VENV_PYTHON=$(VENV_BIN)/python
export PATH := $(VENV_BIN):$(PATH) export PATH := $(VENV_BIN):$(PATH)
default: list-tasks
###############################################################################
# Default task to get a list of tasks when `make' is run without args.
# <https://stackoverflow.com/questions/4219255>
###############################################################################
list-tasks:
@echo Available tasks:
@echo ----------------
@$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | grep -E -v -e '^[^[:alnum:]]' -e '^$@$$'
@echo
###############################################################################
# Installation
###############################################################################
all: uninstall-httpie install test all: uninstall-httpie install test
@ -30,10 +50,10 @@ install: venv install-reqs
install-reqs: install-reqs:
@echo $(H1)Updating package tools$(H1END) @echo $(H1)Updating package tools$(H1END)
$(VENV_PIP) install --upgrade pip wheel $(VENV_PIP) install --upgrade pip wheel build
@echo $(H1)Installing dev requirements$(H1END) @echo $(H1)Installing dev requirements$(H1END)
$(VENV_PIP) install --upgrade --editable '.[dev]' $(VENV_PIP) install --upgrade '.[dev]' '.[test]'
@echo $(H1)Installing HTTPie$(H1END) @echo $(H1)Installing HTTPie$(H1END)
$(VENV_PIP) install --upgrade --editable . $(VENV_PIP) install --upgrade --editable .
@ -104,7 +124,8 @@ test-dist: test-sdist test-bdist-wheel
test-sdist: clean venv test-sdist: clean venv
@echo $(H1)Testing sdist build an installation$(H1END) @echo $(H1)Testing sdist build an installation$(H1END)
$(VENV_PYTHON) setup.py sdist $(VENV_PIP) install build
$(VENV_PYTHON) -m build --sdist
$(VENV_PIP) install --force-reinstall --upgrade dist/*.gz $(VENV_PIP) install --force-reinstall --upgrade dist/*.gz
$(VENV_BIN)/http --version $(VENV_BIN)/http --version
@echo @echo
@ -112,8 +133,8 @@ test-sdist: clean venv
test-bdist-wheel: clean venv test-bdist-wheel: clean venv
@echo $(H1)Testing wheel build an installation$(H1END) @echo $(H1)Testing wheel build an installation$(H1END)
$(VENV_PIP) install wheel $(VENV_PIP) install build
$(VENV_PYTHON) setup.py bdist_wheel $(VENV_PYTHON) -m build --wheel
$(VENV_PIP) install --force-reinstall --upgrade dist/*.whl $(VENV_PIP) install --force-reinstall --upgrade dist/*.whl
$(VENV_BIN)/http --version $(VENV_BIN)/http --version
@echo @echo
@ -147,19 +168,17 @@ doc-check:
mdl --git-recurse --style docs/markdownlint.rb . mdl --git-recurse --style docs/markdownlint.rb .
doc-update-install:
@echo $(H1)Updating installation instructions in the docs$(H1END)
$(VENV_PYTHON) docs/installation/generate.py
############################################################################### ###############################################################################
# Publishing to PyPi # Publishing to PyPi
############################################################################### ###############################################################################
build: build:
rm -rf build/ rm -rf build/ dist/
$(VENV_PYTHON) setup.py sdist bdist_wheel mv httpie/internal/__build_channel__.py httpie/internal/__build_channel__.py.original
echo 'BUILD_CHANNEL = "pip"' > httpie/internal/__build_channel__.py
$(VENV_PYTHON) -m build --sdist --wheel --outdir dist/
mv httpie/internal/__build_channel__.py.original httpie/internal/__build_channel__.py
publish: test-all publish-no-test publish: test-all publish-no-test
@ -203,7 +222,7 @@ brew-test:
- brew uninstall httpie - brew uninstall httpie
@echo $(H1)Building from source…$(H1END) @echo $(H1)Building from source…$(H1END)
- brew install --build-from-source ./docs/packaging/brew/httpie.rb - brew install --HEAD --build-from-source ./docs/packaging/brew/httpie.rb
@echo $(H1)Verifying…$(H1END) @echo $(H1)Verifying…$(H1END)
http --version http --version
@ -211,3 +230,17 @@ brew-test:
@echo $(H1)Auditing…$(H1END) @echo $(H1)Auditing…$(H1END)
brew audit --strict httpie brew audit --strict httpie
###############################################################################
# Generated content
###############################################################################
content: man installation-docs
man: install
@echo $(H1)Regenerate man pages$(H1END)
$(VENV_PYTHON) extras/scripts/generate_man_pages.py
installation-docs:
@echo $(H1)Updating installation instructions in the docs$(H1END)
$(VENV_PYTHON) docs/installation/generate.py

View File

@ -1,10 +1,31 @@
<br/> <h2 align="center">
<a href="https://httpie.io" target="blank_"> <a href="https://httpie.io" target="blank_">
<img height="100" alt="HTTPie" src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-logo.svg" /> <img height="100" alt="HTTPie" src="https://raw.githubusercontent.com/httpie/cli/master/docs/httpie-logo.svg" />
</a> </a>
<br/> <br>
HTTPie CLI: human-friendly HTTP client for the API era
</h2>
# HTTPie: human-friendly CLI HTTP client for the API era <div align="center">
[![HTTPie for Desktop](https://img.shields.io/static/v1?label=HTTPie&message=Desktop&color=4B78E6)](https://httpie.io/product)
[![](https://img.shields.io/static/v1?label=HTTPie&message=Web%20%26%20Mobile&color=73DC8C)](https://httpie.io/app)
[![](https://img.shields.io/static/v1?label=HTTPie&message=CLI&color=FA9BFA)](https://httpie.io/cli)
[![Twitter](https://img.shields.io/twitter/follow/httpie?style=flat&color=%234B78E6&logoColor=%234B78E6)](https://twitter.com/httpie)
[![Chat](https://img.shields.io/discord/725351238698270761?style=flat&label=Chat%20on%20Discord&color=%23FA9BFA)](https://httpie.io/discord)
</div>
<div align="center">
[![Docs](https://img.shields.io/badge/stable%20docs-httpie.io%2Fdocs%2Fcli-brightgreen?style=flat&color=%2373DC8C&label=Docs)](https://httpie.org/docs/cli)
[![Latest version](https://img.shields.io/pypi/v/httpie.svg?style=flat&label=Latest&color=%234B78E6&logo=&logoColor=white)](https://pypi.python.org/pypi/httpie)
[![Build](https://img.shields.io/github/actions/workflow/status/httpie/cli/tests.yml?branch=master&color=%23FA9BFA&label=Build)](https://github.com/httpie/cli/actions)
[![Coverage](https://img.shields.io/codecov/c/github/httpie/cli?style=flat&label=Coverage&color=%2373DC8C)](https://codecov.io/gh/httpie/cli)
[![PyPi downloads](https://img.shields.io/pepy/dt/httpie?style=flat&label=Downloads%20from%20PyPi%20only&color=4B78E6)](https://www.pepy.tech/projects/httpie)
</div>
HTTPie (pronounced _aitch-tee-tee-pie_) is a command-line HTTP client. HTTPie (pronounced _aitch-tee-tee-pie_) is a command-line HTTP client.
Its goal is to make CLI interaction with web services as human-friendly as possible. Its goal is to make CLI interaction with web services as human-friendly as possible.
@ -12,14 +33,22 @@ HTTPie is designed for testing, debugging, and generally interacting with APIs &
The `http` & `https` commands allow for creating and sending arbitrary HTTP requests. The `http` & `https` commands allow for creating and sending arbitrary HTTP requests.
They use simple and natural syntax and provide formatted and colorized output. They use simple and natural syntax and provide formatted and colorized output.
[![Docs](https://img.shields.io/badge/stable%20docs-httpie.io%2Fdocs-brightgreen?style=flat&color=%2373DC8C&label=Docs)](https://httpie.org/docs) <div align="center">
[![Latest version](https://img.shields.io/pypi/v/httpie.svg?style=flat&label=Latest&color=%234B78E6&logo=&logoColor=white)](https://pypi.python.org/pypi/httpie)
[![Build](https://img.shields.io/github/workflow/status/httpie/httpie/Build?color=%23FA9BFA&label=Build)](https://github.com/httpie/httpie/actions) <img src="https://raw.githubusercontent.com/httpie/cli/master/docs/httpie-animation.gif" alt="HTTPie in action" width="100%"/>
[![Coverage](https://img.shields.io/codecov/c/github/httpie/httpie?style=flat&label=Coverage&color=%2373DC8C)](https://codecov.io/gh/httpie/httpie)
[![Twitter](https://img.shields.io/twitter/follow/httpie?style=flat&color=%234B78E6&logoColor=%234B78E6)](https://twitter.com/httpie)
[![Chat](https://img.shields.io/badge/chat-Discord-brightgreen?style=flat&label=Chat%20on&color=%23FA9BFA)](https://httpie.io/discord) </div>
## We lost 54k GitHub stars
Please note we recently accidentally made this repo private for a moment, and GitHub deleted our community that took a decade to build. Read the full story here: https://httpie.io/blog/stardust
![](docs/stardust.png)
<img src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-animation.gif" alt="HTTPie in action" width="100%"/>
## Getting started ## Getting started
@ -45,25 +74,25 @@ They use simple and natural syntax and provide formatted and colorized output.
Hello World: Hello World:
```bash ```bash
$ https httpie.io/hello https httpie.io/hello
``` ```
Custom [HTTP method](https://httpie.io/docs#http-method), [HTTP headers](https://httpie.io/docs#http-headers) and [JSON](https://httpie.io/docs#json) data: Custom [HTTP method](https://httpie.io/docs#http-method), [HTTP headers](https://httpie.io/docs#http-headers) and [JSON](https://httpie.io/docs#json) data:
```bash ```bash
$ http PUT pie.dev/put X-API-Token:123 name=John http PUT pie.dev/put X-API-Token:123 name=John
``` ```
Build and print a request without sending it using [offline mode](https://httpie.io/docs#offline-mode): Build and print a request without sending it using [offline mode](https://httpie.io/docs/cli/offline-mode):
```bash ```bash
$ http --offline pie.dev/post hello=offline http --offline pie.dev/post hello=offline
``` ```
Use [GitHub API](https://developer.github.com/v3/issues/comments/#create-a-comment) to post a comment on an [Issue](https://github.com/httpie/httpie/issues/83) with [authentication](https://httpie.io/docs#authentication): Use [GitHub API](https://developer.github.com/v3/issues/comments/#create-a-comment) to post a comment on an [Issue](https://github.com/httpie/cli/issues/83) with [authentication](https://httpie.io/docs#authentication):
```bash ```bash
$ http -a USERNAME POST https://api.github.com/repos/httpie/httpie/issues/83/comments body='HTTPie is awesome! :heart:' http -a USERNAME POST https://api.github.com/repos/httpie/cli/issues/83/comments body='HTTPie is awesome! :heart:'
``` ```
[See more examples →](https://httpie.io/docs#examples) [See more examples →](https://httpie.io/docs#examples)
@ -74,11 +103,11 @@ $ http -a USERNAME POST https://api.github.com/repos/httpie/httpie/issues/83/com
- Join our [Discord server](https://httpie.io/discord) is to ask questions, discuss features, and for general API chat. - Join our [Discord server](https://httpie.io/discord) is to ask questions, discuss features, and for general API chat.
- Tweet at [@httpie](https://twitter.com/httpie) on Twitter. - Tweet at [@httpie](https://twitter.com/httpie) on Twitter.
- Use [StackOverflow](https://stackoverflow.com/questions/tagged/httpie) to ask questions and include a `httpie` tag. - Use [StackOverflow](https://stackoverflow.com/questions/tagged/httpie) to ask questions and include a `httpie` tag.
- Create [GitHub Issues](https://github.com/httpie/httpie/issues) for bug reports and feature requests. - Create [GitHub Issues](https://github.com/httpie/cli/issues) for bug reports and feature requests.
- Subscribe to the [HTTPie newsletter](https://httpie.io) for occasional updates. - Subscribe to the [HTTPie newsletter](https://httpie.io) for occasional updates.
## Contributing ## Contributing
Have a look through existing [Issues](https://github.com/httpie/httpie/issues) and [Pull Requests](https://github.com/httpie/httpie/pulls) that you could help with. If you'd like to request a feature or report a bug, please [create a GitHub Issue](https://github.com/httpie/httpie/issues) using one of the templates provided. Have a look through existing [Issues](https://github.com/httpie/cli/issues) and [Pull Requests](https://github.com/httpie/cli/pulls) that you could help with. If you'd like to request a feature or report a bug, please [create a GitHub Issue](https://github.com/httpie/cli/issues) using one of the templates provided.
[See contribution guide →](https://github.com/httpie/httpie/blob/master/CONTRIBUTING.md) [See contribution guide →](https://github.com/httpie/cli/blob/master/CONTRIBUTING.md)

View File

@ -19,7 +19,7 @@ This documentation is best viewed at [httpie.io/docs](https://httpie.org/docs).
You can select your corresponding HTTPie version as well as run examples directly from the browser using a [termible.io](https://termible.io?utm_source=httpie-readme) embedded terminal. You can select your corresponding HTTPie version as well as run examples directly from the browser using a [termible.io](https://termible.io?utm_source=httpie-readme) embedded terminal.
If you are reading this on GitHub, then this text covers the current *development* version. If you are reading this on GitHub, then this text covers the current *development* version.
You are invited to submit fixes and improvements to the docs by editing [this file](https://github.com/httpie/httpie/blob/master/docs/README.md). You are invited to submit fixes and improvements to the docs by editing [this file](https://github.com/httpie/cli/blob/master/docs/README.md).
</div> </div>
@ -126,6 +126,66 @@ $ choco upgrade httpie
### Linux ### Linux
#### Debian and Ubuntu
Also works for other 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.
```bash
# Install httpie
$ curl -SsL https://packages.httpie.io/deb/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/httpie.gpg
$ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/httpie.gpg] https://packages.httpie.io/deb ./" | sudo tee /etc/apt/sources.list.d/httpie.list > /dev/null
$ sudo apt update
$ sudo apt install httpie
```
```bash
# Upgrade httpie
$ sudo apt update && sudo apt upgrade httpie
```
#### Fedora
```bash
# Install httpie
$ dnf install httpie
```
```bash
# Upgrade httpie
$ dnf upgrade httpie
```
#### CentOS and RHEL
Also works for other RHEL-derived distributions like ClearOS, Oracle Linux, etc.
```bash
# Install httpie
$ yum install epel-release
$ yum install httpie
```
```bash
# Upgrade httpie
$ yum upgrade httpie
```
#### 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
$ ln -ls ./http ./https
$ chmod +x ./http ./https
```
```bash
# Upgrade httpie
$ https --download packages.httpie.io/binaries/linux/http-latest -o http
```
#### Snapcraft (Linux) #### Snapcraft (Linux)
To install [Snapcraft](https://snapcraft.io/), see [its installation](https://snapcraft.io/docs/installing-snapd). To install [Snapcraft](https://snapcraft.io/), see [its installation](https://snapcraft.io/docs/installing-snapd).
@ -156,49 +216,6 @@ $ brew update
$ brew upgrade httpie $ brew upgrade httpie
``` ```
#### Debian and Ubuntu
Also works for other 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.
```bash
# Install httpie
$ apt update
$ apt install httpie
```
```bash
# Upgrade httpie
$ apt update
$ apt upgrade httpie
```
#### Fedora
```bash
# Install httpie
$ dnf install httpie
```
```bash
# Upgrade httpie
$ dnf upgrade httpie
```
#### CentOS and RHEL
Also works for other RHEL-derived distributions like ClearOS, Oracle Linux, etc.
```bash
# Install httpie
$ yum install epel-release
$ yum install httpie
```
```bash
# Upgrade httpie
$ yum upgrade httpie
```
#### Arch Linux #### Arch Linux
Also works for other Arch-derived distributions like ArcoLinux, EndeavourOS, Artix Linux, etc. Also works for other Arch-derived distributions like ArcoLinux, EndeavourOS, Artix Linux, etc.
@ -233,36 +250,39 @@ $ pkg upgrade www/py-httpie
### Unstable version ### Unstable version
You can also install the latest unreleased development version directly from the `master` branch on GitHub. If you want to try out the latest version of HTTPie that hasn't been officially released yet, you can install the development or unstable version directly from the master branch on GitHub. However, keep in mind that the development version is a work in progress and may not be as reliable as the stable version.
It is a work-in-progress of a future stable release so the experience might be not as smooth.
You can install it on Linux, macOS, Windows, or FreeBSD with `pip`: You can use the following command to install the development version of HTTPie on Linux, macOS, Windows, or FreeBSD operating systems. With this command, the code present in the `master` branch is downloaded and installed using `pip`.
```bash ```bash
$ python -m pip install --upgrade https://github.com/httpie/httpie/archive/master.tar.gz $ python -m pip install --upgrade https://github.com/httpie/cli/archive/master.tar.gz
``` ```
Or on macOS, and Linux, with Homebrew: There are other ways to install the development version of HTTPie on macOS and Linux.
You can install it using Homebrew by running the following commands:
```bash ```bash
$ brew uninstall --force httpie $ brew uninstall --force httpie
$ brew install --HEAD httpie $ brew install --HEAD httpie
``` ```
And even on macOS, and Linux, with Snapcraft: You can install it using Snapcraft by running the following commands:
```bash ```bash
$ snap remove httpie $ snap remove httpie
$ snap install httpie --edge $ snap install httpie --edge
``` ```
Verify that now you have the [current development version identifier](https://github.com/httpie/httpie/blob/master/httpie/__init__.py#L6) with the `.dev0` suffix, for example: To verify the installation, you can compare the [version identifier on GitHub](https://github.com/httpie/cli/blob/master/httpie/__init__.py#L6) with the one available on your machine. You can check the version of HTTPie on your machine by using the command `http --version`.
```bash ```bash
$ http --version $ http --version
# 3.X.X.dev0 # 3.X.X.dev0
``` ```
Note that on your machine, the version name will have the `.dev0` suffix.
## Usage ## Usage
Hello World: Hello World:
@ -277,7 +297,7 @@ Synopsis:
$ http [flags] [METHOD] URL [ITEM [ITEM]] $ 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 ### Examples
@ -305,10 +325,10 @@ Build and print a request without sending it using [offline mode](#offline-mode)
$ http --offline pie.dev/post hello=offline $ http --offline pie.dev/post hello=offline
``` ```
Use [GitHub API](https://developer.github.com/v3/issues/comments/#create-a-comment) to post a comment on an [issue](https://github.com/httpie/httpie/issues/83) with [authentication](#authentication): Use [GitHub API](https://developer.github.com/v3/issues/comments/#create-a-comment) to post a comment on an [issue](https://github.com/httpie/cli/issues/83) with [authentication](#authentication):
```bash ```bash
$ http -a USERNAME POST https://api.github.com/repos/httpie/httpie/issues/83/comments body='HTTPie is awesome! :heart:' $ http -a USERNAME POST https://api.github.com/repos/httpie/cli/issues/83/comments body='HTTPie is awesome! :heart:'
``` ```
Upload a file using [redirected input](#redirected-input): Upload a file using [redirected input](#redirected-input):
@ -448,7 +468,7 @@ $ http https://api.github.com/search/repositories q==httpie per_page==1
GET /search/repositories?q=httpie&per_page=1 HTTP/1.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 ```bash
$ http pie.dev/get text==@files/text.txt $ http pie.dev/get text==@files/text.txt
@ -457,7 +477,7 @@ $ http pie.dev/get text==@files/text.txt
### URL shortcuts for `localhost` ### URL shortcuts for `localhost`
Additionally, curl-like shorthand for localhost is supported. Additionally, curl-like shorthand for localhost is supported.
This means that, for example, `:3000` would expand to `http://localhost:3000` This means that, for example, `:3000` would expand to `http://localhost:3000`.
If the port is omitted, then port 80 is assumed. If the port is omitted, then port 80 is assumed.
```bash ```bash
@ -510,7 +530,7 @@ $ http-unix %2Fvar%2Frun%2Fdocker.sock/info
### `--path-as-is` ### `--path-as-is`
The standard behavior of HTTP clients is to normalize the path portion of URLs by squashing dot segments as a typically filesystem would: The standard behavior of HTTP clients is to normalize the path portion of URLs by squashing dot segments as a typical filesystem would:
```bash ```bash
$ http -v example.org/./../../etc/password $ http -v example.org/./../../etc/password
@ -554,7 +574,7 @@ $ http PUT pie.dev/put \
| HTTP Headers `Name:Value` | Arbitrary HTTP header, e.g. `X-API-Token:123` | | HTTP Headers `Name:Value` | Arbitrary HTTP header, e.g. `X-API-Token:123` |
| URL parameters `name==value` | Appends the given name/value pair as a querystring parameter to the URL. The `==` separator is used. | | URL parameters `name==value` | Appends the given name/value pair as a querystring parameter to the URL. The `==` separator is used. |
| Data Fields `field=value` | Request data fields to be serialized as a JSON object (default), to be form-encoded (with `--form, -f`), or to be serialized as `multipart/form-data` (with `--multipart`) | | Data Fields `field=value` | Request data fields to be serialized as a JSON object (default), to be form-encoded (with `--form, -f`), or to be serialized as `multipart/form-data` (with `--multipart`) |
| Raw JSON fields `field:=json` | Useful when sending JSON and one or more fields need to be a `Boolean`, `Number`, nested `Object`, or an `Array`, e.g., `meals:='["ham","spam"]'` or `pies:=[1,2,3]` (note the quotes) | | Raw JSON fields `field:=json` | Useful when sending JSON and one or more fields need to be a `Boolean`, `Number`, nested `Object`, or an `Array`, e.g., `meals:='["ham","spam"]'` or `pies:='[1,2,3]'` (note the quotes) |
| File upload fields `field@/dir/file`, `field@file;type=mime` | Only available with `--form`, `-f` and `--multipart`. For example `screenshot@~/Pictures/img.png`, or `'cv@cv.txt;type=text/markdown'`. With `--form`, the presence of a file field results in a `--multipart` request | | File upload fields `field@/dir/file`, `field@file;type=mime` | Only available with `--form`, `-f` and `--multipart`. For example `screenshot@~/Pictures/img.png`, or `'cv@cv.txt;type=text/markdown'`. With `--form`, the presence of a file field results in a `--multipart` request |
Note that the structured data fields arent the only way to specify request data: Note that the structured data fields arent the only way to specify request data:
@ -563,7 +583,7 @@ Note that the structured data fields arent the only way to specify request da
### File based separators ### File based separators
Using file contents as values for specific fields is a very common use case, which can be achieved through adding the `@` suffix to Using file contents as values for specific fields is a very common use case, which can be achieved through adding the `@` suffix to
the operators above. For example instead of using a static string as the value for some header, you can use `:@` operator the operators above. For example, instead of using a static string as the value for some header, you can use `:@` operator
to pass the desired value from a file. to pass the desired value from a file.
```bash ```bash
@ -729,7 +749,7 @@ $ http --offline --print=B pie.dev/post \
``` ```
```json ```json
{ {
"category": "tools", "category": "tools",
"search": { "search": {
"keywords": [ "keywords": [
@ -895,6 +915,8 @@ http --offline --print=B pie.dev/post \
"]": 3 "]": 3
}, },
"foo[bar]": 1 "foo[bar]": 1
}
```
If you want to send the literal backslash character (`\`), escape it with another backslash: If you want to send the literal backslash character (`\`), escape it with another backslash:
@ -1022,7 +1044,7 @@ $ http POST pie.dev/post < files/data.json
```bash ```bash
$ http -f POST pie.dev/post name='John Smith' cv@~/files/data.xml $ http -f POST pie.dev/post name='John Smith' cv@~/files/data.xml
``` ```
The request above is the same as if the following HTML form were submitted: The request above is the same as if the following HTML form were submitted:
```html ```html
@ -1154,11 +1176,11 @@ User-Agent: HTTPie/<version>
```bash ```bash
$ http pie.dev/headers 'Header;' $ http pie.dev/headers 'Header;'
``` ```
Please note that some internal headers, such as `Content-Length`, cant be unset if Please note that some internal headers, such as `Content-Length`, cant be unset if
they are automatically added by the client itself. they are automatically added by the client itself.
### Multiple header values with the same name ### Multiple header values with the same name
If the request is sent with multiple headers that are sharing the same name, then If the request is sent with multiple headers that are sharing the same name, then
the HTTPie will send them individually. the HTTPie will send them individually.
@ -1213,7 +1235,7 @@ by individual commands when sending a request instead of being joined together.
Generating raw requests that can be sent with any other client: Generating raw requests that can be sent with any other client:
```bash ```bash
# 1. save a raw request to a file: # 1. save a raw request to a file:
$ http --offline POST pie.dev/post hello=world > request.http $ http --offline POST pie.dev/post hello=world > request.http
``` ```
@ -1436,7 +1458,8 @@ $ http --proxy=http:http://user:pass@10.10.1.10:3128 example.org
## HTTPS ## HTTPS
### Server SSL certificate verification ### Server SSL certificate verification
To skip the hosts SSL certificate verification, you can pass `--verify=no` (default is `yes`):
To skip the hosts SSL certificate verification, you can pass `--verify=no` (default is `yes`):
```bash ```bash
$ http --verify=no https://pie.dev/get $ http --verify=no https://pie.dev/get
@ -1653,6 +1676,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: 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 ```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. 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 The response headers are downloaded always, even if they are not part of the output
@ -1665,14 +1692,6 @@ $ http --all --follow pie.dev/redirect/3
`--raw='data'`, and `@/file/path`. `--raw='data'`, and `@/file/path`.
### Redirected Input ### Redirected Input
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`.
### Redirected Input
The universal method for passing request data is through redirected `stdin` The universal method for passing request data is through redirected `stdin`
(standard input)—piping. (standard input)—piping.
@ -1735,7 +1754,7 @@ $ http pie.dev/post <<<'{"name": "John"}'
$ pbpaste | http PUT pie.dev/put $ pbpaste | http PUT pie.dev/put
``` ```
Passing data through `stdin` **cant** be combined with data fields specified on the command line: Passing data through `stdin` **cant** be combined with data fields specified on the command line:
```bash ```bash
$ echo -n 'data' | http POST example.org more=data # This is invalid $ echo -n 'data' | http POST example.org more=data # This is invalid
@ -1983,7 +2002,7 @@ HTTPie features a download mode in which it acts similarly to `wget`.
### Piping while downloading ### Piping while downloading
You can also redirect the response body to another program while the response headers and progress are still shown in the terminal: You can also redirect the response body to another program while the response headers and progress are still shown in the terminal:
```bash ```bash
$ http -d https://github.com/httpie/cli/archive/master.tar.gz | tar zxf - $ http -d https://github.com/httpie/cli/archive/master.tar.gz | tar zxf -
``` ```
@ -2012,7 +2031,7 @@ To prevent data loss by overwriting, HTTPie adds a unique numerical suffix to th
## Streamed responses ## Streamed responses
Responses are downloaded and printed in chunks. Responses are downloaded and printed in chunks.
This allows for streaming and large file downloads without using too much memory. This allows for streaming and large file downloads without using too much memory.
However, when [colors and formatting](#colors-and-formatting) are applied, the whole response is buffered and only then processed at once. However, when [colors and formatting](#colors-and-formatting) are applied, the whole response is buffered and only then processed at once.
### Disabling buffering ### Disabling buffering
@ -2292,8 +2311,10 @@ Upgraded 'session.json' @ 'pie.dev' to v3.1.0
$ http pie.dev/get $ http pie.dev/get
``` ```
Currently, HTTPie offers a single configurable option: ### Configurable options
Currently, HTTPie offers a single configurable option:
#### `default_options` #### `default_options`
An `Array` (by default empty) of default options that should be applied to every invocation of HTTPie. An `Array` (by default empty) of default options that should be applied to every invocation of HTTPie.
@ -2345,7 +2366,7 @@ However, it is not recommended modifying the default behavior in a way that woul
*) echo 'Other Error!' ;; *) echo 'Other Error!' ;;
esac esac
fi fi
``` ```
### Best practices ### Best practices
@ -2379,16 +2400,13 @@ fi
You can check whether a new update is available for your system by running `httpie cli check-updates`: You can check whether a new update is available for your system by running `httpie cli check-updates`:
```bash-termible ```bash-termible
`httpie cli export-args` command can expose the parser specification of `http`/`https` commands $ httpie cli check-updates
(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:
#### `httpie cli export-args` #### `httpie cli export-args`
`httpie cli export-args` command can expose the parser specification of `http`/`https` commands `httpie cli export-args` command can expose the parser specification of `http`/`https` commands
(like an API definition) to outside tools so that they can use this to build better interactions
over them (e.g., offer auto-complete). over them (e.g., offer auto-complete).
Available formats to export in include: Available formats to export in include:
@ -2404,15 +2422,23 @@ This command is currently in beta.
"Program: http, Version: 0.0.1a0" "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, By default, the plugins (and their missing dependencies) will be stored under the configuration directory,
but this can be modified through `plugins_dir` variable on the config. but this can be modified through `plugins_dir` variable on the config.
##### `httpie cli plugins install` ##### `httpie cli plugins install`
For installing plugins from [PyPI](https://pypi.org/) or from local paths, `httpie plugins install`
For installing plugins from [PyPI](https://pypi.org/) or from local paths, `httpie cli plugins install` For installing plugins from [PyPI](https://pypi.org/) or from local paths, `httpie cli plugins install`
can be used. can be used.
```bash
```bash
$ httpie cli plugins install httpie-plugin $ httpie cli plugins install httpie-plugin
Installing httpie-plugin... Installing httpie-plugin...
Successfully installed httpie-plugin-1.0.2 Successfully installed httpie-plugin-1.0.2
@ -2423,7 +2449,7 @@ $ httpie cli export-args | jq '"Program: " + .spec.name + ", Version: " + .vers
##### `httpie cli plugins list` ##### `httpie cli plugins list`
List all installed plugins. List all installed plugins.
```bash ```bash
$ httpie cli plugins list $ httpie cli plugins list
@ -2434,13 +2460,13 @@ plugin installations on every installation method.
httpie_converter (1.0.0) httpie_converter (1.0.0)
httpie_iterm_converter (httpie.plugins.converter.v1) httpie_iterm_converter (httpie.plugins.converter.v1)
httpie_konsole_konverter (httpie.plugins.converter.v1) httpie_konsole_konverter (httpie.plugins.converter.v1)
``` ```
#### `httpie plugins upgrade` ##### `httpie cli plugins upgrade`
For upgrading already installed plugins, use `httpie plugins upgrade`. For upgrading already installed plugins, use `httpie plugins upgrade`.
```bash ```bash
$ httpie cli plugins upgrade httpie-plugin $ httpie cli plugins upgrade httpie-plugin
``` ```
@ -2448,12 +2474,12 @@ Successfully installed httpie-plugin-1.0.2
Uninstall plugins from the isolated plugins directory. If the plugin is not installed Uninstall plugins from the isolated plugins directory. If the plugin is not installed
through `httpie cli plugins install`, it wont uninstall it. through `httpie cli plugins install`, it wont uninstall it.
```bash ```bash
$ httpie cli plugins uninstall httpie-plugin $ httpie cli plugins uninstall httpie-plugin
``` ```
## Meta ## Meta
### Interface design ### Interface design
@ -2463,21 +2489,21 @@ httpie_converter (1.0.0)
For example, compare this HTTP request: For example, compare this HTTP request:
```http ```http
POST /post HTTP/1.1 POST /post HTTP/1.1
Host: pie.dev Host: pie.dev
X-API-Key: 123 X-API-Key: 123
User-Agent: Bacon/1.0 User-Agent: Bacon/1.0
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
name=value&name2=value2 name=value&name2=value2
``` ```
with the HTTPie command that sends it: with the HTTPie command that sends it:
```bash ```bash
$ http -f POST pie.dev/post \ $ http -f POST pie.dev/post \
X-API-Key:123 \ X-API-Key:123 \
User-Agent:Bacon/1.0 \ User-Agent:Bacon/1.0 \
name=value \ name=value \
name2=value2 name2=value2
``` ```
@ -2520,7 +2546,7 @@ All changes are recorded in the [change log](#change-log).
#### Alternatives #### Alternatives
- [httpcat](https://github.com/httpie/httpcat) — a lower-level sister utility of HTTPie for constructing raw HTTP requests on the command line - [httpcat](https://github.com/httpie/httpcat) — a lower-level sister utility of HTTPie for constructing raw HTTP requests on the command line
- [curl](https://curl.haxx.se) — a "Swiss knife" command line tool and an exceptional library for transferring data with URLs. - [curl](https://curl.haxx.se) — a "Swiss knife" command line tool and an exceptional library for transferring data with URLs.
### Contributing ### Contributing
@ -2530,7 +2556,7 @@ HTTPie has the following community channels:
See [github.com/httpie/cli/security/policy](https://github.com/httpie/cli/security/policy). See [github.com/httpie/cli/security/policy](https://github.com/httpie/cli/security/policy).
### Change log ### Change log
See [CHANGELOG](https://github.com/httpie/cli/blob/master/CHANGELOG.md). See [CHANGELOG](https://github.com/httpie/cli/blob/master/CHANGELOG.md).
@ -2542,7 +2568,7 @@ HTTPie plays exceptionally well with the following tools:
BSD-3-Clause: [LICENSE](https://github.com/httpie/cli/blob/master/LICENSE). BSD-3-Clause: [LICENSE](https://github.com/httpie/cli/blob/master/LICENSE).
### Authors ### Authors
[Jakub Roztocil](https://roztocil.co) ([@jakubroztocil](https://twitter.com/jakubroztocil)) created HTTPie and [these fine people](https://github.com/httpie/cli/blob/master/AUTHORS.md) have contributed. [Jakub Roztocil](https://roztocil.co) ([@jakubroztocil](https://twitter.com/jakubroztocil)) created HTTPie and [these fine people](https://github.com/httpie/cli/blob/master/AUTHORS.md) have contributed.
@ -2551,24 +2577,25 @@ Helpers to convert from other client tools:
### Contributing ### Contributing
See [CONTRIBUTING](https://github.com/httpie/httpie/blob/master/CONTRIBUTING.md). See [CONTRIBUTING](https://github.com/httpie/cli/blob/master/CONTRIBUTING.md).
### Security policy ### Security policy
See [github.com/httpie/httpie/security/policy](https://github.com/httpie/httpie/security/policy). See [github.com/httpie/cli/security/policy](https://github.com/httpie/cli/security/policy).
### Change log ### Change log
See [CHANGELOG](https://github.com/httpie/httpie/blob/master/CHANGELOG.md). See [CHANGELOG](https://github.com/httpie/cli/blob/master/CHANGELOG.md).
### Artwork ### Artwork
- [README Animation](https://github.com/httpie/httpie/blob/master/docs/httpie-animation.gif) by [Allen Smith](https://github.com/loranallensmith). - [README Animation](https://github.com/httpie/cli/blob/master/docs/httpie-animation.gif) by [Allen Smith](https://github.com/loranallensmith).
### Licence ### Licence
BSD-3-Clause: [LICENSE](https://github.com/httpie/httpie/blob/master/LICENSE). BSD-3-Clause: [LICENSE](https://github.com/httpie/cli/blob/master/LICENSE).
### Authors ### Authors
[Jakub Roztocil](https://roztocil.co) ([@jakubroztocil](https://twitter.com/jakubroztocil)) created HTTPie and [these fine people](https://github.com/httpie/httpie/blob/master/AUTHORS.md) have contributed. [Jakub Roztocil](https://roztocil.co) ([@jakubroztocil](https://twitter.com/jakubroztocil)) created HTTPie and [these fine people](https://github.com/httpie/cli/blob/master/AUTHORS.md) have contributed.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,11 +17,12 @@ docs-structure:
Windows: Windows:
- chocolatey - chocolatey
Linux: Linux:
- snap-linux
- brew-linux
- apt - apt
- dnf - dnf
- yum - yum
- single-binary
- snap-linux
- brew-linux
- pacman - pacman
FreeBSD: FreeBSD:
- pkg - pkg
@ -36,11 +37,13 @@ tools:
package: https://packages.debian.org/sid/web/httpie package: https://packages.debian.org/sid/web/httpie
commands: commands:
install: install:
- apt update - curl -SsL https://packages.httpie.io/deb/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/httpie.gpg
- apt install httpie # - curl -SsL -o /etc/apt/sources.list.d/httpie.list https://packages.httpie.io/deb/httpie.list
- echo "deb [arch=amd64 signed-by=/usr/share/keyrings/httpie.gpg] https://packages.httpie.io/deb ./" | sudo tee /etc/apt/sources.list.d/httpie.list > /dev/null
- sudo apt update
- sudo apt install httpie
upgrade: upgrade:
- apt update - sudo apt update && sudo apt upgrade httpie
- apt upgrade httpie
brew-mac: brew-mac:
title: Homebrew title: Homebrew
@ -179,3 +182,16 @@ tools:
- yum install httpie - yum install httpie
upgrade: upgrade:
- yum upgrade httpie - yum upgrade httpie
single-binary:
title: Single binary executables
name: Single binary executables
note: Get the standalone HTTPie Linux executables when you don't want to go through the full installation process.
links:
commands:
install:
- https --download packages.httpie.io/binaries/linux/http-latest -o http
- ln -ls ./http ./https
- chmod +x ./http ./https
upgrade:
- https --download packages.httpie.io/binaries/linux/http-latest -o http

View File

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

View File

@ -3,7 +3,7 @@
Welcome on the documentation part of the **HTTPie release process**. Welcome on the documentation part of the **HTTPie release process**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli). - If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions, then you can find all you need for your OS on [that page](https://httpie.io/docs#installation). In the case you do not find your OS, [let us know](https://github.com/httpie/httpie/issues/). - If you are looking for HTTPie installation or upgrade instructions, then you can find all you need for your OS on [that page](https://httpie.io/docs#installation). In the case you do not find your OS, [let us know](https://github.com/httpie/cli/issues/).
- If you are looking for technical information about the HTTPie packaging, then you are at the good place. - If you are looking for technical information about the HTTPie packaging, then you are at the good place.
## About ## About
@ -12,18 +12,20 @@ You are looking at the HTTPie packaging documentation, where you will find valua
The overall release process starts simple: The overall release process starts simple:
1. Do the [PyPI](https://pypi.org/project/httpie/) publication. 1. Bump the version identifiers in the following places:
2. Then, handle company-related tasks. - `httpie/__init__.py`
3. Finally, follow OS-specific steps, described in documents below, to send patches downstream. - `docs/packaging/windows-chocolatey/httpie.nuspec`
- `CHANGELOG.md`
2. Commit your changes and make a PR against the `master`.
3. Merge the PR, and tag the last commit with your version identifier.
4. Make a GitHub release (by copying the text in `CHANGELOG.md`)
5. Push that release to PyPI (dispatch the `Release PyPI` GitHub action).
6. Once PyPI is ready, push the release to the Snap, Homebrew and Chocolatey with their respective actions.
7. Go to the [`httpie/debian.httpie.io`](https://github.com/httpie/debian.httpie.io) repo and trigger the package index workflow.
## First, PyPI ## Company-specific tasks
Let's do the release on [PyPi](https://pypi.org/project/httpie/). - Blank the `master_and_released_docs_differ_after` value in [config.json](https://github.com/httpie/cli/blob/master/docs/config.json).
That is done quite easily by manually triggering the [release workflow](https://github.com/httpie/httpie/actions/workflows/release.yml).
## Then, company-specific tasks
- Blank the `master_and_released_docs_differ_after` value in [config.json](https://github.com/httpie/httpie/blob/master/docs/config.json).
- Update the [contributors list](../contributors). - Update the [contributors list](../contributors).
- Update the HTTPie version bundled into [Termible](https://termible.io/) ([example](https://github.com/httpie/termible/pull/1)). - Update the HTTPie version bundled into [Termible](https://termible.io/) ([example](https://github.com/httpie/termible/pull/1)).
@ -36,11 +38,10 @@ A more complete state of deployment can be found on [repology](https://repology.
| -------------------------------------------: | -------------- | | -------------------------------------------: | -------------- |
| [Arch Linux, and derived](linux-arch/) | trusted person | | [Arch Linux, and derived](linux-arch/) | trusted person |
| [CentOS, RHEL, and derived](linux-centos/) | trusted person | | [CentOS, RHEL, and derived](linux-centos/) | trusted person |
| [Debian, Ubuntu, and derived](linux-debian/) | trusted person |
| [Fedora](linux-fedora/) | trusted person | | [Fedora](linux-fedora/) | trusted person |
| :construction: [Homebrew, Linuxbrew](brew/) | **HTTPie** | | [Debian, Ubuntu, and derived](linux-debian/) | **HTTPie** |
| :construction: [MacPorts](mac-ports/) | **HTTPie** | | [Homebrew, Linuxbrew](brew/) | **HTTPie** |
| [Snapcraft](snapcraft/) | **HTTPie** | | [Snapcraft](snapcraft/) | **HTTPie** |
| [Windows — Chocolatey](windows-chocolatey/) | **HTTPie** | | [Windows — Chocolatey](windows-chocolatey/) | **HTTPie** |
:new: You do not find your system or you would like to see HTTPie supported on another OS? Then [let us know](https://github.com/httpie/httpie/issues/). :new: You do not find your system or you would like to see HTTPie supported on another OS? Then [let us know](https://github.com/httpie/cli/issues/).

View File

@ -13,21 +13,19 @@ We will discuss setting up the environment, installing development tools, instal
## Overall process ## Overall process
:construction: Work in progress. The brew deployment is completely automated, and only requires a trigger to [`Release on Homebrew`](https://github.com/httpie/cli/actions/workflows/release-brew.yml) action
from the release manager.
First, update the current Formula: If it is needed to be done manually, the following command can be used:
```bash ```console
make brew-deps $ brew bump-formula-pr httpie --version={TARGET_VERSION}
# Copy-paste content into downstream/mac/brew/httpie.rb
git add downstream/mac/brew/httpie.rb
git commit -s -m 'Update brew formula to XXX'
``` ```
That [GitHub workflow](https://github.com/httpie/httpie/actions/workflows/test-package-mac-brew.yml) will test the formula when `downstream/mac/brew/httpie.rb` is changed in a pull request. which will bump the formula, and create a PR against the package index.
Then, open a pull request with those changes to the [downstream file](https://github.com/Homebrew/homebrew-core/blob/master/Formula/httpie.rb).
## Hacking ## Hacking
:construction: Work in progress. Make your changes, test the formula through the [`Test Brew Package`](https://github.com/httpie/cli/actions/workflows/test-package-mac-brew.yml) action
and then finally submit your patch to [`homebrew-core`](https://github.com/Homebrew/homebrew-core`)

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,18 +3,18 @@ class Httpie < Formula
desc "User-friendly cURL replacement (command-line HTTP client)" desc "User-friendly cURL replacement (command-line HTTP client)"
homepage "https://httpie.io/" homepage "https://httpie.io/"
url "https://files.pythonhosted.org/packages/7b/f9/13070f19226b7db3641fb787df36bb715063abe1b8ca03fbaeca0f465d27/httpie-3.0.1.tar.gz" url "https://files.pythonhosted.org/packages/32/85/bb095699be20cc98731261cb80884e9458178f8fef2a38273530ce77c0a5/httpie-3.1.0.tar.gz"
sha256 "0e9bc93ebdcdd2d32ec24b8fa46cf7e4fde9eec7a6bd0c5d0ef224f25d7466b2" sha256 "2e4a2040b84a912e65c01fb34f7aafe88cad2a3af2da8c685ca65080f376feda"
license "BSD-3-Clause" license "BSD-3-Clause"
head "https://github.com/httpie/httpie.git", branch: "master" head "https://github.com/httpie/cli.git", branch: "master"
bottle do bottle do
sha256 cellar: :any_skip_relocation, arm64_monterey: "9d285fcfb55ce8ed787d1b01966d51e6e07f7e77c44a204695a2d6eee9c8698d" sha256 cellar: :any_skip_relocation, arm64_monterey: "9bb6e8c1ef5ba8b019ddedd7e908dd2174da695351aa9a238dfb28b0f57ef005"
sha256 cellar: :any_skip_relocation, arm64_big_sur: "743a282b475e87a4eaf11e545f761aef1b8e4bfe49eaee47251d7629a35a8ced" sha256 cellar: :any_skip_relocation, arm64_big_sur: "47ffccd3241155d863e1b4f6259d538a34d42a0cdeed8152bda257ee607b51be"
sha256 cellar: :any_skip_relocation, monterey: "5d63ea4f47b2028b2ba68abe12a4176934193e058edd869270221b41cc946c76" sha256 cellar: :any_skip_relocation, monterey: "dc4a04cb05a9cd1bfa6a632a0e4a21975905954af54ece41f9050c52474267be"
sha256 cellar: :any_skip_relocation, big_sur: "5a53221a680a35d1aa00cbadde279dbe4f562d22ed207c15bd4221cb8c3180f1" sha256 cellar: :any_skip_relocation, big_sur: "ae469e37864e967e0fd99fba15a78e719dcb351b462f98f3843c78ed1473df6d"
sha256 cellar: :any_skip_relocation, catalina: "5feadb6d76f55d6f9681682e221008c282dccf0e46ae22a959b4bad2efde204a" sha256 cellar: :any_skip_relocation, catalina: "291a3eaecb2a2cc845c1652686a9a14b21053d7e3a7d0115245b2150ca2e199e"
sha256 cellar: :any_skip_relocation, x86_64_linux: "d530ddbec49588b0d481f156d35f7e5bb7d3b6427d203f04750e55cd3eecc303" sha256 cellar: :any_skip_relocation, x86_64_linux: "710836e27c44c8e3ad181d668f4a9f78c4cb4c355d7b148a397599a7cd42713d"
end end
depends_on "python@3.10" depends_on "python@3.10"
@ -25,8 +25,8 @@ class Httpie < Formula
end end
resource "charset-normalizer" do resource "charset-normalizer" do
url "https://files.pythonhosted.org/packages/48/44/76b179e0d1afe6e6a91fd5661c284f60238987f3b42b676d141d01cd5b97/charset-normalizer-2.0.10.tar.gz" url "https://files.pythonhosted.org/packages/56/31/7bcaf657fafb3c6db8c787a865434290b726653c912085fbd371e9b92e1c/charset-normalizer-2.0.12.tar.gz"
sha256 "876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd" sha256 "2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"
end end
resource "defusedxml" do resource "defusedxml" do
@ -40,8 +40,8 @@ class Httpie < Formula
end end
resource "multidict" do resource "multidict" do
url "https://files.pythonhosted.org/packages/8e/7c/e12a69795b7b7d5071614af2c691c97fbf16a2a513c66ec52dd7d0a115bb/multidict-5.2.0.tar.gz" url "https://files.pythonhosted.org/packages/fa/a7/71c253cdb8a1528802bac7503bf82fe674367e4055b09c28846fdfa4ab90/multidict-6.0.2.tar.gz"
sha256 "0dd1c93edb444b33ba2274b66f63def8a327d607c6c790772f448a53b6ea59ce" sha256 "5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"
end end
resource "Pygments" do resource "Pygments" do

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

@ -7,7 +7,7 @@ pkgname=httpie
pkgver=2.6.0 pkgver=2.6.0
pkgrel=1 pkgrel=1
pkgdesc="human-friendly CLI HTTP client for the API era" pkgdesc="human-friendly CLI HTTP client for the API era"
url="https://github.com/httpie/httpie" url="https://github.com/httpie/cli"
depends=('python-defusedxml' depends=('python-defusedxml'
'python-pygments' 'python-pygments'
'python-pysocks' 'python-pysocks'
@ -22,7 +22,7 @@ conflicts=(python-httpie)
replaces=(python-httpie python2-httpie) replaces=(python-httpie python2-httpie)
license=('BSD') license=('BSD')
arch=('any') arch=('any')
source=($pkgname-$pkgver.tar.gz::"https://github.com/httpie/httpie/archive/$pkgver.tar.gz") source=($pkgname-$pkgver.tar.gz::"https://github.com/httpie/cli/archive/$pkgver.tar.gz")
sha256sums=('3bcd9a8cb2b11299da12d3af36c095c6d4b665e41c395898a07f1ae4d99fc14a') sha256sums=('3bcd9a8cb2b11299da12d3af36c095c6d4b665e41c395898a07f1ae4d99fc14a')
build() { build() {

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. This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Debian GNU/Linux. They apply to Ubuntu as well, and any Debian-derived distributions like MX Linux, Linux Mint, deepin, Pop!_OS, KDE neon, Zorin OS, elementary OS, Kubuntu, Devuan, Linux Lite, Peppermint OS, Lubuntu, antiX, Xubuntu, etc.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream. We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
The current maintainer is Bartosz Fenski. We create the standalone binaries (see this [for more details](../../../extras/packaging/linux/)) and package them with
[FPM](https://github.com/jordansissel/fpm)'s `dir` mode. The core `http`/`https` commands don't have any dependencies, but the `httpie`
command (due to the underlying `httpie cli plugins` interface) explicitly depends to the system Python (through `python3`/`python3-pip`).
## Overall process ## Overall process
Open a new bug on the Debian Bug Tracking System by sending an email: The [`Release as Standalone Linux Binary`](https://github.com/httpie/cli/actions/workflows/release-linux-standalone.yml) will be automatically
triggered when a new release is created, and it will submit the `.deb` package as a release asset.
- To: `Debian Bug Tracking System <submit@bugs.debian.org>` For making that asset available for all debian users, the release manager needs to go to the [`httpie/debian.httpie.io`](https://github.com/httpie/debian.httpie.io) repo
- Subject: `httpie: Version XXX available` and trigger the [`Update Index`](https://github.com/httpie/debian.httpie.io/actions/workflows/update-index.yml) action. It will automatically
- Message template (examples [1](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=993937), and [2](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=996479)): scrape all new debian packages from the release assets, properly update the indexes and create a new PR ([an example](https://github.com/httpie/debian.httpie.io/pull/1))
which then will become active when merged.
```email
Package: httpie
Severity: normal
<MESSAGE>
```

View File

@ -15,7 +15,7 @@ The current maintainer is [Miro Hrončok](https://github.com/hroncok).
## Overall process ## Overall process
We added the [.packit.yaml](https://github.com/httpie/httpie/blob/master/.packit.yaml) local file. We added the [.packit.yaml](https://github.com/httpie/cli/blob/master/.packit.yaml) local file.
It unlocks real-time Fedora checks on pull requests and new releases. It unlocks real-time Fedora checks on pull requests and new releases.
So there is nothing to do on our side: `Packit` will see the new release and open a pull request [there](https://src.fedoraproject.org/rpms/httpie). Then, the Fedora maintainer will review and merge. So there is nothing to do on our side: `Packit` will see the new release and open a pull request [there](https://src.fedoraproject.org/rpms/httpie). Then, the Fedora maintainer will review and merge.

View File

@ -1,11 +1,11 @@
Name: httpie Name: httpie
Version: 3.0.2 Version: 3.1.0
Release: 1%{?dist} Release: 1%{?dist}
Summary: A Curl-like tool for humans Summary: A Curl-like tool for humans
License: BSD License: BSD
URL: https://httpie.org/ URL: https://httpie.org/
Source0: https://github.com/httpie/httpie/archive/%{version}/%{name}-%{version}.tar.gz Source0: https://github.com/httpie/cli/archive/%{version}/%{name}-%{version}.tar.gz
BuildArch: noarch BuildArch: noarch
@ -78,6 +78,10 @@ help2man %{buildroot}%{_bindir}/httpie > %{buildroot}%{_mandir}/man1/httpie.1
%changelog %changelog
* Tue Mar 08 2022 Miro Hrončok <mhroncok@redhat.com> - 3.1.0-1
- Update to 3.1.0
- Fixes: rhbz#2061597
* Mon Jan 24 2022 Miro Hrončok <mhroncok@redhat.com> - 3.0.2-1 * Mon Jan 24 2022 Miro Hrončok <mhroncok@redhat.com> - 3.0.2-1
- Update to 3.0.2 - Update to 3.0.2
- Fixes: rhbz#2044572 - Fixes: rhbz#2044572

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

@ -19,7 +19,7 @@ Open a pull request to update the [downstream file](https://github.com/macports/
```bash ```bash
# Download the archive # Download the archive
$ wget https://api.github.com/repos/httpie/httpie/tarball/2.5.0 $ wget https://api.github.com/repos/httpie/cli/tarball/2.5.0
# Size # Size
$ stat --printf="%s\n" 2.5.0 $ stat --printf="%s\n" 2.5.0

View File

@ -13,7 +13,16 @@ We will discuss setting up the environment, installing development tools, instal
## Overall process ## Overall process
Trigger a new [build](https://snapcraft.io/httpie/builds), then [promote it](https://snapcraft.io/httpie/releases). If more management is needed: [revisions supervision](https://dashboard.snapcraft.io/snaps/httpie/revisions/). Trigger the [`Release on Snap`](https://github.com/httpie/cli/actions/workflows/release-snap.yml) action, which will
create a snap package for HTTPie and then push it to Snap Store in the following channels:
- Edge
- Beta
- Candidate
- Stable
If a push to any of them fail, all the release tasks for the following channels will be cancelled so that the
release manager can look into the underlying cause.
## Hacking ## Hacking
@ -28,7 +37,7 @@ From inside the container:
```bash ```bash
# Clone # Clone
git clone --depth=1 https://github.com/httpie/httpie.git git clone --depth=1 https://github.com/httpie/cli.git
cd httpie cd httpie
# Build # Build

View File

@ -13,18 +13,23 @@ We will discuss setting up the environment, installing development tools, instal
## Overall process ## Overall process
After having successfully [built and tested](#hacking) the package, push it: After having successfully [built and tested](#hacking) the package, either trigger the
[`Release on Chocolatey`](https://github.com/httpie/cli/actions/workflows/release-choco.yml) action
to push it to the `Chocolatey` store or use the CLI:
```bash ```bash
# Replace 2.5.0 with the correct version # Replace 2.5.0 with the correct version
choco push httpie.2.5.0.nupkg -s https://push.chocolatey.org/ --api-key=API_KEY choco push httpie.2.5.0.nupkg -s https://push.chocolatey.org/ --api-key=API_KEY
``` ```
Be aware that it might take multiple days until the release is approved, sine it goes through multiple
sets of reviews (some of them are done manually).
## Hacking ## Hacking
```bash ```bash
# Clone # Clone
git clone --depth=1 https://github.com/httpie/httpie.git git clone --depth=1 https://github.com/httpie/cli.git
cd httpie/docs/packaging/windows-chocolatey cd httpie/docs/packaging/windows-chocolatey
# Build # Build

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata> <metadata>
<id>httpie</id> <id>httpie</id>
<version>3.0.2</version> <version>3.2.2</version>
<summary>Modern, user-friendly command-line HTTP client for the API era</summary> <summary>Modern, user-friendly command-line HTTP client for the API era</summary>
<description> <description>
HTTPie *aitch-tee-tee-pie* is a user-friendly command-line HTTP client for the API era. HTTPie *aitch-tee-tee-pie* is a user-friendly command-line HTTP client for the API era.
@ -30,16 +30,16 @@ Main features:
<authors>HTTPie</authors> <authors>HTTPie</authors>
<owners>jakubroztocil</owners> <owners>jakubroztocil</owners>
<copyright>2012-2022 Jakub Roztocil</copyright> <copyright>2012-2022 Jakub Roztocil</copyright>
<licenseUrl>https://raw.githubusercontent.com/httpie/httpie/master/LICENSE</licenseUrl> <licenseUrl>https://raw.githubusercontent.com/httpie/cli/master/LICENSE</licenseUrl>
<iconUrl>https://pie-assets.s3.eu-central-1.amazonaws.com/LogoIcons/GB.png</iconUrl> <iconUrl>https://pie-assets.s3.eu-central-1.amazonaws.com/LogoIcons/GB.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<releaseNotes>See the [changelog](https://httpie.io/blog/httpie-3.0.0).</releaseNotes> <releaseNotes>See the [changelog](https://github.com/httpie/cli/releases/tag/3.2.2).</releaseNotes>
<tags>httpie http https rest api client curl python ssl cli foss oss url</tags> <tags>httpie http https rest api client curl python ssl cli foss oss url</tags>
<projectUrl>https://httpie.io</projectUrl> <projectUrl>https://httpie.io</projectUrl>
<packageSourceUrl>https://github.com/httpie/httpie/tree/master/docs/packaging/windows-chocolatey</packageSourceUrl> <packageSourceUrl>https://github.com/httpie/cli/tree/master/docs/packaging/windows-chocolatey</packageSourceUrl>
<projectSourceUrl>https://github.com/httpie/httpie</projectSourceUrl> <projectSourceUrl>https://github.com/httpie/cli</projectSourceUrl>
<docsUrl>https://httpie.io/docs</docsUrl> <docsUrl>https://httpie.io/docs</docsUrl>
<bugTrackerUrl>https://github.com/httpie/httpie/issues</bugTrackerUrl> <bugTrackerUrl>https://github.com/httpie/cli/issues</bugTrackerUrl>
<dependencies> <dependencies>
<dependency id="python3" version="3.7" /> <dependency id="python3" version="3.7" />
</dependencies> </dependencies>

BIN
docs/stardust.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

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

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

@ -0,0 +1,600 @@
.\" This file is auto-generated from the parser declaration in httpie/cli/definition.py by extras/scripts/generate_man_pages.py.
.TH http 1 "2024-07-10" "HTTPie 3.2.4" "HTTPie Manual"
.SH NAME
http
.SH SYNOPSIS
http [METHOD] URL [REQUEST_ITEM ...]
.SH DESCRIPTION
HTTPie: modern, user-friendly command-line HTTP client for the API era. <https://httpie.io>
.SH Positional arguments
These arguments come after any flags and in the order they are listed here.
Only URL is required.
.IP "\fB\,METHOD\/\fR"
The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).
This argument can be omitted in which case HTTPie will use POST if there
is some data to be sent, otherwise GET:
$ http example.org # => GET
$ http example.org hello=world # => POST
.IP "\fB\,URL\/\fR"
The request URL. Scheme defaults to \[aq]http://\[aq] if the URL
does not include one. (You can override this with: \fB\,--default-scheme\/\fR=http/https)
You can also use a shorthand for localhost
$ http :3000 # => http://localhost:3000
$ http :/foo # => http://localhost/foo
.IP "\fB\,REQUEST_ITEM\/\fR"
Optional key-value pairs to be included in the request. The separator used
determines the type:
\[aq]:\[aq] HTTP headers:
Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0
\[aq]==\[aq] URL parameters to be appended to the request URI:
search==httpie
\[aq]=\[aq] Data fields to be serialized into a JSON object (with \fB\,--json\/\fR, \fB\,-j\/\fR)
or form data (with \fB\,--form\/\fR, \fB\,-f\/\fR):
name=HTTPie language=Python description=\[aq]CLI HTTP client\[aq]
\[aq]:=\[aq] Non-string JSON data fields (only with \fB\,--json\/\fR, \fB\,-j\/\fR):
awesome:=true amount:=42 colors:=\[aq][\[dq]red\[dq], \[dq]green\[dq], \[dq]blue\[dq]]\[aq]
\[aq]@\[aq] Form file fields (only with \fB\,--form\/\fR or \fB\,--multipart\/\fR):
cv@\(ti/Documents/CV.pdf
cv@\[aq]\(ti/Documents/CV.pdf;type=application/pdf\[aq]
\[aq]=@\[aq] A data field like \[aq]=\[aq], but takes a file path and embeds its content:
essay=@Documents/essay.txt
\[aq]:=@\[aq] A raw JSON field like \[aq]:=\[aq], but takes a file path and embeds its content:
package:=@./package.json
You can use a backslash to escape a colliding separator in the field name:
field-name-with\e:colon=value
.PP
.SH Predefined content types
.IP "\fB\,--json\/\fR, \fB\,-j\/\fR"
(default) Data items from the command line are serialized as a JSON object.
The Content-Type and Accept headers are set to application/json
(if not specified).
.IP "\fB\,--form\/\fR, \fB\,-f\/\fR"
Data items from the command line are serialized as form fields.
The Content-Type is set to application/x-www-form-urlencoded (if not
specified). The presence of any file fields results in a
multipart/form-data request.
.IP "\fB\,--multipart\/\fR"
Similar to \fB\,--form\/\fR, but always sends a multipart/form-data request (i.e., even without files).
.IP "\fB\,--boundary\/\fR"
Specify a custom boundary string for multipart/form-data requests. Only has effect only together with \fB\,--form\/\fR.
.IP "\fB\,--raw\/\fR"
This option allows you to pass raw request data without extra processing
(as opposed to the structured request items syntax):
$ http \fB\,--raw\/\fR=\[aq]data\[aq] pie.dev/post
You can achieve the same by piping the data via stdin:
$ echo data | http pie.dev/post
Or have HTTPie load the raw data from a file:
$ http pie.dev/post @data.txt
.PP
.SH Content processing options
.IP "\fB\,--compress\/\fR, \fB\,-x\/\fR"
Content compressed (encoded) with Deflate algorithm.
The Content-Encoding header is set to deflate.
Compression is skipped if it appears that compression ratio is
negative. Compression can be forced by repeating the argument.
.PP
.SH Output processing
.IP "\fB\,--pretty\/\fR"
Controls output processing. The value can be \[dq]none\[dq] to not prettify
the output (default for redirected output), \[dq]all\[dq] to apply both colors
and formatting (default for terminal output), \[dq]colors\[dq], or \[dq]format\[dq].
.IP "\fB\,--style\/\fR, \fB\,-s\/\fR \fI\,STYLE\/\fR"
Output coloring style (default is \[dq]auto\[dq]). It can be one of:
auto, pie, pie-dark, pie-light, solarized
For finding out all available styles in your system, try:
$ http \fB\,--style\/\fR
The \[dq]auto\[dq] style follows your terminal\[aq]s ANSI color styles.
For non-auto styles to work properly, please make sure that the
$TERM environment variable is set to \[dq]xterm-256color\[dq] or similar
(e.g., via `export TERM=xterm-256color\[aq] in your \(ti/.bashrc).
.IP "\fB\,--unsorted\/\fR"
Disables all sorting while formatting output. It is a shortcut for:
\fB\,--format-options\/\fR=headers.sort:false,json.sort_keys:false
.IP "\fB\,--sorted\/\fR"
Re-enables all sorting options while formatting output. It is a shortcut for:
\fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true
.IP "\fB\,--response-charset\/\fR \fI\,ENCODING\/\fR"
Override the response encoding for terminal display purposes, e.g.:
\fB\,--response-charset\/\fR=utf8
\fB\,--response-charset\/\fR=big5
.IP "\fB\,--response-mime\/\fR \fI\,MIME_TYPE\/\fR"
Override the response mime type for coloring and formatting for the terminal, e.g.:
\fB\,--response-mime\/\fR=application/json
\fB\,--response-mime\/\fR=text/xml
.IP "\fB\,--format-options\/\fR"
Controls output formatting. Only relevant when formatting is enabled
through (explicit or implied) \fB\,--pretty\/\fR=all or \fB\,--pretty\/\fR=format.
The following are the default options:
headers.sort:true
json.format:true
json.indent:4
json.sort_keys:true
xml.format:true
xml.indent:2
You may use this option multiple times, as well as specify multiple
comma-separated options at the same time. For example, this modifies the
settings to disable the sorting of JSON keys, and sets the indent size to 2:
\fB\,--format-options\/\fR json.sort_keys:false,json.indent:2
This is something you will typically put into your config file.
.PP
.SH Output options
.IP "\fB\,--print\/\fR, \fB\,-p\/\fR \fI\,WHAT\/\fR"
String specifying what the output should contain:
\[aq]H\[aq] request headers
\[aq]B\[aq] request body
\[aq]h\[aq] response headers
\[aq]b\[aq] response body
\[aq]m\[aq] response metadata
The default behaviour is \[aq]hb\[aq] (i.e., the response
headers and body is printed), if standard output is not redirected.
If the output is piped to another program or to a file, then only the
response body is printed by default.
.IP "\fB\,--headers\/\fR, \fB\,-h\/\fR"
Print only the response headers. Shortcut for \fB\,--print\/\fR=h.
.IP "\fB\,--meta\/\fR, \fB\,-m\/\fR"
Print only the response metadata. Shortcut for \fB\,--print\/\fR=m.
.IP "\fB\,--body\/\fR, \fB\,-b\/\fR"
Print only the response body. Shortcut for \fB\,--print\/\fR=b.
.IP "\fB\,--verbose\/\fR, \fB\,-v\/\fR"
Verbose output. For the level one (with single `\fB\,-v\/\fR`/`\fB\,--verbose\/\fR`), print
the whole request as well as the response. Also print any intermediary
requests/responses (such as redirects). For the second level and higher,
print these as well as the response metadata.
Level one is a shortcut for: \fB\,--all\/\fR \fB\,--print\/\fR=BHbh
Level two is a shortcut for: \fB\,--all\/\fR \fB\,--print\/\fR=BHbhm
.IP "\fB\,--all\/\fR"
By default, only the final request/response is shown. Use this flag to show
any intermediary requests/responses as well. Intermediary requests include
followed redirects (with \fB\,--follow\/\fR), the first unauthorized request when
Digest auth is used (\fB\,--auth\/\fR=digest), etc.
.IP "\fB\,--stream\/\fR, \fB\,-S\/\fR"
Always stream the response body by line, i.e., behave like `tail \fB\,-f\/\fR\[aq].
Without \fB\,--stream\/\fR and with \fB\,--pretty\/\fR (either set or implied),
HTTPie fetches the whole response before it outputs the processed data.
Set this option when you want to continuously display a prettified
long-lived response, such as one from the Twitter streaming API.
It is useful also without \fB\,--pretty\/\fR: It ensures that the output is flushed
more often and in smaller chunks.
.IP "\fB\,--output\/\fR, \fB\,-o\/\fR \fI\,FILE\/\fR"
Save output to FILE instead of stdout. If \fB\,--download\/\fR is also set, then only
the response body is saved to FILE. Other parts of the HTTP exchange are
printed to stderr.
.IP "\fB\,--download\/\fR, \fB\,-d\/\fR"
Do not print the response body to stdout. Rather, download it and store it
in a file. The filename is guessed unless specified with \fB\,--output\/\fR
[filename]. This action is similar to the default behaviour of wget.
.IP "\fB\,--continue\/\fR, \fB\,-c\/\fR"
Resume an interrupted download. Note that the \fB\,--output\/\fR option needs to be
specified as well.
.IP "\fB\,--quiet\/\fR, \fB\,-q\/\fR"
Do not print to stdout or stderr, except for errors and warnings when provided once.
Provide twice to suppress warnings as well.
stdout is still redirected if \fB\,--output\/\fR is specified.
Flag doesn\[aq]t affect behaviour of download beyond not printing to terminal.
.PP
.SH Sessions
.IP "\fB\,--session\/\fR \fI\,SESSION_NAME_OR_PATH\/\fR"
Create, or reuse and update a session. Within a session, custom headers,
auth credential, as well as any cookies sent by the server persist between
requests.
Session files are stored in:
[HTTPIE_CONFIG_DIR]/<HOST>/<SESSION_NAME>.json.
See the following page to find out your default HTTPIE_CONFIG_DIR:
https://httpie.io/docs/cli/config-file-directory
.IP "\fB\,--session-read-only\/\fR \fI\,SESSION_NAME_OR_PATH\/\fR"
Create or read a session without updating it form the request/response
exchange.
.PP
.SH Authentication
.IP "\fB\,--auth\/\fR, \fB\,-a\/\fR \fI\,USER[:PASS] | TOKEN\/\fR"
For username/password based authentication mechanisms (e.g
basic auth or digest auth) if only the username is provided
(\fB\,-a\/\fR username), HTTPie will prompt for the password.
.IP "\fB\,--auth-type\/\fR, \fB\,-A\/\fR"
The authentication mechanism to be used. Defaults to \[dq]basic\[dq].
\[dq]basic\[dq]: Basic HTTP auth
\[dq]digest\[dq]: Digest HTTP auth
\[dq]bearer\[dq]: Bearer HTTP Auth
To see all available auth types on your system, including ones installed via plugins, run:
$ http \fB\,--auth-type\/\fR
.IP "\fB\,--ignore-netrc\/\fR"
Ignore credentials from .netrc.
.PP
.SH Network
.IP "\fB\,--offline\/\fR"
Build the request and print it but don\(gat actually send it.
.IP "\fB\,--proxy\/\fR \fI\,PROTOCOL:PROXY_URL\/\fR"
String mapping protocol to the URL of the proxy
(e.g. http:http://foo.bar:3128). You can specify multiple proxies with
different protocols. The environment variables $ALL_PROXY, $HTTP_PROXY,
and $HTTPS_proxy are supported as well.
.IP "\fB\,--follow\/\fR, \fB\,-F\/\fR"
Follow 30x Location redirects.
.IP "\fB\,--max-redirects\/\fR"
By default, requests have a limit of 30 redirects (works with \fB\,--follow\/\fR).
.IP "\fB\,--max-headers\/\fR"
The maximum number of response headers to be read before giving up (default 0, i.e., no limit).
.IP "\fB\,--timeout\/\fR \fI\,SECONDS\/\fR"
The connection timeout of the request in seconds.
The default value is 0, i.e., there is no timeout limit.
This is not a time limit on the entire response download;
rather, an error is reported if the server has not issued a response for
timeout seconds (more precisely, if no bytes have been received on
the underlying socket for timeout seconds).
.IP "\fB\,--check-status\/\fR"
By default, HTTPie exits with 0 when no network or other fatal errors
occur. This flag instructs HTTPie to also check the HTTP status code and
exit with an error if the status indicates one.
When the server replies with a 4xx (Client Error) or 5xx (Server Error)
status code, HTTPie exits with 4 or 5 respectively. If the response is a
3xx (Redirect) and \fB\,--follow\/\fR hasn\[aq]t been set, then the exit status is 3.
Also an error message is written to stderr if stdout is redirected.
.IP "\fB\,--path-as-is\/\fR"
Bypass dot segment (/../ or /./) URL squashing.
.IP "\fB\,--chunked\/\fR"
Enable streaming via chunked transfer encoding. The Transfer-Encoding header is set to chunked.
.PP
.SH SSL
.IP "\fB\,--verify\/\fR"
Set to \[dq]no\[dq] (or \[dq]false\[dq]) to skip checking the host\[aq]s SSL certificate.
Defaults to \[dq]yes\[dq] (\[dq]true\[dq]). You can also pass the path to a CA_BUNDLE file
for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment
variable instead.)
.IP "\fB\,--ssl\/\fR"
The desired protocol version to use. This will default to
SSL v2.3 which will negotiate the highest protocol that both
the server and your installation of OpenSSL support. Available protocols
may vary depending on OpenSSL installation (only the supported ones
are shown here).
.IP "\fB\,--ciphers\/\fR"
A string in the OpenSSL cipher list format.
See `http \fB\,--help\/\fR` for the default ciphers list on you system.
.IP "\fB\,--cert\/\fR"
You can specify a local cert to use as client side SSL certificate.
This file may either contain both private key and certificate or you may
specify \fB\,--cert-key\/\fR separately.
.IP "\fB\,--cert-key\/\fR"
The private key to use with SSL. Only needed if \fB\,--cert\/\fR is given and the
certificate file does not contain the private key.
.IP "\fB\,--cert-key-pass\/\fR"
The passphrase to be used to with the given private key. Only needed if \fB\,--cert-key\/\fR
is given and the key file requires a passphrase.
If not provided, you\(gall be prompted interactively.
.PP
.SH Troubleshooting
.IP "\fB\,--ignore-stdin\/\fR, \fB\,-I\/\fR"
Do not attempt to read stdin
.IP "\fB\,--help\/\fR"
Show this help message and exit.
.IP "\fB\,--manual\/\fR"
Show the full manual.
.IP "\fB\,--version\/\fR"
Show version and exit.
.IP "\fB\,--traceback\/\fR"
Prints the exception traceback should one occur.
.IP "\fB\,--default-scheme\/\fR"
The default scheme to use if not specified in the URL.
.IP "\fB\,--debug\/\fR"
Prints the exception traceback should one occur, as well as other
information useful for debugging HTTPie itself and for reporting bugs.
.PP
.SH SEE ALSO
For every \fB\,--OPTION\/\fR there is also a \fB\,--no-OPTION\/\fR that reverts OPTION
to its default value.
Suggestions and bug reports are greatly appreciated:
https://github.com/httpie/cli/issues

100
extras/man/httpie.1 Normal file
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 "2024-07-10" "HTTPie 3.2.4" "HTTPie Manual"
.SH NAME
httpie
.SH SYNOPSIS
httpie
.SH DESCRIPTION
Managing interface for the HTTPie itself. <https://httpie.io/docs#manager>
Be aware that you might be looking for http/https commands for sending
HTTP requests. This command is only available for managing the HTTTPie
plugins and the configuration around it.
If you are looking for the man pages of http/https commands, try one of the following:
$ man http
$ man https
.SH httpie cli export-args
Export available options for the CLI
.IP "\fB\,-f\/\fR, \fB\,--format\/\fR"
Format to export in.
.PP
.SH httpie cli check-updates
Check for updates
.PP
.SH httpie cli sessions upgrade
Upgrade the given HTTPie session with the latest layout. A list of changes between different session versions can be found in the official documentation.
.IP "\fB\,HOSTNAME\/\fR"
The host this session belongs.
.IP "\fB\,SESSION_NAME_OR_PATH\/\fR"
The name or the path for the session that will be upgraded.
.IP "\fB\,--bind-cookies\/\fR"
Bind domainless cookies to the host that session belongs.
.PP
.SH httpie cli sessions upgrade-all
Upgrade all named sessions with the latest layout. A list of changes between different session versions can be found in the official documentation.
.IP "\fB\,--bind-cookies\/\fR"
Bind domainless cookies to the host that session belongs.
.PP
.SH httpie cli plugins install
Install the given targets from PyPI or from a local paths.
.IP "\fB\,TARGET\/\fR"
targets to install
.PP
.SH httpie cli plugins upgrade
Upgrade the given plugins
.IP "\fB\,TARGET\/\fR"
targets to upgrade
.PP
.SH httpie cli plugins uninstall
Uninstall the given HTTPie plugins.
.IP "\fB\,TARGET\/\fR"
targets to install
.PP
.SH httpie cli plugins list
List all installed HTTPie plugins.
.PP
.SH httpie plugins install
Install the given targets from PyPI or from a local paths.
.IP "\fB\,TARGET\/\fR"
targets to install
.PP
.SH httpie plugins upgrade
Upgrade the given plugins
.IP "\fB\,TARGET\/\fR"
targets to upgrade
.PP
.SH httpie plugins uninstall
Uninstall the given HTTPie plugins.
.IP "\fB\,TARGET\/\fR"
targets to install
.PP
.SH httpie plugins list
List all installed HTTPie plugins.
.PP

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

@ -0,0 +1,600 @@
.\" This file is auto-generated from the parser declaration in httpie/cli/definition.py by extras/scripts/generate_man_pages.py.
.TH https 1 "2024-07-10" "HTTPie 3.2.4" "HTTPie Manual"
.SH NAME
https
.SH SYNOPSIS
https [METHOD] URL [REQUEST_ITEM ...]
.SH DESCRIPTION
HTTPie: modern, user-friendly command-line HTTP client for the API era. <https://httpie.io>
.SH Positional arguments
These arguments come after any flags and in the order they are listed here.
Only URL is required.
.IP "\fB\,METHOD\/\fR"
The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).
This argument can be omitted in which case HTTPie will use POST if there
is some data to be sent, otherwise GET:
$ http example.org # => GET
$ http example.org hello=world # => POST
.IP "\fB\,URL\/\fR"
The request URL. Scheme defaults to \[aq]http://\[aq] if the URL
does not include one. (You can override this with: \fB\,--default-scheme\/\fR=http/https)
You can also use a shorthand for localhost
$ http :3000 # => http://localhost:3000
$ http :/foo # => http://localhost/foo
.IP "\fB\,REQUEST_ITEM\/\fR"
Optional key-value pairs to be included in the request. The separator used
determines the type:
\[aq]:\[aq] HTTP headers:
Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0
\[aq]==\[aq] URL parameters to be appended to the request URI:
search==httpie
\[aq]=\[aq] Data fields to be serialized into a JSON object (with \fB\,--json\/\fR, \fB\,-j\/\fR)
or form data (with \fB\,--form\/\fR, \fB\,-f\/\fR):
name=HTTPie language=Python description=\[aq]CLI HTTP client\[aq]
\[aq]:=\[aq] Non-string JSON data fields (only with \fB\,--json\/\fR, \fB\,-j\/\fR):
awesome:=true amount:=42 colors:=\[aq][\[dq]red\[dq], \[dq]green\[dq], \[dq]blue\[dq]]\[aq]
\[aq]@\[aq] Form file fields (only with \fB\,--form\/\fR or \fB\,--multipart\/\fR):
cv@\(ti/Documents/CV.pdf
cv@\[aq]\(ti/Documents/CV.pdf;type=application/pdf\[aq]
\[aq]=@\[aq] A data field like \[aq]=\[aq], but takes a file path and embeds its content:
essay=@Documents/essay.txt
\[aq]:=@\[aq] A raw JSON field like \[aq]:=\[aq], but takes a file path and embeds its content:
package:=@./package.json
You can use a backslash to escape a colliding separator in the field name:
field-name-with\e:colon=value
.PP
.SH Predefined content types
.IP "\fB\,--json\/\fR, \fB\,-j\/\fR"
(default) Data items from the command line are serialized as a JSON object.
The Content-Type and Accept headers are set to application/json
(if not specified).
.IP "\fB\,--form\/\fR, \fB\,-f\/\fR"
Data items from the command line are serialized as form fields.
The Content-Type is set to application/x-www-form-urlencoded (if not
specified). The presence of any file fields results in a
multipart/form-data request.
.IP "\fB\,--multipart\/\fR"
Similar to \fB\,--form\/\fR, but always sends a multipart/form-data request (i.e., even without files).
.IP "\fB\,--boundary\/\fR"
Specify a custom boundary string for multipart/form-data requests. Only has effect only together with \fB\,--form\/\fR.
.IP "\fB\,--raw\/\fR"
This option allows you to pass raw request data without extra processing
(as opposed to the structured request items syntax):
$ http \fB\,--raw\/\fR=\[aq]data\[aq] pie.dev/post
You can achieve the same by piping the data via stdin:
$ echo data | http pie.dev/post
Or have HTTPie load the raw data from a file:
$ http pie.dev/post @data.txt
.PP
.SH Content processing options
.IP "\fB\,--compress\/\fR, \fB\,-x\/\fR"
Content compressed (encoded) with Deflate algorithm.
The Content-Encoding header is set to deflate.
Compression is skipped if it appears that compression ratio is
negative. Compression can be forced by repeating the argument.
.PP
.SH Output processing
.IP "\fB\,--pretty\/\fR"
Controls output processing. The value can be \[dq]none\[dq] to not prettify
the output (default for redirected output), \[dq]all\[dq] to apply both colors
and formatting (default for terminal output), \[dq]colors\[dq], or \[dq]format\[dq].
.IP "\fB\,--style\/\fR, \fB\,-s\/\fR \fI\,STYLE\/\fR"
Output coloring style (default is \[dq]auto\[dq]). It can be one of:
auto, pie, pie-dark, pie-light, solarized
For finding out all available styles in your system, try:
$ http \fB\,--style\/\fR
The \[dq]auto\[dq] style follows your terminal\[aq]s ANSI color styles.
For non-auto styles to work properly, please make sure that the
$TERM environment variable is set to \[dq]xterm-256color\[dq] or similar
(e.g., via `export TERM=xterm-256color\[aq] in your \(ti/.bashrc).
.IP "\fB\,--unsorted\/\fR"
Disables all sorting while formatting output. It is a shortcut for:
\fB\,--format-options\/\fR=headers.sort:false,json.sort_keys:false
.IP "\fB\,--sorted\/\fR"
Re-enables all sorting options while formatting output. It is a shortcut for:
\fB\,--format-options\/\fR=headers.sort:true,json.sort_keys:true
.IP "\fB\,--response-charset\/\fR \fI\,ENCODING\/\fR"
Override the response encoding for terminal display purposes, e.g.:
\fB\,--response-charset\/\fR=utf8
\fB\,--response-charset\/\fR=big5
.IP "\fB\,--response-mime\/\fR \fI\,MIME_TYPE\/\fR"
Override the response mime type for coloring and formatting for the terminal, e.g.:
\fB\,--response-mime\/\fR=application/json
\fB\,--response-mime\/\fR=text/xml
.IP "\fB\,--format-options\/\fR"
Controls output formatting. Only relevant when formatting is enabled
through (explicit or implied) \fB\,--pretty\/\fR=all or \fB\,--pretty\/\fR=format.
The following are the default options:
headers.sort:true
json.format:true
json.indent:4
json.sort_keys:true
xml.format:true
xml.indent:2
You may use this option multiple times, as well as specify multiple
comma-separated options at the same time. For example, this modifies the
settings to disable the sorting of JSON keys, and sets the indent size to 2:
\fB\,--format-options\/\fR json.sort_keys:false,json.indent:2
This is something you will typically put into your config file.
.PP
.SH Output options
.IP "\fB\,--print\/\fR, \fB\,-p\/\fR \fI\,WHAT\/\fR"
String specifying what the output should contain:
\[aq]H\[aq] request headers
\[aq]B\[aq] request body
\[aq]h\[aq] response headers
\[aq]b\[aq] response body
\[aq]m\[aq] response metadata
The default behaviour is \[aq]hb\[aq] (i.e., the response
headers and body is printed), if standard output is not redirected.
If the output is piped to another program or to a file, then only the
response body is printed by default.
.IP "\fB\,--headers\/\fR, \fB\,-h\/\fR"
Print only the response headers. Shortcut for \fB\,--print\/\fR=h.
.IP "\fB\,--meta\/\fR, \fB\,-m\/\fR"
Print only the response metadata. Shortcut for \fB\,--print\/\fR=m.
.IP "\fB\,--body\/\fR, \fB\,-b\/\fR"
Print only the response body. Shortcut for \fB\,--print\/\fR=b.
.IP "\fB\,--verbose\/\fR, \fB\,-v\/\fR"
Verbose output. For the level one (with single `\fB\,-v\/\fR`/`\fB\,--verbose\/\fR`), print
the whole request as well as the response. Also print any intermediary
requests/responses (such as redirects). For the second level and higher,
print these as well as the response metadata.
Level one is a shortcut for: \fB\,--all\/\fR \fB\,--print\/\fR=BHbh
Level two is a shortcut for: \fB\,--all\/\fR \fB\,--print\/\fR=BHbhm
.IP "\fB\,--all\/\fR"
By default, only the final request/response is shown. Use this flag to show
any intermediary requests/responses as well. Intermediary requests include
followed redirects (with \fB\,--follow\/\fR), the first unauthorized request when
Digest auth is used (\fB\,--auth\/\fR=digest), etc.
.IP "\fB\,--stream\/\fR, \fB\,-S\/\fR"
Always stream the response body by line, i.e., behave like `tail \fB\,-f\/\fR\[aq].
Without \fB\,--stream\/\fR and with \fB\,--pretty\/\fR (either set or implied),
HTTPie fetches the whole response before it outputs the processed data.
Set this option when you want to continuously display a prettified
long-lived response, such as one from the Twitter streaming API.
It is useful also without \fB\,--pretty\/\fR: It ensures that the output is flushed
more often and in smaller chunks.
.IP "\fB\,--output\/\fR, \fB\,-o\/\fR \fI\,FILE\/\fR"
Save output to FILE instead of stdout. If \fB\,--download\/\fR is also set, then only
the response body is saved to FILE. Other parts of the HTTP exchange are
printed to stderr.
.IP "\fB\,--download\/\fR, \fB\,-d\/\fR"
Do not print the response body to stdout. Rather, download it and store it
in a file. The filename is guessed unless specified with \fB\,--output\/\fR
[filename]. This action is similar to the default behaviour of wget.
.IP "\fB\,--continue\/\fR, \fB\,-c\/\fR"
Resume an interrupted download. Note that the \fB\,--output\/\fR option needs to be
specified as well.
.IP "\fB\,--quiet\/\fR, \fB\,-q\/\fR"
Do not print to stdout or stderr, except for errors and warnings when provided once.
Provide twice to suppress warnings as well.
stdout is still redirected if \fB\,--output\/\fR is specified.
Flag doesn\[aq]t affect behaviour of download beyond not printing to terminal.
.PP
.SH Sessions
.IP "\fB\,--session\/\fR \fI\,SESSION_NAME_OR_PATH\/\fR"
Create, or reuse and update a session. Within a session, custom headers,
auth credential, as well as any cookies sent by the server persist between
requests.
Session files are stored in:
[HTTPIE_CONFIG_DIR]/<HOST>/<SESSION_NAME>.json.
See the following page to find out your default HTTPIE_CONFIG_DIR:
https://httpie.io/docs/cli/config-file-directory
.IP "\fB\,--session-read-only\/\fR \fI\,SESSION_NAME_OR_PATH\/\fR"
Create or read a session without updating it form the request/response
exchange.
.PP
.SH Authentication
.IP "\fB\,--auth\/\fR, \fB\,-a\/\fR \fI\,USER[:PASS] | TOKEN\/\fR"
For username/password based authentication mechanisms (e.g
basic auth or digest auth) if only the username is provided
(\fB\,-a\/\fR username), HTTPie will prompt for the password.
.IP "\fB\,--auth-type\/\fR, \fB\,-A\/\fR"
The authentication mechanism to be used. Defaults to \[dq]basic\[dq].
\[dq]basic\[dq]: Basic HTTP auth
\[dq]digest\[dq]: Digest HTTP auth
\[dq]bearer\[dq]: Bearer HTTP Auth
To see all available auth types on your system, including ones installed via plugins, run:
$ http \fB\,--auth-type\/\fR
.IP "\fB\,--ignore-netrc\/\fR"
Ignore credentials from .netrc.
.PP
.SH Network
.IP "\fB\,--offline\/\fR"
Build the request and print it but don\(gat actually send it.
.IP "\fB\,--proxy\/\fR \fI\,PROTOCOL:PROXY_URL\/\fR"
String mapping protocol to the URL of the proxy
(e.g. http:http://foo.bar:3128). You can specify multiple proxies with
different protocols. The environment variables $ALL_PROXY, $HTTP_PROXY,
and $HTTPS_proxy are supported as well.
.IP "\fB\,--follow\/\fR, \fB\,-F\/\fR"
Follow 30x Location redirects.
.IP "\fB\,--max-redirects\/\fR"
By default, requests have a limit of 30 redirects (works with \fB\,--follow\/\fR).
.IP "\fB\,--max-headers\/\fR"
The maximum number of response headers to be read before giving up (default 0, i.e., no limit).
.IP "\fB\,--timeout\/\fR \fI\,SECONDS\/\fR"
The connection timeout of the request in seconds.
The default value is 0, i.e., there is no timeout limit.
This is not a time limit on the entire response download;
rather, an error is reported if the server has not issued a response for
timeout seconds (more precisely, if no bytes have been received on
the underlying socket for timeout seconds).
.IP "\fB\,--check-status\/\fR"
By default, HTTPie exits with 0 when no network or other fatal errors
occur. This flag instructs HTTPie to also check the HTTP status code and
exit with an error if the status indicates one.
When the server replies with a 4xx (Client Error) or 5xx (Server Error)
status code, HTTPie exits with 4 or 5 respectively. If the response is a
3xx (Redirect) and \fB\,--follow\/\fR hasn\[aq]t been set, then the exit status is 3.
Also an error message is written to stderr if stdout is redirected.
.IP "\fB\,--path-as-is\/\fR"
Bypass dot segment (/../ or /./) URL squashing.
.IP "\fB\,--chunked\/\fR"
Enable streaming via chunked transfer encoding. The Transfer-Encoding header is set to chunked.
.PP
.SH SSL
.IP "\fB\,--verify\/\fR"
Set to \[dq]no\[dq] (or \[dq]false\[dq]) to skip checking the host\[aq]s SSL certificate.
Defaults to \[dq]yes\[dq] (\[dq]true\[dq]). You can also pass the path to a CA_BUNDLE file
for private certs. (Or you can set the REQUESTS_CA_BUNDLE environment
variable instead.)
.IP "\fB\,--ssl\/\fR"
The desired protocol version to use. This will default to
SSL v2.3 which will negotiate the highest protocol that both
the server and your installation of OpenSSL support. Available protocols
may vary depending on OpenSSL installation (only the supported ones
are shown here).
.IP "\fB\,--ciphers\/\fR"
A string in the OpenSSL cipher list format.
See `http \fB\,--help\/\fR` for the default ciphers list on you system.
.IP "\fB\,--cert\/\fR"
You can specify a local cert to use as client side SSL certificate.
This file may either contain both private key and certificate or you may
specify \fB\,--cert-key\/\fR separately.
.IP "\fB\,--cert-key\/\fR"
The private key to use with SSL. Only needed if \fB\,--cert\/\fR is given and the
certificate file does not contain the private key.
.IP "\fB\,--cert-key-pass\/\fR"
The passphrase to be used to with the given private key. Only needed if \fB\,--cert-key\/\fR
is given and the key file requires a passphrase.
If not provided, you\(gall be prompted interactively.
.PP
.SH Troubleshooting
.IP "\fB\,--ignore-stdin\/\fR, \fB\,-I\/\fR"
Do not attempt to read stdin
.IP "\fB\,--help\/\fR"
Show this help message and exit.
.IP "\fB\,--manual\/\fR"
Show the full manual.
.IP "\fB\,--version\/\fR"
Show version and exit.
.IP "\fB\,--traceback\/\fR"
Prints the exception traceback should one occur.
.IP "\fB\,--default-scheme\/\fR"
The default scheme to use if not specified in the URL.
.IP "\fB\,--debug\/\fR"
Prints the exception traceback should one occur, as well as other
information useful for debugging HTTPie itself and for reporting bugs.
.PP
.SH SEE ALSO
For every \fB\,--OPTION\/\fR there is also a \fB\,--no-OPTION\/\fR that reverts OPTION
to its default value.
Suggestions and bug reports are greatly appreciated:
https://github.com/httpie/cli/issues

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/cli/actions/workflows/release-linux-standalone.yml)
action. Once it finishes, the release artifacts will be attached in the summary page of the triggered run.
## Hacking
The main entry point for the package builder is the [`build.py`](https://github.com/httpie/cli/blob/master/extras/packaging/linux/build.py). It
contains 2 major methods:
- `build_binaries`, for the self-contained executables
- `build_packages`, for the OS-specific packages (which wrap the binaries)
### `build_binaries`
We use [PyInstaller](https://pyinstaller.readthedocs.io/en/stable/) for the binaries. Normally pyinstaller offers two different modes:
- Single directory (harder to distribute, low redundancy. Library files are shared across different executables)
- Single binary (easier to distribute, higher redundancy. Same libraries are statically linked to different executables, so higher total size)
Since our binary size (in total 20 MiBs) is not that big, we have decided to choose the single binary mode for the sake of easier distribution.
We also disable `UPX`, which is a runtime decompression method since it adds some startup cost.
### `build_packages`
We build our OS-specific packages with [FPM](https://github.com/jordansissel/fpm) which offers a really nice abstraction. We use the `dir` mode,
and package `http`, `https` and `httpie` commands. More can be added to the `files` option.
Since the `httpie` depends on having a pip executable, we explicitly depend on the system Python even though the core does not use it.
### Docker Image
This directory also contains a [docker image](https://github.com/httpie/cli/blob/master/extras/packaging/linux/Dockerfile) which helps
building our standalone binaries in an isolated environment with the lowest possible library versions. This is important, since even though
the executables are standalone they still depend on some main system C libraries (like `glibc`) so we need to create our executables inside
an environment with a very old (but not deprecated) glibc version. It makes us soundproof for all active Ubuntu/Debian versions.
It also contains the Python version we package our HTTPie with, so it is the place if you need to change it.
### `./get_release_artifacts.sh`
If you make a change in the `build.py`, run the following script to test it out. It will return multiple files under `artifacts/dist` which
then you can test out and ensure their quality (it is also the script that we use in our automation).

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

@ -1,7 +1,7 @@
# HTTPie Benchmarking Infrastructure # HTTPie Benchmarking Infrastructure
This directory includes the benchmarks we use for testing HTTPie's speed and the This directory includes the benchmarks we use for testing HTTPie's speed and the
infrastructure to automate this testing accross versions. infrastructure to automate this testing across versions.
## Usage ## Usage
@ -10,7 +10,7 @@ Ensure the following requirements are satisfied:
- Python 3.7+ - Python 3.7+
- `pyperf` - `pyperf`
Then, run the `extras/benchmarks/run.py`: Then, run the `extras/profiling/run.py`:
```console ```console
$ python extras/profiling/run.py $ python extras/profiling/run.py
@ -28,12 +28,12 @@ on both of them. It will compare the results and print it as a markdown table:
| Geometric mean | (ref) | 1.10x faster | | Geometric mean | (ref) | 1.10x faster |
If your `master` branch is not up-to-date, you can get a fresh clone by passing 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` `--fresh` option. This way, the benchmark runner will clone the `httpie/cli`
repo from `GitHub` and use it as the baseline. repo from `GitHub` and use it as the baseline.
You can customize these branches by passing `--local-repo`/`--target-branch`, You can customize these branches by passing `--local-repo`/`--target-branch`,
and customize the repos by passing `--local-repo`/`--target-repo` (can either and customize the repos by passing `--local-repo`/`--target-repo` (can either
take a URL or a path). take a URL or a path).
If you want to run a third enviroment with additional dependencies (such as If you want to run a third environment with additional dependencies (such as
`pyOpenSSL`), you can pass `--complex`. `pyOpenSSL`), you can pass `--complex`.

View File

@ -9,11 +9,11 @@ timings.
The benchmarks are run through 'pyperf', which allows to The benchmarks are run through 'pyperf', which allows to
do get very precise results. For micro-benchmarks like startup, do get very precise results. For micro-benchmarks like startup,
please run `pyperf system tune` to get even more acurrate results. please run `pyperf system tune` to get even more accurate results.
Examples: Examples:
# Run everything as usual, the default is that we do 3 warmup runs # Run everything as usual, the default is that we do 3 warm-up runs
# and 5 actual runs. # and 5 actual runs.
$ python extras/profiling/benchmarks.py $ python extras/profiling/benchmarks.py
@ -21,7 +21,7 @@ Examples:
$ python extras/profiling/benchmarks.py --fast $ python extras/profiling/benchmarks.py --fast
# For verify everything works as expected, pass --debug-single-value. # For verify everything works as expected, pass --debug-single-value.
# It will only run everything once, so the resuls are not realiable. But # It will only run everything once, so the resuls are not reliable. But
# very useful when iterating on a benchmark # very useful when iterating on a benchmark
$ python extras/profiling/benchmarks.py --debug-single-value $ python extras/profiling/benchmarks.py --debug-single-value

View File

@ -19,19 +19,19 @@ which would include additional dependencies like pyOpenSSL.
Examples: Examples:
# Run everything as usual, and compare last commit with master # Run everything as usual, and compare last commit with master
$ python extras/benchmarks/run.py $ python extras/profiling/run.py
# Include complex environments # Include complex environments
$ python extras/benchmarks/run.py --complex $ python extras/profiling/run.py --complex
# Compare against a fresh copy # Compare against a fresh copy
$ python extras/benchmarks/run.py --fresh $ python extras/profiling/run.py --fresh
# Compare against a custom branch of a custom repo # Compare against a custom branch of a custom repo
$ python extras/benchmarks/run.py --target-repo my_repo --target-branch my_branch $ python extras/profiling/run.py --target-repo my_repo --target-branch my_branch
# Debug changes made on this script (only run benchmarks once) # Debug changes made on this script (only run benchmarks once)
$ python extras/benchmarks/run.py --debug $ python extras/profiling/run.py --debug
""" """
import dataclasses import dataclasses
@ -50,7 +50,7 @@ from typing import (IO, Dict, Generator, Iterable, List, Optional,
BENCHMARK_SCRIPT = Path(__file__).parent / 'benchmarks.py' BENCHMARK_SCRIPT = Path(__file__).parent / 'benchmarks.py'
CURRENT_REPO = Path(__file__).parent.parent.parent CURRENT_REPO = Path(__file__).parent.parent.parent
GITHUB_URL = 'https://github.com/httpie/httpie.git' GITHUB_URL = 'https://github.com/httpie/cli.git'
TARGET_BRANCH = 'master' TARGET_BRANCH = 'master'
# Additional dependencies for --complex # Additional dependencies for --complex

View File

@ -0,0 +1,188 @@
import os
import re
from contextlib import contextmanager
from pathlib import Path
from typing import Optional, Iterator, Iterable
# So that httpie.cli.definition can provide man-page-specific output. Must be set before importing httpie.
os.environ['HTTPIE_BUILDING_MAN_PAGES'] = '1'
import httpie
from httpie.cli.definition import options as core_options, IS_MAN_PAGE
from httpie.cli.options import ParserSpec
from httpie.manager.cli import options as manager_options
from httpie.output.ui.rich_help import OptionsHighlighter, to_usage
from httpie.output.ui.rich_utils import render_as_string
assert IS_MAN_PAGE, 'CLI definition does not understand were building man pages'
# Escape certain characters, so they are rendered properly on all terminals.
# <https://man7.org/linux/man-pages/man7/groff_char.7.html>
ESCAPE_MAP = {
'"': '\[dq]',
"'": '\[aq]',
'~': '\(ti',
'': "\(ga",
'\\': '\e',
}
ESCAPE_MAP = {ord(key): value for key, value in ESCAPE_MAP.items()}
EXTRAS_DIR = Path(__file__).parent.parent
MAN_PAGE_PATH = EXTRAS_DIR / 'man'
PROJECT_ROOT = EXTRAS_DIR.parent
OPTION_HIGHLIGHT_RE = re.compile(
OptionsHighlighter.highlights[0]
)
class ManPageBuilder:
def __init__(self):
self.source = []
def title_line(
self,
full_name: str,
program_name: str,
program_version: str,
last_edit_date: str,
) -> None:
self.source.append(
f'.TH {program_name} 1 "{last_edit_date}" '
f'"{full_name} {program_version}" "{full_name} Manual"'
)
def set_name(self, program_name: str) -> None:
with self.section('NAME'):
self.write(program_name)
def write(self, text: str, *, bold: bool = False) -> None:
if bold:
text = '.B ' + text
self.source.append(text)
def separate(self) -> None:
self.source.append('.PP')
def format_desc(self, desc: str) -> str:
description = _escape_and_dedent(desc)
description = OPTION_HIGHLIGHT_RE.sub(
# Boldify the option part, but don't remove the prefix (start of the match).
lambda match: match[1] + self.boldify(match['option']),
description
)
return description
def add_comment(self, comment: str) -> None:
self.source.append(f'.\\" {comment}')
def add_options(self, options: Iterable[str], *, metavar: Optional[str] = None) -> None:
text = ", ".join(map(self.boldify, options))
if metavar:
text += f' {self.underline(metavar)}'
self.write(f'.IP "{text}"')
def build(self) -> str:
return '\n'.join(self.source)
@contextmanager
def section(self, section_name: str) -> Iterator[None]:
self.write(f'.SH {section_name}')
self.in_section = True
yield
self.in_section = False
def underline(self, text: str) -> str:
return r'\fI\,{}\/\fR'.format(text)
def boldify(self, text: str) -> str:
return r'\fB\,{}\/\fR'.format(text)
def _escape_and_dedent(text: str) -> str:
lines = []
for should_act, line in enumerate(text.splitlines()):
# Only dedent after the first line.
if should_act:
if line.startswith(' '):
line = line[4:]
lines.append(line)
return '\n'.join(lines).translate(ESCAPE_MAP)
def to_man_page(program_name: str, spec: ParserSpec, *, is_top_level_cmd: bool = False) -> str:
builder = ManPageBuilder()
builder.add_comment(
f"This file is auto-generated from the parser declaration "
+ (f"in {Path(spec.source_file).relative_to(PROJECT_ROOT)} " if spec.source_file else "")
+ f"by {Path(__file__).relative_to(PROJECT_ROOT)}."
)
builder.title_line(
full_name='HTTPie',
program_name=program_name,
program_version=httpie.__version__,
last_edit_date=httpie.__date__,
)
builder.set_name(program_name)
with builder.section('SYNOPSIS'):
# `http` and `https` are commands that can be directly used, so they can have
# a valid usage. But `httpie` is a top-level command with multiple sub commands,
# so for the synopsis we'll only reference the `httpie` name.
if is_top_level_cmd:
synopsis = program_name
else:
synopsis = render_as_string(to_usage(spec, program_name=program_name))
builder.write(synopsis)
with builder.section('DESCRIPTION'):
builder.write(spec.description)
if spec.man_page_hint:
builder.write(spec.man_page_hint)
for index, group in enumerate(spec.groups, 1):
with builder.section(group.name):
if group.description:
builder.write(group.description)
for argument in group.arguments:
if argument.is_hidden:
continue
raw_arg = argument.serialize(isolation_mode=True)
metavar = raw_arg.get('metavar')
if raw_arg.get('is_positional'):
# In case of positional arguments, metavar is always equal
# to the list of options (e.g `METHOD`).
metavar = None
builder.add_options(raw_arg['options'], metavar=metavar)
desc = builder.format_desc(raw_arg.get('description', ''))
builder.write('\n' + desc + '\n')
builder.separate()
if spec.epilog:
with builder.section('SEE ALSO'):
builder.write(builder.format_desc(spec.epilog))
return builder.build()
def main() -> None:
for program_name, spec, config in [
('http', core_options, {}),
('https', core_options, {}),
('httpie', manager_options, {'is_top_level_cmd': True}),
]:
with open((MAN_PAGE_PATH / program_name).with_suffix('.1'), 'w') as stream:
stream.write(to_man_page(program_name, spec, **config))
if __name__ == '__main__':
main()

View File

@ -3,6 +3,7 @@ HTTPie: modern, user-friendly command-line HTTP client for the API era.
""" """
__version__ = '3.1.0' __version__ = '3.2.4'
__date__ = '2024-11-01'
__author__ = 'Jakub Roztocil' __author__ = 'Jakub Roztocil'
__licence__ = 'BSD' __licence__ = 'BSD'

View File

@ -155,6 +155,7 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
namespace=None namespace=None
) -> argparse.Namespace: ) -> argparse.Namespace:
self.env = env self.env = env
self.env.args = namespace = namespace or argparse.Namespace()
self.args, no_options = super().parse_known_args(args, namespace) self.args, no_options = super().parse_known_args(args, namespace)
if self.args.debug: if self.args.debug:
self.args.traceback = True self.args.traceback = True
@ -557,19 +558,56 @@ class HTTPieArgumentParser(BaseHTTPieArgumentParser):
parsed_options = parse_format_options(options_group, defaults=parsed_options) parsed_options = parse_format_options(options_group, defaults=parsed_options)
self.args.format_options = parsed_options self.args.format_options = parsed_options
def print_manual(self):
from httpie.output.ui import man_pages
if man_pages.is_available(self.env.program_name):
man_pages.display_for(self.env, self.env.program_name)
return None
text = self.format_help()
with self.env.rich_console.pager():
self.env.rich_console.print(
text,
highlight=False
)
def print_usage(self, file):
from rich.text import Text
from httpie.output.ui import rich_help
whitelist = set()
_, exception, _ = sys.exc_info()
if (
isinstance(exception, argparse.ArgumentError)
and len(exception.args) >= 1
and isinstance(exception.args[0], argparse.Action)
and exception.args[0].option_strings
):
# add_usage path is also taken when you pass an invalid option,
# e.g --style=invalid. If something like that happens, we want
# to include to action that caused to the invalid usage into
# the list of actions we are displaying.
whitelist.add(exception.args[0].option_strings[0])
usage_text = Text('usage', style='bold')
usage_text.append(':\n ')
usage_text.append(rich_help.to_usage(self.spec, whitelist=whitelist))
self.env.rich_error_console.print(usage_text)
def error(self, message): def error(self, message):
"""Prints a usage message incorporating the message to stderr and """Prints a usage message incorporating the message to stderr and
exits.""" exits."""
self.print_usage(sys.stderr) self.print_usage(sys.stderr)
self.exit( self.env.rich_error_console.print(
2,
dedent( dedent(
f''' f'''
error: [bold]error[/bold]:
{message} {message}
for more information: [bold]for more information[/bold]:
run '{self.prog} --help' or visit https://httpie.io/docs/cli run '{self.prog} --help' or visit https://httpie.io/docs/cli
''' '''.rstrip()
) )
) )
self.exit(2)

View File

@ -9,6 +9,7 @@ URL_SCHEME_RE = re.compile(r'^[a-z][a-z0-9.+-]*://', re.IGNORECASE)
HTTP_POST = 'POST' HTTP_POST = 'POST'
HTTP_GET = 'GET' HTTP_GET = 'GET'
HTTP_OPTIONS = 'OPTIONS'
# Various separators used in args # Various separators used in args
SEPARATOR_HEADER = ':' SEPARATOR_HEADER = ':'
@ -131,10 +132,3 @@ class RequestType(enum.Enum):
FORM = enum.auto() FORM = enum.auto()
MULTIPART = enum.auto() MULTIPART = enum.auto()
JSON = enum.auto() JSON = enum.auto()
EMPTY_STRING = ''
OPEN_BRACKET = '['
CLOSE_BRACKET = ']'
BACKSLASH = '\\'
HIGHLIGHTER = '^'

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import os
import textwrap import textwrap
from argparse import FileType from argparse import FileType
@ -16,12 +17,17 @@ from httpie.cli.constants import (BASE_OUTPUT_OPTIONS, DEFAULT_FORMAT_OPTIONS,
SORTED_FORMAT_OPTIONS_STRING, SORTED_FORMAT_OPTIONS_STRING,
UNSORTED_FORMAT_OPTIONS_STRING, RequestType) UNSORTED_FORMAT_OPTIONS_STRING, RequestType)
from httpie.cli.options import ParserSpec, Qualifiers, to_argparse from httpie.cli.options import ParserSpec, Qualifiers, to_argparse
from httpie.output.formatters.colors import (AUTO_STYLE, DEFAULT_STYLE, from httpie.output.formatters.colors import (AUTO_STYLE, DEFAULT_STYLE, BUNDLED_STYLES,
get_available_styles) get_available_styles)
from httpie.plugins.builtin import BuiltinAuthPlugin from httpie.plugins.builtin import BuiltinAuthPlugin
from httpie.plugins.registry import plugin_manager from httpie.plugins.registry import plugin_manager
from httpie.sessions import DEFAULT_SESSIONS_DIR from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS_STRING
from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS
# Man pages are static (built when making a release).
# We use this check to not include generated, system-specific information there (e.g., default --ciphers).
IS_MAN_PAGE = bool(os.environ.get('HTTPIE_BUILDING_MAN_PAGES'))
options = ParserSpec( options = ParserSpec(
'http', 'http',
@ -29,18 +35,19 @@ options = ParserSpec(
epilog=""" epilog="""
For every --OPTION there is also a --no-OPTION that reverts OPTION For every --OPTION there is also a --no-OPTION that reverts OPTION
to its default value. to its default value.
Suggestions and bug reports are greatly appreciated:
https://github.com/httpie/httpie/issues
""",
)
Suggestions and bug reports are greatly appreciated:
https://github.com/httpie/cli/issues
""",
source_file=__file__
)
####################################################################### #######################################################################
# Positional arguments. # Positional arguments.
####################################################################### #######################################################################
positional_arguments = options.add_group( positional_arguments = options.add_group(
'Positional Arguments', 'Positional arguments',
description=""" description="""
These arguments come after any flags and in the order they are listed here. These arguments come after any flags and in the order they are listed here.
Only URL is required. Only URL is required.
@ -52,6 +59,7 @@ positional_arguments.add_argument(
metavar='METHOD', metavar='METHOD',
nargs=Qualifiers.OPTIONAL, nargs=Qualifiers.OPTIONAL,
default=None, default=None,
short_help='The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).',
help=""" help="""
The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...). The HTTP method to be used for the request (GET, POST, PUT, DELETE, ...).
@ -66,9 +74,10 @@ positional_arguments.add_argument(
positional_arguments.add_argument( positional_arguments.add_argument(
dest='url', dest='url',
metavar='URL', metavar='URL',
short_help='The request URL.',
help=""" help="""
The scheme defaults to 'http://' if the URL does not include one. The request URL. Scheme defaults to 'http://' if the URL
(You can override this with: --default-scheme=https) does not include one. (You can override this with: --default-scheme=http/https)
You can also use a shorthand for localhost You can also use a shorthand for localhost
@ -83,6 +92,17 @@ positional_arguments.add_argument(
nargs=Qualifiers.ZERO_OR_MORE, nargs=Qualifiers.ZERO_OR_MORE,
default=None, default=None,
type=KeyValueArgType(*SEPARATOR_GROUP_ALL_ITEMS), type=KeyValueArgType(*SEPARATOR_GROUP_ALL_ITEMS),
short_help=(
'HTTPies request items syntax for specifying HTTP headers, JSON/Form'
'data, files, and URL parameters.'
),
nested_options=[
('HTTP Headers', 'Name:Value', 'Arbitrary HTTP header, e.g X-API-Token:123'),
('URL Parameters', 'name==value', 'Querystring parameter to the URL, e.g limit==50'),
('Data Fields', 'field=value', 'Data fields to be serialized as JSON (default) or Form Data (with --form)'),
('Raw JSON Fields', 'field:=json', 'Data field for real JSON types.'),
('File upload Fields', 'field@/dir/file', 'Path field for uploading a file.'),
],
help=r""" help=r"""
Optional key-value pairs to be included in the request. The separator used Optional key-value pairs to be included in the request. The separator used
determines the type: determines the type:
@ -128,7 +148,7 @@ positional_arguments.add_argument(
# Content type. # Content type.
####################################################################### #######################################################################
content_types = options.add_group('Predefined Content Types') content_types = options.add_group('Predefined content types')
content_types.add_argument( content_types.add_argument(
'--json', '--json',
@ -136,6 +156,7 @@ content_types.add_argument(
action='store_const', action='store_const',
const=RequestType.JSON, const=RequestType.JSON,
dest='request_type', dest='request_type',
short_help='(default) Serialize data items from the command line as a JSON object.',
help=""" help="""
(default) Data items from the command line are serialized as a JSON object. (default) Data items from the command line are serialized as a JSON object.
The Content-Type and Accept headers are set to application/json The Content-Type and Accept headers are set to application/json
@ -149,6 +170,7 @@ content_types.add_argument(
action='store_const', action='store_const',
const=RequestType.FORM, const=RequestType.FORM,
dest='request_type', dest='request_type',
short_help='Serialize data items from the command line as form field data.',
help=""" help="""
Data items from the command line are serialized as form fields. Data items from the command line are serialized as form fields.
@ -163,22 +185,21 @@ content_types.add_argument(
action='store_const', action='store_const',
const=RequestType.MULTIPART, const=RequestType.MULTIPART,
dest='request_type', dest='request_type',
help=""" short_help=(
Similar to --form, but always sends a multipart/form-data 'Similar to --form, but always sends a multipart/form-data '
request (i.e., even without files). 'request (i.e., even without files).'
)
""",
) )
content_types.add_argument( content_types.add_argument(
'--boundary', '--boundary',
help=""" short_help=(
Specify a custom boundary string for multipart/form-data requests. 'Specify a custom boundary string for multipart/form-data requests. '
Only has effect only together with --form. 'Only has effect only together with --form.'
)
""",
) )
content_types.add_argument( content_types.add_argument(
'--raw', '--raw',
short_help='Pass raw request data without extra processing.',
help=""" help="""
This option allows you to pass raw request data without extra processing This option allows you to pass raw request data without extra processing
(as opposed to the structured request items syntax): (as opposed to the structured request items syntax):
@ -201,13 +222,14 @@ content_types.add_argument(
# Content processing. # Content processing.
####################################################################### #######################################################################
processing_options = options.add_group('Content Processing Options') processing_options = options.add_group('Content processing options')
processing_options.add_argument( processing_options.add_argument(
'--compress', '--compress',
'-x', '-x',
action='count', action='count',
default=0, default=0,
short_help='Compress the content with Deflate algorithm.',
help=""" help="""
Content compressed (encoded) with Deflate algorithm. Content compressed (encoded) with Deflate algorithm.
The Content-Encoding header is set to deflate. The Content-Encoding header is set to deflate.
@ -218,27 +240,39 @@ processing_options.add_argument(
""", """,
) )
####################################################################### #######################################################################
# Output processing # Output processing
####################################################################### #######################################################################
def format_style_help(available_styles): def format_style_help(available_styles, *, isolation_mode: bool = False):
return """ text = """
Output coloring style (default is "{default}"). It can be one of: Output coloring style (default is "{default}"). It can be one of:
{available_styles} {available_styles}
"""
if isolation_mode:
text += '\n\n'
text += 'For finding out all available styles in your system, try:\n\n'
text += ' $ http --style\n'
text += textwrap.dedent("""
The "{auto_style}" style follows your terminal's ANSI color styles. The "{auto_style}" style follows your terminal's ANSI color styles.
For non-{auto_style} styles to work properly, please make sure that the For non-{auto_style} styles to work properly, please make sure that the
$TERM environment variable is set to "xterm-256color" or similar $TERM environment variable is set to "xterm-256color" or similar
(e.g., via `export TERM=xterm-256color' in your ~/.bashrc). (e.g., via `export TERM=xterm-256color' in your ~/.bashrc).
""".format( """)
default=DEFAULT_STYLE,
available_styles='\n'.join( if isolation_mode:
available_styles = sorted(BUNDLED_STYLES)
available_styles_text = '\n'.join(
f' {line.strip()}' f' {line.strip()}'
for line in textwrap.wrap(', '.join(available_styles), 60) for line in textwrap.wrap(', '.join(available_styles), 60)
).strip(), ).strip()
return text.format(
default=DEFAULT_STYLE,
available_styles=available_styles_text,
auto_style=AUTO_STYLE, auto_style=AUTO_STYLE,
) )
@ -254,13 +288,14 @@ _unsorted_kwargs = {
'dest': 'format_options', 'dest': 'format_options',
} }
output_processing = options.add_group('Output Processing') output_processing = options.add_group('Output processing')
output_processing.add_argument( output_processing.add_argument(
'--pretty', '--pretty',
dest='prettify', dest='prettify',
default=PRETTY_STDOUT_TTY_ONLY, default=PRETTY_STDOUT_TTY_ONLY,
choices=sorted(PRETTY_MAP.keys()), choices=sorted(PRETTY_MAP.keys()),
short_help='Control the processing of console outputs.',
help=""" help="""
Controls output processing. The value can be "none" to not prettify Controls output processing. The value can be "none" to not prettify
the output (default for redirected output), "all" to apply both colors the output (default for redirected output), "all" to apply both colors
@ -276,6 +311,7 @@ output_processing.add_argument(
default=DEFAULT_STYLE, default=DEFAULT_STYLE,
action='lazy_choices', action='lazy_choices',
getter=get_available_styles, getter=get_available_styles,
short_help=f'Output coloring style (default is "{DEFAULT_STYLE}").',
help_formatter=format_style_help, help_formatter=format_style_help,
) )
@ -291,6 +327,7 @@ output_processing.add_argument(
output_processing.add_argument( output_processing.add_argument(
'--unsorted', '--unsorted',
**_unsorted_kwargs, **_unsorted_kwargs,
short_help='Disables all sorting while formatting output.',
help=f""" help=f"""
Disables all sorting while formatting output. It is a shortcut for: Disables all sorting while formatting output. It is a shortcut for:
@ -301,6 +338,7 @@ output_processing.add_argument(
output_processing.add_argument( output_processing.add_argument(
'--sorted', '--sorted',
**_sorted_kwargs, **_sorted_kwargs,
short_help='Re-enables all sorting options while formatting output.',
help=f""" help=f"""
Re-enables all sorting options while formatting output. It is a shortcut for: Re-enables all sorting options while formatting output. It is a shortcut for:
@ -312,6 +350,7 @@ output_processing.add_argument(
'--response-charset', '--response-charset',
metavar='ENCODING', metavar='ENCODING',
type=response_charset_type, type=response_charset_type,
short_help='Override the response encoding for terminal display purposes.',
help=""" help="""
Override the response encoding for terminal display purposes, e.g.: Override the response encoding for terminal display purposes, e.g.:
@ -324,6 +363,7 @@ output_processing.add_argument(
'--response-mime', '--response-mime',
metavar='MIME_TYPE', metavar='MIME_TYPE',
type=response_mime_type, type=response_mime_type,
short_help='Override the response mime type for coloring and formatting for the terminal.',
help=""" help="""
Override the response mime type for coloring and formatting for the terminal, e.g.: Override the response mime type for coloring and formatting for the terminal, e.g.:
@ -335,6 +375,7 @@ output_processing.add_argument(
output_processing.add_argument( output_processing.add_argument(
'--format-options', '--format-options',
action='append', action='append',
short_help='Controls output formatting.',
help=""" help="""
Controls output formatting. Only relevant when formatting is enabled Controls output formatting. Only relevant when formatting is enabled
through (explicit or implied) --pretty=all or --pretty=format. through (explicit or implied) --pretty=all or --pretty=format.
@ -361,13 +402,14 @@ output_processing.add_argument(
# Output options # Output options
####################################################################### #######################################################################
output_options = options.add_group('Output Options') output_options = options.add_group('Output options')
output_options.add_argument( output_options.add_argument(
'--print', '--print',
'-p', '-p',
dest='output_options', dest='output_options',
metavar='WHAT', metavar='WHAT',
short_help='Options to specify what the console output should contain.',
help=f""" help=f"""
String specifying what the output should contain: String specifying what the output should contain:
@ -390,6 +432,7 @@ output_options.add_argument(
dest='output_options', dest='output_options',
action='store_const', action='store_const',
const=OUT_RESP_HEAD, const=OUT_RESP_HEAD,
short_help='Print only the response headers.',
help=f""" help=f"""
Print only the response headers. Shortcut for --print={OUT_RESP_HEAD}. Print only the response headers. Shortcut for --print={OUT_RESP_HEAD}.
@ -401,6 +444,7 @@ output_options.add_argument(
dest='output_options', dest='output_options',
action='store_const', action='store_const',
const=OUT_RESP_META, const=OUT_RESP_META,
short_help='Print only the response metadata.',
help=f""" help=f"""
Print only the response metadata. Shortcut for --print={OUT_RESP_META}. Print only the response metadata. Shortcut for --print={OUT_RESP_META}.
@ -412,6 +456,7 @@ output_options.add_argument(
dest='output_options', dest='output_options',
action='store_const', action='store_const',
const=OUT_RESP_BODY, const=OUT_RESP_BODY,
short_help='Print only the response body.',
help=f""" help=f"""
Print only the response body. Shortcut for --print={OUT_RESP_BODY}. Print only the response body. Shortcut for --print={OUT_RESP_BODY}.
@ -424,20 +469,22 @@ output_options.add_argument(
dest='verbose', dest='verbose',
action='count', action='count',
default=0, default=0,
short_help='Make output more verbose.',
help=f""" help=f"""
Verbose output. For the level one (with single `-v`/`--verbose`), print Verbose output. For the level one (with single `-v`/`--verbose`), print
the whole request as well as the response. Also print any intermediary the whole request as well as the response. Also print any intermediary
requests/responses (such as redirects). For the second level and higher, requests/responses (such as redirects). For the second level and higher,
print these as well as the response metadata. print these as well as the response metadata.
Level one is a shortcut for: --all --print={''.join(BASE_OUTPUT_OPTIONS)} Level one is a shortcut for: --all --print={''.join(sorted(BASE_OUTPUT_OPTIONS))}
Level two is a shortcut for: --all --print={''.join(OUTPUT_OPTIONS)} Level two is a shortcut for: --all --print={''.join(sorted(OUTPUT_OPTIONS))}
""", """,
) )
output_options.add_argument( output_options.add_argument(
'--all', '--all',
default=False, default=False,
action='store_true', action='store_true',
short_help='Show any intermediary requests/responses.',
help=""" help="""
By default, only the final request/response is shown. Use this flag to show By default, only the final request/response is shown. Use this flag to show
any intermediary requests/responses as well. Intermediary requests include any intermediary requests/responses as well. Intermediary requests include
@ -451,19 +498,14 @@ output_options.add_argument(
'-P', '-P',
dest='output_options_history', dest='output_options_history',
metavar='WHAT', metavar='WHAT',
help=""" help=Qualifiers.SUPPRESS,
The same as --print, -p but applies only to intermediary requests/responses
(such as redirects) when their inclusion is enabled with --all. If this
options is not specified, then they are formatted the same way as the final
response.
""",
) )
output_options.add_argument( output_options.add_argument(
'--stream', '--stream',
'-S', '-S',
action='store_true', action='store_true',
default=False, default=False,
short_help='Always stream the response body by line, i.e., behave like `tail -f`.',
help=""" help="""
Always stream the response body by line, i.e., behave like `tail -f'. Always stream the response body by line, i.e., behave like `tail -f'.
@ -484,6 +526,7 @@ output_options.add_argument(
type=FileType('a+b'), type=FileType('a+b'),
dest='output_file', dest='output_file',
metavar='FILE', metavar='FILE',
short_help='Save output to FILE instead of stdout.',
help=""" help="""
Save output to FILE instead of stdout. If --download is also set, then only Save output to FILE instead of stdout. If --download is also set, then only
the response body is saved to FILE. Other parts of the HTTP exchange are the response body is saved to FILE. Other parts of the HTTP exchange are
@ -497,6 +540,7 @@ output_options.add_argument(
'-d', '-d',
action='store_true', action='store_true',
default=False, default=False,
short_help='Download the body to a file instead of printing it to stdout.',
help=""" help="""
Do not print the response body to stdout. Rather, download it and store it Do not print the response body to stdout. Rather, download it and store it
in a file. The filename is guessed unless specified with --output in a file. The filename is guessed unless specified with --output
@ -510,6 +554,7 @@ output_options.add_argument(
dest='download_resume', dest='download_resume',
action='store_true', action='store_true',
default=False, default=False,
short_help='Resume an interrupted download (--output needs to be specified).',
help=""" help="""
Resume an interrupted download. Note that the --output option needs to be Resume an interrupted download. Note that the --output option needs to be
specified as well. specified as well.
@ -521,6 +566,7 @@ output_options.add_argument(
'-q', '-q',
action='count', action='count',
default=0, default=0,
short_help='Do not print to stdout or stderr, except for errors and warnings when provided once.',
help=""" help="""
Do not print to stdout or stderr, except for errors and warnings when provided once. Do not print to stdout or stderr, except for errors and warnings when provided once.
Provide twice to suppress warnings as well. Provide twice to suppress warnings as well.
@ -544,21 +590,26 @@ sessions.add_argument(
'--session', '--session',
metavar='SESSION_NAME_OR_PATH', metavar='SESSION_NAME_OR_PATH',
type=session_name_validator, type=session_name_validator,
help=f""" short_help='Create, or reuse and update a session.',
help="""
Create, or reuse and update a session. Within a session, custom headers, 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 auth credential, as well as any cookies sent by the server persist between
requests. requests.
Session files are stored in: Session files are stored in:
{DEFAULT_SESSIONS_DIR}/<HOST>/<SESSION_NAME>.json. [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
""", """,
) )
sessions.add_argument( sessions.add_argument(
'--session-read-only', '--session-read-only',
metavar='SESSION_NAME_OR_PATH', metavar='SESSION_NAME_OR_PATH',
type=session_name_validator, type=session_name_validator,
short_help='Create or read a session without updating it',
help=""" help="""
Create or read a session without updating it form the request/response Create or read a session without updating it form the request/response
exchange. exchange.
@ -566,21 +617,31 @@ sessions.add_argument(
""", """,
) )
####################################################################### #######################################################################
# Authentication # Authentication
####################################################################### #######################################################################
def format_auth_help(auth_plugins_mapping): def format_auth_help(auth_plugins_mapping, *, isolation_mode: bool = False):
auth_plugins = list(auth_plugins_mapping.values()) text = """
return """
The authentication mechanism to be used. Defaults to "{default}". The authentication mechanism to be used. Defaults to "{default}".
{types} {auth_types}
"""
""".format( auth_plugins = list(auth_plugins_mapping.values())
default=auth_plugins[0].auth_type, if isolation_mode:
types='\n '.join( auth_plugins = [
auth_plugin
for auth_plugin in auth_plugins
if issubclass(auth_plugin, BuiltinAuthPlugin)
]
text += '\n'
text += 'To see all available auth types on your system, including ones installed via plugins, run:\n\n'
text += ' $ http --auth-type'
auth_types = '\n\n '.join(
'"{type}": {name}{package}{description}'.format( '"{type}": {name}{package}{description}'.format(
type=plugin.auth_type, type=plugin.auth_type,
name=plugin.name, name=plugin.name,
@ -597,7 +658,11 @@ def format_auth_help(auth_plugins_mapping):
), ),
) )
for plugin in auth_plugins for plugin in auth_plugins
), )
return text.format(
default=auth_plugins[0].auth_type,
auth_types=auth_types,
) )
@ -608,6 +673,7 @@ authentication.add_argument(
'-a', '-a',
default=None, default=None,
metavar='USER[:PASS] | TOKEN', metavar='USER[:PASS] | TOKEN',
short_help='Credentials for the selected (-A) authentication method.',
help=""" help="""
For username/password based authentication mechanisms (e.g For username/password based authentication mechanisms (e.g
basic auth or digest auth) if only the username is provided basic auth or digest auth) if only the username is provided
@ -623,16 +689,14 @@ authentication.add_argument(
getter=plugin_manager.get_auth_plugin_mapping, getter=plugin_manager.get_auth_plugin_mapping,
sort=True, sort=True,
cache=False, cache=False,
short_help='The authentication mechanism to be used.',
help_formatter=format_auth_help, help_formatter=format_auth_help,
) )
authentication.add_argument( authentication.add_argument(
'--ignore-netrc', '--ignore-netrc',
default=False, default=False,
action='store_true', action='store_true',
help=""" short_help='Ignore credentials from .netrc.'
Ignore credentials from .netrc.
""",
) )
####################################################################### #######################################################################
@ -645,9 +709,7 @@ network.add_argument(
'--offline', '--offline',
default=False, default=False,
action='store_true', action='store_true',
help=""" short_help='Build the request and print it but dont actually send it.'
Build the request and print it but dont actually send it.
""",
) )
network.add_argument( network.add_argument(
'--proxy', '--proxy',
@ -655,6 +717,7 @@ network.add_argument(
action='append', action='append',
metavar='PROTOCOL:PROXY_URL', metavar='PROTOCOL:PROXY_URL',
type=KeyValueArgType(SEPARATOR_PROXY), type=KeyValueArgType(SEPARATOR_PROXY),
short_help='String mapping of protocol to the URL of the proxy.',
help=""" help="""
String mapping protocol to the URL of the proxy String mapping protocol to the URL of the proxy
(e.g. http:http://foo.bar:3128). You can specify multiple proxies with (e.g. http:http://foo.bar:3128). You can specify multiple proxies with
@ -668,16 +731,14 @@ network.add_argument(
'-F', '-F',
default=False, default=False,
action='store_true', action='store_true',
help=""" short_help='Follow 30x Location redirects.'
Follow 30x Location redirects.
""",
) )
network.add_argument( network.add_argument(
'--max-redirects', '--max-redirects',
type=int, type=int,
default=30, default=30,
short_help='The maximum number of redirects that should be followed (with --follow).',
help=""" help="""
By default, requests have a limit of 30 redirects (works with --follow). By default, requests have a limit of 30 redirects (works with --follow).
@ -687,11 +748,10 @@ network.add_argument(
'--max-headers', '--max-headers',
type=int, type=int,
default=0, default=0,
help=""" short_help=(
The maximum number of response headers to be read before giving up 'The maximum number of response headers to be read before '
(default 0, i.e., no limit). 'giving up (default 0, i.e., no limit).'
)
""",
) )
network.add_argument( network.add_argument(
@ -699,6 +759,7 @@ network.add_argument(
type=float, type=float,
default=0, default=0,
metavar='SECONDS', metavar='SECONDS',
short_help='The connection timeout of the request in seconds.',
help=""" help="""
The connection timeout of the request in seconds. The connection timeout of the request in seconds.
The default value is 0, i.e., there is no timeout limit. The default value is 0, i.e., there is no timeout limit.
@ -713,6 +774,7 @@ network.add_argument(
'--check-status', '--check-status',
default=False, default=False,
action='store_true', action='store_true',
short_help='Exit with an error status code if the server replies with an error.',
help=""" help="""
By default, HTTPie exits with 0 when no network or other fatal errors 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 occur. This flag instructs HTTPie to also check the HTTP status code and
@ -729,20 +791,16 @@ network.add_argument(
'--path-as-is', '--path-as-is',
default=False, default=False,
action='store_true', action='store_true',
help=""" short_help='Bypass dot segment (/../ or /./) URL squashing.'
Bypass dot segment (/../ or /./) URL squashing.
""",
) )
network.add_argument( network.add_argument(
'--chunked', '--chunked',
default=False, default=False,
action='store_true', action='store_true',
help=""" short_help=(
Enable streaming via chunked transfer encoding. 'Enable streaming via chunked transfer encoding. '
The Transfer-Encoding header is set to chunked. 'The Transfer-Encoding header is set to chunked.'
)
""",
) )
####################################################################### #######################################################################
@ -754,6 +812,7 @@ ssl = options.add_group('SSL')
ssl.add_argument( ssl.add_argument(
'--verify', '--verify',
default='yes', default='yes',
short_help='If "no", skip SSL verification. If a file path, use it as a CA bundle.',
help=""" help="""
Set to "no" (or "false") to skip checking the host's SSL certificate. Set to "no" (or "false") to skip checking the host's SSL certificate.
Defaults to "yes" ("true"). You can also pass the path to a CA_BUNDLE file Defaults to "yes" ("true"). You can also pass the path to a CA_BUNDLE file
@ -765,6 +824,7 @@ ssl.add_argument(
'--ssl', '--ssl',
dest='ssl_version', dest='ssl_version',
choices=sorted(AVAILABLE_SSL_VERSION_ARG_MAPPING.keys()), choices=sorted(AVAILABLE_SSL_VERSION_ARG_MAPPING.keys()),
short_help='The desired protocol version to used.',
help=""" help="""
The desired protocol version to use. This will default to The desired protocol version to use. This will default to
SSL v2.3 which will negotiate the highest protocol that both SSL v2.3 which will negotiate the highest protocol that both
@ -774,21 +834,36 @@ ssl.add_argument(
""", """,
) )
CIPHERS_CURRENT_DEFAULTS = (
"""
See `http --help` for the default ciphers list on you system.
"""
if IS_MAN_PAGE else
f"""
By default, the following ciphers are used on your system:
{DEFAULT_SSL_CIPHERS_STRING}
"""
)
ssl.add_argument( ssl.add_argument(
'--ciphers', '--ciphers',
short_help='A string in the OpenSSL cipher list format.',
help=f""" help=f"""
A string in the OpenSSL cipher list format. By default, the following A string in the OpenSSL cipher list format.
is used:
{DEFAULT_SSL_CIPHERS} {CIPHERS_CURRENT_DEFAULTS}
""", """
) )
ssl.add_argument( ssl.add_argument(
'--cert', '--cert',
default=None, default=None,
type=readable_file_arg, type=readable_file_arg,
short_help='Specifies a local cert to use as the client-side SSL certificate.',
help=""" help="""
You can specify a local cert to use as client side SSL certificate. You can specify a local cert to use as client side SSL certificate.
This file may either contain both private key and certificate or you may This file may either contain both private key and certificate or you may
@ -800,6 +875,7 @@ ssl.add_argument(
'--cert-key', '--cert-key',
default=None, default=None,
type=readable_file_arg, type=readable_file_arg,
short_help='The private key to use with SSL. Only needed if --cert is given.',
help=""" help="""
The private key to use with SSL. Only needed if --cert is given and the The private key to use with SSL. Only needed if --cert is given and the
certificate file does not contain the private key. certificate file does not contain the private key.
@ -811,11 +887,12 @@ ssl.add_argument(
'--cert-key-pass', '--cert-key-pass',
default=None, default=None,
type=SSLCredentials, type=SSLCredentials,
help=''' short_help='The passphrase to be used to with the given private key.',
help="""
The passphrase to be used to with the given private key. Only needed if --cert-key The passphrase to be used to with the given private key. Only needed if --cert-key
is given and the key file requires a passphrase. is given and the key file requires a passphrase.
If not provided, youll be prompted interactively. If not provided, youll be prompted interactively.
''' """
) )
####################################################################### #######################################################################
@ -828,50 +905,42 @@ troubleshooting.add_argument(
'-I', '-I',
action='store_true', action='store_true',
default=False, default=False,
help=""" short_help='Do not attempt to read stdin'
Do not attempt to read stdin.
""",
) )
troubleshooting.add_argument( troubleshooting.add_argument(
'--help', '--help',
action='help', action='help',
default=Qualifiers.SUPPRESS, default=Qualifiers.SUPPRESS,
help=""" short_help='Show this help message and exit.',
Show this help message and exit. )
troubleshooting.add_argument(
""", '--manual',
action='manual',
default=Qualifiers.SUPPRESS,
short_help='Show the full manual.',
) )
troubleshooting.add_argument( troubleshooting.add_argument(
'--version', '--version',
action='version', action='version',
version=__version__, version=__version__,
help=""" short_help='Show version and exit.',
Show version and exit.
""",
) )
troubleshooting.add_argument( troubleshooting.add_argument(
'--traceback', '--traceback',
action='store_true', action='store_true',
default=False, default=False,
help=""" short_help='Prints the exception traceback should one occur.',
Prints the exception traceback should one occur.
""",
) )
troubleshooting.add_argument( troubleshooting.add_argument(
'--default-scheme', '--default-scheme',
default='http', default='http',
help=""" short_help='The default scheme to use if not specified in the URL.'
The default scheme to use if not specified in the URL.
""",
) )
troubleshooting.add_argument( troubleshooting.add_argument(
'--debug', '--debug',
action='store_true', action='store_true',
default=False, default=False,
short_help='Print useful diagnostic information for bug reports.',
help=""" help="""
Prints the exception traceback should one occur, as well as other Prints the exception traceback should one occur, as well as other
information useful for debugging HTTPie itself and for reporting bugs. information useful for debugging HTTPie itself and for reporting bugs.

View File

@ -35,6 +35,16 @@ class HTTPHeadersDict(CIMultiDict, BaseMultiDict):
super().add(key, value) super().add(key, value)
def remove_item(self, key, value):
"""
Remove a (key, value) pair from the dict.
"""
existing_values = self.popall(key)
existing_values.remove(value)
for value in existing_values:
self.add(key, value)
class RequestJSONDataDict(OrderedDict): class RequestJSONDataDict(OrderedDict):
pass pass
@ -82,7 +92,3 @@ class MultipartRequestDataDict(MultiValueOrderedDict):
class RequestFilesDict(RequestDataDict): class RequestFilesDict(RequestDataDict):
pass pass
class NestedJSONArray(list):
"""Denotes a top-level JSON array."""

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,15 +3,16 @@ import textwrap
import typing import typing
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum, auto from enum import Enum, auto
from typing import Any, Optional, Dict, List, Type, TypeVar from typing import Any, Optional, Dict, List, Tuple, Type, TypeVar
from httpie.cli.argparser import HTTPieArgumentParser from httpie.cli.argparser import HTTPieArgumentParser
from httpie.cli.utils import LazyChoices from httpie.cli.utils import Manual, LazyChoices
class Qualifiers(Enum): class Qualifiers(Enum):
OPTIONAL = auto() OPTIONAL = auto()
ZERO_OR_MORE = auto() ZERO_OR_MORE = auto()
ONE_OR_MORE = auto()
SUPPRESS = auto() SUPPRESS = auto()
@ -24,6 +25,16 @@ def map_qualifiers(
} }
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' PARSER_SPEC_VERSION = '0.0.1a0'
@ -33,6 +44,8 @@ class ParserSpec:
description: Optional[str] = None description: Optional[str] = None
epilog: Optional[str] = None epilog: Optional[str] = None
groups: List['Group'] = field(default_factory=list) groups: List['Group'] = field(default_factory=list)
man_page_hint: Optional[str] = None
source_file: Optional[str] = None
def finalize(self) -> 'ParserSpec': def finalize(self) -> 'ParserSpec':
if self.description: if self.description:
@ -69,6 +82,7 @@ class Group:
def add_argument(self, *args, **kwargs): def add_argument(self, *args, **kwargs):
argument = Argument(list(args), kwargs.copy()) argument = Argument(list(args), kwargs.copy())
argument.post_init()
self.arguments.append(argument) self.arguments.append(argument)
return argument return argument
@ -85,14 +99,32 @@ class Argument(typing.NamedTuple):
aliases: List[str] aliases: List[str]
configuration: Dict[str, Any] configuration: Dict[str, Any]
def serialize(self) -> 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() configuration = self.configuration.copy()
# Unpack the dynamically computed choices, since we # Unpack the dynamically computed choices, since we
# will need to store the actual values somewhere. # will need to store the actual values somewhere.
action = configuration.pop('action', None) action = configuration.pop('action', None)
short_help = configuration.pop('short_help', None)
nested_options = configuration.pop('nested_options', None)
if action == 'lazy_choices': if action == 'lazy_choices':
choices = LazyChoices(self.aliases, **{'dest': None, **configuration}) choices = LazyChoices(
self.aliases,
**{'dest': None, **configuration},
isolation_mode=isolation_mode
)
configuration['choices'] = list(choices.load()) configuration['choices'] = list(choices.load())
configuration['help'] = choices.help configuration['help'] = choices.help
@ -106,9 +138,13 @@ class Argument(typing.NamedTuple):
qualifiers = JSON_QUALIFIER_TO_OPTIONS[configuration.get('nargs', Qualifiers.SUPPRESS)] qualifiers = JSON_QUALIFIER_TO_OPTIONS[configuration.get('nargs', Qualifiers.SUPPRESS)]
result.update(qualifiers) result.update(qualifiers)
help_msg = configuration.get('help') description = configuration.get('help')
if help_msg and help_msg is not Qualifiers.SUPPRESS: if description and description is not Qualifiers.SUPPRESS:
result['description'] = help_msg.strip() result['short_description'] = short_help
result['description'] = description
if nested_options:
result['nested_options'] = nested_options
python_type = configuration.get('type') python_type = configuration.get('type')
if python_type is not None: if python_type is not None:
@ -123,10 +159,19 @@ class Argument(typing.NamedTuple):
key: value key: value
for key, value in configuration.items() for key, value in configuration.items()
if key in JSON_DIRECT_MIRROR_OPTIONS if key in JSON_DIRECT_MIRROR_OPTIONS
if value is not Qualifiers.SUPPRESS
}) })
return result 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): def __getattr__(self, attribute_name):
if attribute_name in self.configuration: if attribute_name in self.configuration:
return self.configuration[attribute_name] return self.configuration[attribute_name]
@ -140,7 +185,9 @@ ARGPARSE_QUALIFIER_MAP = {
Qualifiers.OPTIONAL: argparse.OPTIONAL, Qualifiers.OPTIONAL: argparse.OPTIONAL,
Qualifiers.SUPPRESS: argparse.SUPPRESS, Qualifiers.SUPPRESS: argparse.SUPPRESS,
Qualifiers.ZERO_OR_MORE: argparse.ZERO_OR_MORE, 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( def to_argparse(
@ -152,7 +199,9 @@ def to_argparse(
description=abstract_options.description, description=abstract_options.description,
epilog=abstract_options.epilog, epilog=abstract_options.epilog,
) )
concrete_parser.spec = abstract_options
concrete_parser.register('action', 'lazy_choices', LazyChoices) concrete_parser.register('action', 'lazy_choices', LazyChoices)
concrete_parser.register('action', 'manual', Manual)
for abstract_group in abstract_options.groups: for abstract_group in abstract_options.groups:
concrete_group = concrete_parser.add_argument_group( concrete_group = concrete_parser.add_argument_group(
@ -164,9 +213,9 @@ def to_argparse(
for abstract_argument in abstract_group.arguments: for abstract_argument in abstract_group.arguments:
concrete_group.add_argument( concrete_group.add_argument(
*abstract_argument.aliases, *abstract_argument.aliases,
**map_qualifiers( **drop_keys(map_qualifiers(
abstract_argument.configuration, ARGPARSE_QUALIFIER_MAP abstract_argument.configuration, ARGPARSE_QUALIFIER_MAP
) ), ARGPARSE_IGNORE_KEYS)
) )
return concrete_parser return concrete_parser
@ -181,9 +230,20 @@ JSON_DIRECT_MIRROR_OPTIONS = (
JSON_QUALIFIER_TO_OPTIONS = { JSON_QUALIFIER_TO_OPTIONS = {
Qualifiers.OPTIONAL: {'is_optional': True}, Qualifiers.OPTIONAL: {'is_optional': True},
Qualifiers.ZERO_OR_MORE: {'is_optional': True, 'is_variadic': True}, Qualifiers.ZERO_OR_MORE: {'is_optional': True, 'is_variadic': True},
Qualifiers.ONE_OR_MORE: {'is_optional': False, 'is_variadic': True},
Qualifiers.SUPPRESS: {} Qualifiers.SUPPRESS: {}
} }
def to_data(abstract_options: ParserSpec) -> Dict[str, Any]: def to_data(abstract_options: ParserSpec) -> Dict[str, Any]:
return {'version': PARSER_SPEC_VERSION, 'spec': abstract_options.serialize()} return {'version': PARSER_SPEC_VERSION, 'spec': abstract_options.serialize()}
def parser_to_parser_spec(parser: argparse.ArgumentParser, **kwargs) -> ParserSpec:
"""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

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

View File

@ -4,20 +4,43 @@ from typing import Any, Callable, Generic, Iterator, Iterable, Optional, TypeVar
T = TypeVar('T') T = TypeVar('T')
class Manual(argparse.Action):
def __init__(
self,
option_strings,
dest=argparse.SUPPRESS,
default=argparse.SUPPRESS,
help=None
):
super().__init__(
option_strings=option_strings,
dest=dest,
default=default,
nargs=0,
help=help
)
def __call__(self, parser, namespace, values, option_string=None):
parser.print_manual()
parser.exit()
class LazyChoices(argparse.Action, Generic[T]): class LazyChoices(argparse.Action, Generic[T]):
def __init__( def __init__(
self, self,
*args, *args,
getter: Callable[[], Iterable[T]], getter: Callable[[], Iterable[T]],
help_formatter: Optional[Callable[[T], str]] = None, help_formatter: Optional[Callable[[T, bool], str]] = None,
sort: bool = False, sort: bool = False,
cache: bool = True, cache: bool = True,
isolation_mode: bool = False,
**kwargs **kwargs
) -> None: ) -> None:
self.getter = getter self.getter = getter
self.help_formatter = help_formatter self.help_formatter = help_formatter
self.sort = sort self.sort = sort
self.cache = cache self.cache = cache
self.isolation_mode = isolation_mode
self._help: Optional[str] = None self._help: Optional[str] = None
self._obj: Optional[Iterable[T]] = None self._obj: Optional[Iterable[T]] = None
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -33,7 +56,10 @@ class LazyChoices(argparse.Action, Generic[T]):
@property @property
def help(self) -> str: def help(self) -> str:
if self._help is None and self.help_formatter is not None: if self._help is None and self.help_formatter is not None:
self._help = self.help_formatter(self.load()) self._help = self.help_formatter(
self.load(),
isolation_mode=self.isolation_mode
)
return self._help return self._help
@help.setter @help.setter

View File

@ -10,11 +10,14 @@ from urllib.parse import urlparse, urlunparse
import requests import requests
# noinspection PyPackageRequirements # noinspection PyPackageRequirements
import urllib3 import urllib3
from urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS
from . import __version__ from . import __version__
from .adapters import HTTPieHTTPAdapter from .adapters import HTTPieHTTPAdapter
from .cli.constants import HTTP_OPTIONS
from .cli.dicts import HTTPHeadersDict
from .cli.nested_json import unwrap_top_level_list_if_needed
from .context import Environment from .context import Environment
from .cli.constants import EMPTY_STRING
from .cli.dicts import HTTPHeadersDict, NestedJSONArray
from .encoding import UTF8 from .encoding import UTF8
from .models import RequestsMessage from .models import RequestsMessage
from .plugins.registry import plugin_manager from .plugins.registry import plugin_manager
@ -34,6 +37,8 @@ JSON_CONTENT_TYPE = 'application/json'
JSON_ACCEPT = f'{JSON_CONTENT_TYPE}, */*;q=0.5' JSON_ACCEPT = f'{JSON_CONTENT_TYPE}, */*;q=0.5'
DEFAULT_UA = f'HTTPie/{__version__}' DEFAULT_UA = f'HTTPie/{__version__}'
IGNORE_CONTENT_LENGTH_METHODS = frozenset([HTTP_OPTIONS])
def collect_messages( def collect_messages(
env: Environment, env: Environment,
@ -85,7 +90,7 @@ def collect_messages(
request = requests.Request(**request_kwargs) request = requests.Request(**request_kwargs)
prepared_request = requests_session.prepare_request(request) prepared_request = requests_session.prepare_request(request)
apply_missing_repeated_headers(prepared_request, request.headers) transform_headers(request, prepared_request)
if args.path_as_is: if args.path_as_is:
prepared_request.url = ensure_path_as_is( prepared_request.url = ensure_path_as_is(
orig_url=args.url, orig_url=args.url,
@ -138,7 +143,7 @@ def collect_messages(
# noinspection PyProtectedMember # noinspection PyProtectedMember
@contextmanager @contextmanager
def max_headers(limit): def max_headers(limit):
# <https://github.com/httpie/httpie/issues/802> # <https://github.com/httpie/cli/issues/802>
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
orig = http.client._MAXHEADERS orig = http.client._MAXHEADERS
http.client._MAXHEADERS = limit or float('Inf') http.client._MAXHEADERS = limit or float('Inf')
@ -194,15 +199,40 @@ def finalize_headers(headers: HTTPHeadersDict) -> HTTPHeadersDict:
# Also, requests raises `InvalidHeader` for leading spaces. # Also, requests raises `InvalidHeader` for leading spaces.
value = value.strip() value = value.strip()
if isinstance(value, str): if isinstance(value, str):
# See <https://github.com/httpie/httpie/issues/212> # See <https://github.com/httpie/cli/issues/212>
value = value.encode() value = value.encode()
elif name.lower() in SKIPPABLE_HEADERS:
# Some headers get overwritten by urllib3 when set to `None`
# and should be replaced with the `SKIP_HEADER` constant.
value = SKIP_HEADER
final_headers.add(name, value) final_headers.add(name, value)
return final_headers return final_headers
def transform_headers(
request: requests.Request,
prepared_request: requests.PreparedRequest
) -> None:
"""Apply various transformations on top of the `prepared_requests`'s
headers to change the request prepreation behavior."""
# Remove 'Content-Length' when it is misplaced by requests.
if (
prepared_request.method in IGNORE_CONTENT_LENGTH_METHODS
and prepared_request.headers.get('Content-Length') == '0'
and request.headers.get('Content-Length') != '0'
):
prepared_request.headers.pop('Content-Length')
apply_missing_repeated_headers(
request.headers,
prepared_request
)
def apply_missing_repeated_headers( def apply_missing_repeated_headers(
prepared_request: requests.PreparedRequest, original_headers: HTTPHeadersDict,
original_headers: HTTPHeadersDict prepared_request: requests.PreparedRequest
) -> None: ) -> None:
"""Update the given `prepared_request`'s headers with the original """Update the given `prepared_request`'s headers with the original
ones. This allows the requests to be prepared as usual, and then later ones. This allows the requests to be prepared as usual, and then later
@ -282,21 +312,13 @@ def make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict:
def json_dict_to_request_body(data: Dict[str, Any]) -> str: def json_dict_to_request_body(data: Dict[str, Any]) -> str:
# Propagate the top-level list if there is only one data = unwrap_top_level_list_if_needed(data)
# item in the object, with an en empty key.
if len(data) == 1:
[(key, value)] = data.items()
if isinstance(value, NestedJSONArray):
assert key == EMPTY_STRING
data = value
if data: if data:
data = json.dumps(data) data = json.dumps(data)
else: else:
# We need to set data to an empty string to prevent requests # We need to set data to an empty string to prevent requests
# from assigning an empty list to `response.request.data`. # from assigning an empty list to `response.request.data`.
data = '' data = ''
return data return data
@ -359,7 +381,7 @@ def ensure_path_as_is(orig_url: str, prepped_url: str) -> str:
untouched because other (welcome) processing on the URL might have untouched because other (welcome) processing on the URL might have
taken place. taken place.
<https://github.com/httpie/httpie/issues/895> <https://github.com/httpie/cli/issues/895>
<https://ec.haxx.se/http/http-basics#path-as-is> <https://ec.haxx.se/http/http-basics#path-as-is>

View File

@ -1,9 +1,21 @@
import sys import sys
from ssl import SSLContext
from typing import Any, Optional, Iterable from typing import Any, Optional, Iterable
from httpie.cookies import HTTPieCookiePolicy
from http import cookiejar # noqa
# Request does not carry the original policy attached to the
# cookie jar, so until it is resolved we change the global cookie
# policy. <https://github.com/psf/requests/issues/5449>
cookiejar.DefaultCookiePolicy = HTTPieCookiePolicy
is_windows = 'win32' in str(sys.platform).lower() is_windows = 'win32' in str(sys.platform).lower()
is_frozen = getattr(sys, 'frozen', False)
MIN_SUPPORTED_PY_VERSION = (3, 7)
MAX_SUPPORTED_PY_VERSION = (3, 11)
try: try:
from functools import cached_property from functools import cached_property
@ -54,7 +66,6 @@ except ImportError:
res = instance.__dict__[self.name] = self.func(instance) res = instance.__dict__[self.name] = self.func(instance)
return res return res
# importlib_metadata was a provisional module, so the APIs changed quite a few times # importlib_metadata was a provisional module, so the APIs changed quite a few times
# between 3.8-3.10. It was also not included in the standard library until 3.8, so # between 3.8-3.10. It was also not included in the standard library until 3.8, so
# we install the backport for <3.8. # we install the backport for <3.8.
@ -88,3 +99,15 @@ def get_dist_name(entry_point: importlib_metadata.EntryPoint) -> Optional[str]:
return None return None
else: else:
return metadata.get('name') return metadata.get('name')
def ensure_default_certs_loaded(ssl_context: SSLContext) -> None:
"""
Workaround for a bug in Requests 2.32.3
See <https://github.com/httpie/cli/issues/1583>
"""
if hasattr(ssl_context, 'load_default_certs'):
if not ssl_context.get_ca_certs():
ssl_context.load_default_certs()

View File

@ -149,6 +149,24 @@ class Config(BaseConfigDict):
def default_options(self) -> list: def default_options(self) -> list:
return self['default_options'] return self['default_options']
def _configured_path(self, config_option: str, default: str) -> None:
return Path(
self.get(config_option, self.directory / default)
).expanduser().resolve()
@property @property
def plugins_dir(self) -> Path: def plugins_dir(self) -> Path:
return Path(self.get('plugins_dir', self.directory / 'plugins')).resolve() return self._configured_path('plugins_dir', 'plugins')
@property
def version_info_file(self) -> Path:
return self._configured_path('version_info_file', 'version_info.json')
@property
def developer_mode(self) -> bool:
"""This is a special setting for the development environment. It is
different from the --debug mode in the terms that it might change
the behavior for certain parameters (e.g updater system) that
we usually ignore."""
return self.get('developer_mode')

View File

@ -1,9 +1,10 @@
import argparse
import sys import sys
import os import os
import warnings import warnings
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from typing import Iterator, IO, Optional from typing import Iterator, IO, Optional, TYPE_CHECKING
from enum import Enum from enum import Enum
@ -12,21 +13,33 @@ try:
except ImportError: except ImportError:
curses = None # Compiled w/o curses curses = None # Compiled w/o curses
from .compat import is_windows from .compat import is_windows, cached_property
from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
from .encoding import UTF8 from .encoding import UTF8
from .utils import repr_dict from .utils import repr_dict
from .output.ui.palette import GenericColor
if TYPE_CHECKING:
from rich.console import Console
class Levels(str, Enum): class LogLevel(str, Enum):
INFO = 'info'
WARNING = 'warning' WARNING = 'warning'
ERROR = 'error' ERROR = 'error'
DISPLAY_THRESHOLDS = { LOG_LEVEL_COLORS = {
Levels.WARNING: 2, LogLevel.INFO: GenericColor.PINK,
Levels.ERROR: float('inf'), # Never hide errors. 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.
} }
@ -40,6 +53,7 @@ class Environment:
is used by the test suite to simulate various scenarios. is used by the test suite to simulate various scenarios.
""" """
args = argparse.Namespace()
is_windows: bool = is_windows is_windows: bool = is_windows
config_dir: Path = DEFAULT_CONFIG_DIR config_dir: Path = DEFAULT_CONFIG_DIR
stdin: Optional[IO] = sys.stdin # `None` when closed fd (#791) stdin: Optional[IO] = sys.stdin # `None` when closed fd (#791)
@ -52,6 +66,10 @@ class Environment:
stderr_isatty: bool = stderr.isatty() stderr_isatty: bool = stderr.isatty()
colors = 256 colors = 256
program_name: str = 'http' program_name: str = 'http'
# Whether to show progress bars / status spinners etc.
show_displays: bool = True
if not is_windows: if not is_windows:
if curses: if curses:
try: try:
@ -127,7 +145,7 @@ class Environment:
try: try:
config.load() config.load()
except ConfigFileError as e: except ConfigFileError as e:
self.log_error(e, level='warning') self.log_error(e, level=LogLevel.WARNING)
return config return config
@property @property
@ -149,14 +167,51 @@ class Environment:
self.stdout = original_stdout self.stdout = original_stdout
self.stderr = original_stderr self.stderr = original_stderr
def log_error(self, msg: str, level: Levels = Levels.ERROR) -> None: def log_error(self, msg: str, level: LogLevel = LogLevel.ERROR) -> None:
if self.stdout_isatty and self.quiet >= DISPLAY_THRESHOLDS[level]: if self.stdout_isatty and self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[level]:
stderr = self.stderr # Not directly /dev/null, since stderr might be mocked stderr = self.stderr # Not directly /dev/null, since stderr might be mocked
else: else:
stderr = self._orig_stderr stderr = self._orig_stderr
rich_console = self._make_rich_console(file=stderr, force_terminal=stderr.isatty())
stderr.write(f'\n{self.program_name}: {level}: {msg}\n\n') rich_console.print(
f'\n{self.program_name}: {level.value}: {msg}\n\n',
style=LOG_LEVEL_COLORS[level],
markup=False,
highlight=False,
soft_wrap=True
)
def apply_warnings_filter(self) -> None: def apply_warnings_filter(self) -> None:
if self.quiet >= DISPLAY_THRESHOLDS[Levels.WARNING]: if self.quiet >= LOG_LEVEL_DISPLAY_THRESHOLDS[LogLevel.WARNING]:
warnings.simplefilter("ignore") 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

@ -11,9 +11,9 @@ from requests import __version__ as requests_version
from . import __version__ as httpie_version from . import __version__ as httpie_version
from .cli.constants import OUT_REQ_BODY from .cli.constants import OUT_REQ_BODY
from .cli.nested_json import HTTPieSyntaxError from .cli.nested_json import NestedJSONSyntaxError
from .client import collect_messages from .client import collect_messages
from .context import Environment, Levels from .context import Environment, LogLevel
from .downloads import Downloader from .downloads import Downloader
from .models import ( from .models import (
RequestsMessageKind, RequestsMessageKind,
@ -24,6 +24,8 @@ from .output.writer import write_message, write_stream, write_raw_data, MESSAGE_
from .plugins.registry import plugin_manager from .plugins.registry import plugin_manager
from .status import ExitStatus, http_status_to_exit_status from .status import ExitStatus, http_status_to_exit_status
from .utils import unwrap_context from .utils import unwrap_context
from .internal.update_warnings import check_updates
from .internal.daemon_runner import is_daemon_mode, run_daemon_task
# noinspection PyDefaultArgument # noinspection PyDefaultArgument
@ -37,6 +39,10 @@ def raw_main(
program_name, *args = args program_name, *args = args
env.program_name = os.path.basename(program_name) env.program_name = os.path.basename(program_name)
args = decode_raw_args(args, env.stdin_encoding) args = decode_raw_args(args, env.stdin_encoding)
if is_daemon_mode(args):
return run_daemon_task(env, args)
plugin_manager.load_installed_plugins(env.config.plugins_dir) plugin_manager.load_installed_plugins(env.config.plugins_dir)
if use_default_options and env.config.default_options: if use_default_options and env.config.default_options:
@ -72,7 +78,7 @@ def raw_main(
args=args, args=args,
env=env, env=env,
) )
except HTTPieSyntaxError as exc: except NestedJSONSyntaxError as exc:
env.stderr.write(str(exc) + "\n") env.stderr.write(str(exc) + "\n")
if include_traceback: if include_traceback:
raise raise
@ -89,6 +95,7 @@ def raw_main(
raise raise
exit_status = ExitStatus.ERROR exit_status = ExitStatus.ERROR
else: else:
check_updates(env)
try: try:
exit_status = main_program( exit_status = main_program(
args=parsed_args, args=parsed_args,
@ -195,7 +202,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
try: try:
if args.download: if args.download:
args.follow = True # --download implies --follow. args.follow = True # --download implies --follow.
downloader = Downloader(output_file=args.output_file, progress_file=env.stderr, resume=args.download_resume) downloader = Downloader(env, output_file=args.output_file, resume=args.download_resume)
downloader.pre_request(args.headers) downloader.pre_request(args.headers)
messages = collect_messages(env, args=args, messages = collect_messages(env, args=args,
request_body_read_callback=request_body_read_callback) request_body_read_callback=request_body_read_callback)
@ -223,7 +230,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
if args.check_status or downloader: if args.check_status or downloader:
exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow) exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)
if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet == 1): if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet == 1):
env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level=Levels.WARNING) env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level=LogLevel.WARNING)
write_message( write_message(
requests_message=message, requests_message=message,
env=env, env=env,

View File

@ -5,10 +5,8 @@ Download mode implementation.
import mimetypes import mimetypes
import os import os
import re import re
import sys
import threading
from mailbox import Message from mailbox import Message
from time import sleep, monotonic from time import monotonic
from typing import IO, Optional, Tuple from typing import IO, Optional, Tuple
from urllib.parse import urlsplit from urllib.parse import urlsplit
@ -16,22 +14,11 @@ import requests
from .models import HTTPResponse, OutputOptions from .models import HTTPResponse, OutputOptions
from .output.streams import RawStream from .output.streams import RawStream
from .utils import humanize_bytes from .context import Environment
PARTIAL_CONTENT = 206 PARTIAL_CONTENT = 206
CLEAR_LINE = '\r\033[K'
PROGRESS = (
'{percentage: 6.2f} %'
' {downloaded: >10}'
' {speed: >10}/s'
' {eta: >8} ETA'
)
PROGRESS_NO_CONTENT_LENGTH = '{downloaded: >10} {speed: >10}/s'
SUMMARY = 'Done. {downloaded} in {time:0.5f}s ({speed}/s)\n'
SPINNER = '|/-\\'
class ContentRangeError(ValueError): class ContentRangeError(ValueError):
pass pass
@ -176,9 +163,9 @@ class Downloader:
def __init__( def __init__(
self, self,
env: Environment,
output_file: IO = None, output_file: IO = None,
resume: bool = False, resume: bool = False
progress_file: IO = sys.stderr
): ):
""" """
:param resume: Should the download resume if partial download :param resume: Should the download resume if partial download
@ -191,14 +178,10 @@ class Downloader:
""" """
self.finished = False self.finished = False
self.status = DownloadStatus() self.status = DownloadStatus(env=env)
self._output_file = output_file self._output_file = output_file
self._resume = resume self._resume = resume
self._resumed_from = 0 self._resumed_from = 0
self._progress_reporter = ProgressReporterThread(
status=self.status,
output=progress_file
)
def pre_request(self, request_headers: dict): def pre_request(self, request_headers: dict):
"""Called just before the HTTP request is sent. """Called just before the HTTP request is sent.
@ -234,7 +217,7 @@ class Downloader:
assert not self.status.time_started assert not self.status.time_started
# FIXME: some servers still might sent Content-Encoding: gzip # FIXME: some servers still might sent Content-Encoding: gzip
# <https://github.com/httpie/httpie/issues/423> # <https://github.com/httpie/cli/issues/423>
try: try:
total_size = int(final_response.headers['Content-Length']) total_size = int(final_response.headers['Content-Length'])
except (KeyError, ValueError, TypeError): except (KeyError, ValueError, TypeError):
@ -261,11 +244,6 @@ class Downloader:
except OSError: except OSError:
pass # stdout pass # stdout
self.status.started(
resumed_from=self._resumed_from,
total_size=total_size
)
output_options = OutputOptions.from_message(final_response, headers=False, body=True) output_options = OutputOptions.from_message(final_response, headers=False, body=True)
stream = RawStream( stream = RawStream(
msg=HTTPResponse(final_response), msg=HTTPResponse(final_response),
@ -273,11 +251,11 @@ class Downloader:
on_body_chunk_downloaded=self.chunk_downloaded, on_body_chunk_downloaded=self.chunk_downloaded,
) )
self._progress_reporter.output.write( self.status.started(
f'Downloading {humanize_bytes(total_size) + " " if total_size is not None else ""}' output_file=self._output_file,
f'to "{self._output_file.name}"\n' resumed_from=self._resumed_from,
total_size=total_size
) )
self._progress_reporter.start()
return stream, self._output_file return stream, self._output_file
@ -287,7 +265,7 @@ class Downloader:
self.status.finished() self.status.finished()
def failed(self): def failed(self):
self._progress_reporter.stop() self.status.terminate()
@property @property
def interrupted(self) -> bool: def interrupted(self) -> bool:
@ -329,127 +307,71 @@ class Downloader:
class DownloadStatus: class DownloadStatus:
"""Holds details about the download status.""" """Holds details about the download status."""
def __init__(self): def __init__(self, env):
self.env = env
self.downloaded = 0 self.downloaded = 0
self.total_size = None self.total_size = None
self.resumed_from = 0 self.resumed_from = 0
self.time_started = None self.time_started = None
self.time_finished = None self.time_finished = None
def started(self, resumed_from=0, total_size=None): def started(self, output_file, resumed_from=0, total_size=None):
assert self.time_started is None assert self.time_started is None
self.total_size = total_size self.total_size = total_size
self.downloaded = self.resumed_from = resumed_from self.downloaded = self.resumed_from = resumed_from
self.time_started = monotonic() self.time_started = monotonic()
self.start_display(output_file=output_file)
def start_display(self, output_file):
from httpie.output.ui.rich_progress import (
DummyDisplay,
StatusDisplay,
ProgressDisplay
)
message = f'Downloading to {output_file.name}'
if self.env.show_displays:
if self.total_size is None:
# Rich does not support progress bars without a total
# size given. Instead we use status objects.
self.display = StatusDisplay(self.env)
else:
self.display = ProgressDisplay(self.env)
else:
self.display = DummyDisplay(self.env)
self.display.start(
total=self.total_size,
at=self.downloaded,
description=message
)
def chunk_downloaded(self, size): def chunk_downloaded(self, size):
assert self.time_finished is None assert self.time_finished is None
self.downloaded += size self.downloaded += size
self.display.update(size)
@property @property
def has_finished(self): def has_finished(self):
return self.time_finished is not None return self.time_finished is not None
@property
def time_spent(self):
if (
self.time_started is not None
and self.time_finished is not None
):
return self.time_finished - self.time_started
else:
return None
def finished(self): def finished(self):
assert self.time_started is not None assert self.time_started is not None
assert self.time_finished is None assert self.time_finished is None
self.time_finished = monotonic() self.time_finished = monotonic()
if hasattr(self, 'display'):
self.display.stop(self.time_spent)
def terminate(self):
class ProgressReporterThread(threading.Thread): if hasattr(self, 'display'):
""" self.display.stop(self.time_spent)
Reports download progress based on its status.
Uses threading to periodically update the status (speed, ETA, etc.).
"""
def __init__(
self,
status: DownloadStatus,
output: IO,
tick=.1,
update_interval=1
):
super().__init__()
self.status = status
self.output = output
self._tick = tick
self._update_interval = update_interval
self._spinner_pos = 0
self._status_line = ''
self._prev_bytes = 0
self._prev_time = monotonic()
self._should_stop = threading.Event()
def stop(self):
"""Stop reporting on next tick."""
self._should_stop.set()
def run(self):
while not self._should_stop.is_set():
if self.status.has_finished:
self.sum_up()
break
self.report_speed()
sleep(self._tick)
def report_speed(self):
now = monotonic()
if now - self._prev_time >= self._update_interval:
downloaded = self.status.downloaded
speed = ((downloaded - self._prev_bytes)
/ (now - self._prev_time))
if not self.status.total_size:
self._status_line = PROGRESS_NO_CONTENT_LENGTH.format(
downloaded=humanize_bytes(downloaded),
speed=humanize_bytes(speed),
)
else:
percentage = (downloaded / self.status.total_size * 100
if self.status.total_size
else 0)
if not speed:
eta = '-:--:--'
else:
s = int((self.status.total_size - downloaded) / speed)
h, s = divmod(s, 60 * 60)
m, s = divmod(s, 60)
eta = f'{h}:{m:0>2}:{s:0>2}'
self._status_line = PROGRESS.format(
percentage=percentage,
downloaded=humanize_bytes(downloaded),
speed=humanize_bytes(speed),
eta=eta,
)
self._prev_time = now
self._prev_bytes = downloaded
self.output.write(
f'{CLEAR_LINE} {SPINNER[self._spinner_pos]} {self._status_line}'
)
self.output.flush()
self._spinner_pos = (self._spinner_pos + 1) % len(SPINNER)
def sum_up(self):
actually_downloaded = (
self.status.downloaded - self.status.resumed_from)
time_taken = self.status.time_finished - self.status.time_started
speed = actually_downloaded / time_taken if time_taken else actually_downloaded
self.output.write(CLEAR_LINE)
self.output.write(SUMMARY.format(
downloaded=humanize_bytes(actually_downloaded),
total=(self.status.total_size
and humanize_bytes(self.status.total_size)),
speed=humanize_bytes(speed),
time=time_taken,
))
self.output.flush()

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

@ -4,6 +4,7 @@ from typing import Any, Type, List, Dict, TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from httpie.sessions import Session from httpie.sessions import Session
INSECURE_COOKIE_JAR_WARNING = '''\ INSECURE_COOKIE_JAR_WARNING = '''\
Outdated layout detected for the current session. Please consider updating it, Outdated layout detected for the current session. Please consider updating it,
in order to not get affected by potential security problems. in order to not get affected by potential security problems.
@ -53,16 +54,12 @@ def pre_process(session: 'Session', cookies: Any) -> List[Dict[str, Any]]:
for cookie in normalized_cookies for cookie in normalized_cookies
) )
if should_issue_warning and not session.refactor_mode: if should_issue_warning:
warning = INSECURE_COOKIE_JAR_WARNING.format(hostname=session.bound_host, session_id=session.session_id) warning = INSECURE_COOKIE_JAR_WARNING.format(hostname=session.bound_host, session_id=session.session_id)
if not session.is_anonymous: if not session.is_anonymous:
warning += INSECURE_COOKIE_JAR_WARNING_FOR_NAMED_SESSIONS warning += INSECURE_COOKIE_JAR_WARNING_FOR_NAMED_SESSIONS
warning += INSECURE_COOKIE_SECURITY_LINK warning += INSECURE_COOKIE_SECURITY_LINK
session.warn_legacy_usage(warning)
session.env.log_error(
warning,
level='warning'
)
return normalized_cookies return normalized_cookies

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

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

View File

@ -1,5 +1,6 @@
from textwrap import dedent from textwrap import dedent
from httpie.cli.argparser import HTTPieManagerArgumentParser from httpie.cli.argparser import HTTPieManagerArgumentParser
from httpie.cli.options import Qualifiers, ARGPARSE_QUALIFIER_MAP, map_qualifiers, parser_to_parser_spec
from httpie import __version__ from httpie import __version__
CLI_SESSION_UPGRADE_FLAGS = [ CLI_SESSION_UPGRADE_FLAGS = [
@ -12,37 +13,6 @@ CLI_SESSION_UPGRADE_FLAGS = [
] ]
COMMANDS = { COMMANDS = {
'plugins': {
'help': 'Manage HTTPie plugins.',
'install': [
'Install the given targets from PyPI '
'or from a local paths.',
{
'dest': 'targets',
'nargs': '+',
'help': 'targets to install'
}
],
'upgrade': [
'Upgrade the given plugins',
{
'dest': 'targets',
'nargs': '+',
'help': 'targets to upgrade'
}
],
'uninstall': [
'Uninstall the given HTTPie plugins.',
{
'dest': 'targets',
'nargs': '+',
'help': 'targets to install'
}
],
'list': [
'List all installed HTTPie plugins.'
],
},
'cli': { 'cli': {
'help': 'Manage HTTPie for Terminal', 'help': 'Manage HTTPie for Terminal',
'export-args': [ 'export-args': [
@ -50,9 +20,13 @@ COMMANDS = {
{ {
'flags': ['-f', '--format'], 'flags': ['-f', '--format'],
'choices': ['json'], 'choices': ['json'],
'help': 'Format to export in.',
'default': 'json' 'default': 'json'
} }
], ],
'check-updates': [
'Check for updates'
],
'sessions': { 'sessions': {
'help': 'Manage HTTPie sessions', 'help': 'Manage HTTPie sessions',
'upgrade': [ 'upgrade': [
@ -82,6 +56,42 @@ COMMANDS = {
} }
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.'
],
}
def missing_subcommand(*args) -> str: def missing_subcommand(*args) -> str:
base = COMMANDS base = COMMANDS
for arg in args: for arg in args:
@ -92,7 +102,7 @@ def missing_subcommand(*args) -> str:
return f'Please specify one of these: {subcommands}' return f'Please specify one of these: {subcommands}'
def generate_subparsers(root, parent_parser, definitions): def generate_subparsers(root, parent_parser, definitions, spec):
action_dest = '_'.join(parent_parser.prog.split()[1:] + ['action']) action_dest = '_'.join(parent_parser.prog.split()[1:] + ['action'])
actions = parent_parser.add_subparsers( actions = parent_parser.add_subparsers(
dest=action_dest dest=action_dest
@ -105,13 +115,15 @@ def generate_subparsers(root, parent_parser, definitions):
command_parser = actions.add_parser(command, description=descr) command_parser = actions.add_parser(command, description=descr)
command_parser.root = root command_parser.root = root
if is_subparser: if is_subparser:
generate_subparsers(root, command_parser, properties) generate_subparsers(root, command_parser, properties, spec)
continue continue
group = spec.add_group(parent_parser.prog + ' ' + command, description=descr)
for argument in properties: for argument in properties:
argument = argument.copy() argument = argument.copy()
flags = argument.pop('flags', []) flags = argument.pop('flags', [])
command_parser.add_argument(*flags, **argument) command_parser.add_argument(*flags, **map_qualifiers(argument, ARGPARSE_QUALIFIER_MAP))
group.add_argument(*flags, **argument)
parser = HTTPieManagerArgumentParser( parser = HTTPieManagerArgumentParser(
@ -158,4 +170,12 @@ parser.add_argument(
''' '''
) )
generate_subparsers(parser, parser, COMMANDS) man_page_hint = '''
If you are looking for the man pages of http/https commands, try one of the following:
$ man http
$ man https
'''
options = parser_to_parser_spec(parser, man_page_hint=man_page_hint, source_file=__file__)
generate_subparsers(parser, parser, COMMANDS, options)

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

@ -0,0 +1,68 @@
import sys
import shutil
import subprocess
from contextlib import suppress
from typing import List, Optional
from httpie.compat import is_frozen
class PipError(Exception):
"""An exception that occurs when pip exits with an error status code."""
def __init__(self, stdout, stderr):
self.stdout = stdout
self.stderr = stderr
def _discover_system_pip() -> List[str]:
# When we are running inside of a frozen binary, we need the system
# pip to install plugins since there is no way for us to execute any
# code outside of the HTTPie.
#
# We explicitly depend on system pip, so the SystemError should not
# be executed (except for broken installations).
def _check_pip_version(pip_location: Optional[str]) -> bool:
if not pip_location:
return False
with suppress(subprocess.CalledProcessError):
stdout = subprocess.check_output([pip_location, "--version"], text=True)
return "python 3" in stdout
targets = [
"pip",
"pip3"
]
for target in targets:
pip_location = shutil.which(target)
if _check_pip_version(pip_location):
return pip_location
raise SystemError("Couldn't find 'pip' executable. Please ensure that pip in your system is available.")
def _run_pip_subprocess(pip_executable: List[str], args: List[str]) -> bytes:
cmd = [*pip_executable, *args]
try:
process = subprocess.run(
cmd,
check=True,
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
except subprocess.CalledProcessError as error:
raise PipError(error.stdout, error.stderr) from error
else:
return process.stdout
def run_pip(args: List[str]) -> bytes:
if is_frozen:
pip_executable = [_discover_system_pip()]
else:
pip_executable = [sys.executable, '-m', 'pip']
return _run_pip_subprocess(pip_executable, args)

View File

@ -2,7 +2,6 @@ import argparse
from typing import Optional from typing import Optional
from httpie.context import Environment from httpie.context import Environment
from httpie.manager.plugins import PluginInstaller
from httpie.status import ExitStatus from httpie.status import ExitStatus
from httpie.manager.cli import missing_subcommand, parser from httpie.manager.cli import missing_subcommand, parser
from httpie.manager.tasks import CLI_TASKS from httpie.manager.tasks import CLI_TASKS
@ -36,8 +35,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
parser.error(MSG_NAKED_INVOCATION) parser.error(MSG_NAKED_INVOCATION)
if args.action == 'plugins': if args.action == 'plugins':
plugins = PluginInstaller(env, debug=args.debug) return dispatch_cli_task(env, args.action, args)
return plugins.run(args.plugins_action, args)
elif args.action == 'cli': elif args.action == 'cli':
return dispatch_cli_task(env, args.cli_action, args) return dispatch_cli_task(env, args.cli_action, args)

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

@ -1,25 +1,19 @@
import argparse import argparse
from typing import TypeVar, Callable, Tuple
from httpie.sessions import SESSIONS_DIR_NAME, get_httpie_session from httpie.sessions import SESSIONS_DIR_NAME, get_httpie_session
from httpie.status import ExitStatus from httpie.status import ExitStatus
from httpie.context import Environment from httpie.context import Environment
from httpie.legacy import cookie_format as legacy_cookies 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.manager.cli import missing_subcommand, parser
from httpie.utils import is_version_greater
T = TypeVar('T')
CLI_TASKS = {}
def task(name: str) -> Callable[[T], T]: FIXERS_TO_VERSIONS = {
def wrapper(func: T) -> T: '3.1.0': v3_1_0_session_cookie_format.fix_layout,
CLI_TASKS[name] = func '3.2.0': v3_2_0_session_header_format.fix_layout,
return func }
return wrapper
@task('sessions')
def cli_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus: def cli_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus:
action = args.cli_sessions_action action = args.cli_sessions_action
if action is None: if action is None:
@ -33,30 +27,6 @@ def cli_sessions(env: Environment, args: argparse.Namespace) -> ExitStatus:
raise ValueError(f'Unexpected action: {action}') raise ValueError(f'Unexpected action: {action}')
def is_version_greater(version_1: str, version_2: str) -> bool:
# In an ideal scenerio, we would depend on `packaging` in order
# to offer PEP 440 compatible parsing. But since it might not be
# commonly available for outside packages, and since we are only
# going to parse HTTPie's own version it should be fine to compare
# this in a SemVer subset fashion.
def split_version(version: str) -> Tuple[int, ...]:
parts = []
for part in version.split('.')[:3]:
try:
parts.append(int(part))
except ValueError:
break
return tuple(parts)
return split_version(version_1) > split_version(version_2)
FIXERS_TO_VERSIONS = {
'3.1.0': legacy_cookies.fix_layout
}
def upgrade_session(env: Environment, args: argparse.Namespace, hostname: str, session_name: str): def upgrade_session(env: Environment, args: argparse.Namespace, hostname: str, session_name: str):
session = get_httpie_session( session = get_httpie_session(
env=env, env=env,
@ -64,7 +34,7 @@ def upgrade_session(env: Environment, args: argparse.Namespace, hostname: str, s
session_name=session_name, session_name=session_name,
host=hostname, host=hostname,
url=hostname, url=hostname,
refactor_mode=True suppress_legacy_warnings=True
) )
session_name = session.path.stem session_name = session.path.stem
@ -114,28 +84,3 @@ def cli_upgrade_all_sessions(env: Environment, args: argparse.Namespace) -> Exit
session_name=session_name session_name=session_name
) )
return status return status
FORMAT_TO_CONTENT_TYPE = {
'json': 'application/json'
}
@task('export-args')
def cli_export(env: Environment, args: argparse.Namespace) -> ExitStatus:
import json
from httpie.cli.definition import options
from httpie.cli.options import to_data
from httpie.output.writer import write_raw_data
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,6 +1,7 @@
from time import monotonic from time import monotonic
import requests import requests
from urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS
from enum import Enum, auto from enum import Enum, auto
from typing import Iterable, Union, NamedTuple from typing import Iterable, Union, NamedTuple
@ -16,7 +17,6 @@ from .cli.constants import (
from .compat import cached_property from .compat import cached_property
from .utils import split_cookies, parse_content_type_header from .utils import split_cookies, parse_content_type_header
ELAPSED_TIME_LABEL = 'Elapsed time' ELAPSED_TIME_LABEL = 'Elapsed time'
@ -67,23 +67,10 @@ class HTTPResponse(HTTPMessage):
def iter_lines(self, chunk_size): def iter_lines(self, chunk_size):
return ((line, b'\n') for line in self._orig.iter_lines(chunk_size)) return ((line, b'\n') for line in self._orig.iter_lines(chunk_size))
# noinspection PyProtectedMember
@property @property
def headers(self): def headers(self):
try:
raw_version = self._orig.raw._original_response.version
except AttributeError:
# Assume HTTP/1.1
raw_version = 11
version = {
9: '0.9',
10: '1.0',
11: '1.1',
20: '2',
}[raw_version]
original = self._orig original = self._orig
status_line = f'HTTP/{version} {original.status_code} {original.reason}' status_line = f'HTTP/{self.version} {original.status_code} {original.reason}'
headers = [status_line] headers = [status_line]
headers.extend( headers.extend(
': '.join(header) ': '.join(header)
@ -113,6 +100,32 @@ class HTTPResponse(HTTPMessage):
for key, value in data.items() for key, value in data.items()
) )
@property
def version(self) -> str:
"""
Return the HTTP version used by the server, e.g. '1.1'.
Assume HTTP/1.1 if version is not available.
"""
mapping = {
9: '0.9',
10: '1.0',
11: '1.1',
20: '2.0',
}
fallback = 11
version = None
try:
raw = self._orig.raw
if getattr(raw, '_original_response', None):
version = raw._original_response.version
else:
version = raw.version
except AttributeError:
pass
return mapping[version or fallback]
class HTTPRequest(HTTPMessage): class HTTPRequest(HTTPMessage):
"""A :class:`requests.models.Request` wrapper.""" """A :class:`requests.models.Request` wrapper."""
@ -140,6 +153,7 @@ class HTTPRequest(HTTPMessage):
headers = [ headers = [
f'{name}: {value if isinstance(value, str) else value.decode()}' f'{name}: {value if isinstance(value, str) else value.decode()}'
for name, value in headers.items() for name, value in headers.items()
if not (name.lower() in SKIPPABLE_HEADERS and value == SKIP_HEADER)
] ]
headers.insert(0, request_line) headers.insert(0, request_line)

View File

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

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