Compare commits

...

71 Commits

Author SHA1 Message Date
279e387d86 WIP 2021-10-08 10:45:49 +02:00
50f57f8c82 Add help output for --chunked (#1174) 2021-10-07 17:37:06 +02:00
555afef486 Update README.md 2021-10-07 16:21:36 +02:00
476eb4f0d9 Use HTTPie repository everywhere 2021-10-07 16:15:52 +02:00
3869c3ce99 Remove unused import 2021-10-07 14:41:03 +02:00
17f74f10f3 Add summary to Chocolatey metadata
It is strongly recommended to add a summary.
It will be effective for the next release though.
2021-10-07 14:34:33 +02:00
1e094d0a79 Fix Snapcraft and Spack anchors 2021-10-07 14:06:06 +02:00
bd227c0364 Specify the API key to submit Chocolatey packages 2021-10-07 13:58:02 +02:00
9dda23a322 Add Chocolatey packaging information (#1172)
* Add Chocolatey packaging information

* Fix working directory

* Fix Python dependency

* Update icon URL

* Fix local installation

* Simplify links

* Remove the workflow, it adds no real value and th etime to fix is is not worth
2021-10-07 13:53:11 +02:00
ef4fa20ceb Tweak 2021-10-06 19:31:43 +02:00
7e0bed4e54 Add more types and docs for plugins API II. 2021-10-06 19:28:21 +02:00
e1627803fe Add more types and docs for plugins API 2021-10-06 19:24:10 +02:00
f954c9e2b7 Update CHANGELOG.md 2021-10-06 17:35:07 +02:00
80e83f0463 Ignore more venv folders and VS Code folder 2021-10-06 17:29:03 +02:00
4f1c9441c5 Fix encoding error with non-prettified encoded responses (#1168)
* Fix encoding error with non-prettified encoded responses

Removed `--format-option response.as` an promote `--response-as`: using
the format option would be misleading as it is now also used by non-prettified
responses.

* Encoding refactoring

* split --response-as into --response-mime and --response-charset
* add support for Content-Type charset for requests printed to terminal
* add support charset detection for requests printed to terminal without a Content-Type charset
* etc.

* `test_unicode.py` → `test_encoding.py`

* Drop sequence length check

* Clean-up tests

* [skip ci] Tweaks

* Use the compatible release clause for `charset_normalizer` requirement

Cf. https://www.python.org/dev/peps/pep-0440/#version-specifiers

* Clean-up

* Partially revert d52a4833e4

* Changelog

* Tweak tests

* [skip ci] Better test name

* Cleanup tests and add request body charset detection

* More test suite cleanups

* Cleanup

* Fix code style in test

* Improve detect_encoding() docstring

* Uniformize pytest.mark.parametrize() calls

* [skip ci] Comment out TODOs (will be tackled in a specific PR)

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2021-10-06 17:27:07 +02:00
7989e438d2 Add documentation about our release process (#1159)
* Add documentation about our release process

* Fixes

* Add company-related tasks, enable back WIP pages

* Fix WIP links

* Add AOSC OS

* Add WIP for AOSC OS

* Tweak

* Remove maintainers email IDs

* Use GH nicknames

* Remove useless WIP for brew

* Tweaks
2021-10-06 16:45:44 +02:00
93114072c8 Fix looked path for workflow testing packages 2021-10-06 11:21:54 +02:00
08751d3672 Add install/update instructions database (#1160)
* Add install/update instructions database

* Update the database

* Revert README changes

They will be overwritten later.

* Revert

* Tweak

* Tweaks

* Upgrade database

* Complete commands

Still not sure about Spack upgrades.

* Sort

* Doc generation script draft

* Remove OS names from tool names

* Fix Linuxbrew name

* `wheel` already installs `setuptools`

* Gen docs

* Update

* Tweak

* Add a GitHub workflow to check for outdated installation instructions

* Fix return value

* Test

* Delete test

* Rename the script

* Add `make doc-install-inst`

* Add missing dev requirement

* The first tool is the primary we want to display

Then they are simply sorted by `tool.title`.

* Sort OSes by name

* Refactoring, jinja template, etc.

* Add tool title uniqueness `assert`, fix platform list extra `\n`

* Rebuild docs

* Update generate.py

* Update README.md

* Update methods.yml

* Update distros derived, more assertions

* Tweaks

* Add workflow to auto-update the docs

* Do not hide the command

* Tweaks

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2021-10-06 11:18:27 +02:00
0c9d701618 Add make install-reqs to install packages without creating env 2021-10-05 21:37:48 +02:00
a3fa016428 Cover on Python 3.10 2021-10-05 21:30:28 +02:00
9c52449344 Add self to paths; same paths for PR and push 2021-10-05 21:28:39 +02:00
e4e4927567 Test on Python 3.10 2021-10-05 21:25:10 +02:00
031b4b89e3 Fix docs formatting 2021-10-05 15:46:10 +02:00
e1c08a3de5 Mention Snapcraft for unstable version installation 2021-10-05 15:45:34 +02:00
033798adc1 Update README.md 2021-10-03 03:20:23 +02:00
6a4e985f71 Remove redundant/inconsistent article 2021-10-03 03:08:05 +02:00
a6c70334cf Expand HTTP method docs
* mention `POST` without a body
* mention `GET` with a body
* mention custom method names
* include examples for default `GET`/`POST`
2021-10-03 03:05:37 +02:00
7388401134 Update setup.py 2021-10-02 16:50:39 +02:00
4ef31ecf71 Update utils.py 2021-10-02 16:43:29 +02:00
2423f893e5 Update config.json 2021-09-30 14:39:32 +02:00
b6a694afbc master/latest docs differ since --response-as 2021-09-30 14:39:18 +02:00
71adcd97d0 Improve handling of prettified responses without correct content-type encoding (#1110)
* Improve handling of responses without correct content-type charset

* [skip ci] Minor tweaks in tests

* [skip ci] Add documentation

Co-authored-by: claudiatd <claudiatd@gmail.com>

* Improve unknown encoding test

[skip ci]

* Review mime and options retrieval

* Add full content-type example in help output

* Simplify decoder

* [skip ci] s/charset/encoding/

* Tweaks

* [skip ci] Fix type annotation

* [skip ci] s/charset/encoding/

* Tweaks

* Fix type annoation

* Improvement

* Introduce `codec.encode()`

* [skip ci] Tweak changelog

Co-authored-by: claudiatd <claudiatd@gmail.com>
2021-09-29 20:22:19 +02:00
b50f9aa7e7 Use PYthon 3 documentation
[skip ci]
2021-09-28 12:54:16 +02:00
fe96b2af20 Use httpie.io/docs everywhere
[skip ci]
2021-09-28 12:53:53 +02:00
727b8a2c05 Sort available style choices (#1166) 2021-09-27 16:55:10 +02:00
9c89c703ae Allow to overwrite the response Content-Type from options (#1134)
* Allow to override the response `Content-Type` from options

* Apply suggestions from code review

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>

* Rename the option from `--response.content-type` to `--response-as`

* Update CHANGELOG.md

Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2021-09-27 13:58:19 +02:00
8f8851f1db Remove trailing comma in test 2021-09-24 10:37:59 +02:00
bce2b3a98e Sort changelog 2021-09-23 17:17:29 +02:00
474093acdf Include plugin info in --debug output (#1165)
* Include plugin info in `--debug` output

* Adapt issue number

* Fix docs
2021-09-23 17:15:14 +02:00
1535d0c976 Mention XML when explaining formatting 2021-09-23 12:27:03 +02:00
cae83b3f9e Add FreeBSD installation instructions
Closes #761.
2021-09-23 10:46:06 +02:00
507514b795 Add workflow to test with pyOpenSSL active (#1164)
* Add workflow to test with pyOpenSSL active

Original patch by @gmelodie.

* Fix tests on Windows with Python 3.6
2021-09-23 10:37:23 +02:00
d7ed45bbcd Fix duplicate keys preservation of JSON data (#1163)
* Fix duplicate keys preservation of JSON data

* Update issue number

* Fix type annotations

* Changes after review

* Rewording
2021-09-21 19:07:59 +02:00
e6c5cd3e4b Improve JSON output when there is leading data before the actual JSON body (#1130)
In some special cases, to prevent against Cross Site Script Inclusion (XSSI)
attacks, the JSON response body starts with a magic prefix line that must be
stripped before feeding the rest of the response body to the JSON parser.
Such prefix is now simply ignored from the parser but still printed in the
terminal.

* Fix Windows tests
2021-09-21 11:15:43 +02:00
273134123a Bump the version to 2.6.0.dev0 (#1162)
[skip ci]
2021-09-21 10:40:09 +02:00
529aa78ee1 Expand the pytest configuration (#1161)
And rely on it to run tests.
2021-09-20 17:36:03 +02:00
e2ba214ac0 [snap] Improve OS integration (#1157)
Get back read-write access to `$HOME/.config/httpie` and `$HOME/.httpie`.
2021-09-15 16:50:44 +02:00
9dd0203bae Use HTTPie for the documentation build request (#1150)
Co-authored-by: Jakub Roztocil <jakub@roztocil.co>
2021-09-15 14:25:46 +02:00
ba6fd0bc14 Add a blog post link 2021-09-14 01:01:46 +02:00
8f7f4a6ef4 Remove some horizontal lines in the bug report
And add myself to assignees.
2021-09-13 12:36:01 +02:00
9984447f18 Reverse results in bug report temlpate
Ir seems weird to ask for the expected result before knowing the current one.
2021-09-13 12:33:32 +02:00
10081b9fcc Update README.md 2021-09-11 17:17:15 +02:00
4f84362d73 Update config.json 2021-09-10 23:58:33 +02:00
2b5f8f48bf Update update-documentation.yml 2021-09-10 20:06:29 +02:00
a51068a44d Update update-documentation.yml 2021-09-10 20:05:23 +02:00
f06d870012 Update and rename documentations.yml to check-markdown.yml 2021-09-10 20:04:52 +02:00
0115a4a466 Create config.json 2021-09-10 18:50:45 +02:00
7c1d26a8fa Update README.md 2021-09-10 11:17:23 +02:00
7734e47280 Update README.md 2021-09-10 11:17:10 +02:00
30c595b770 [snap] Comment out the problematic interface
It seems it just needs to be present for the snap to be rejected.
2021-09-10 11:04:57 +02:00
b38352858f [snap] Remove personal-files interface
Use of the `personal-files` interface is reserved for vetted publishers.

The interface requires a validation, but we need to publish at least
one package first. So let's skip that part, release a version and ask
for the interface access in a second time.

Also add a workflow to build & test the snap package.
2021-09-10 10:30:44 +02:00
a45b94fda6 Complete CentOS installation instructions 2021-09-09 16:38:36 +02:00
513e5080e4 Add the release workflow
It has to be triggered manually for now.
2021-09-09 16:13:13 +02:00
7c9f415107 Add a workflow to check documentations (#1151)
* Add a workflow to check documentations

* Fix markdown issues

* Install Ruby 2.7

* Finally, handle and fix GitHub templates

* Minor improvement in the feature request template

* Verbose mode to be sure all files are checked
2021-09-09 15:52:24 +02:00
4c8633c6e5 Split the monolithic workflow into specific ones (#1149)
* Split the monolithic workflow into specific ones

* Rename workflows, improve commands

* Update pip from the venv

* Fix Windows setup

* Lowercase macos-latest

* Fix Windows run, again
2021-09-08 16:41:55 +02:00
4d7d6b66cf Trigger official documentation build when documentation is updated here 2021-09-08 15:43:34 +02:00
a586fca246 Update brew formula to 2.5.0 (#1144)
* Update brew formula to 2.5.0

* Can use `idna` 3.2

* Sort requirements to ease reproductible builds

And also to have the same output as `brew bump-formula-pr`.

* Sync `bottles` with official Formula

And keep the `high_sierra` one.

* Add a workflow to check the Formula
2021-09-08 11:01:27 +02:00
978258ec5b Add Alpine Linux installation instructions 2021-09-08 10:04:49 +02:00
84ef9f588c Use lzo compression for snap (#1146) 2021-09-07 16:57:05 +02:00
cf21790411 Add the Snap build file for general Linux packaging
Based on the work of @elopio and @chipaca.

- Added support for the `snapd` protocol URL.
- Packaged Unix socket transport plugin.
2021-09-07 16:45:57 +02:00
1ef127c61d Packit: Get the current Fedora Rawhide specfile
Using the fork is not needed anymore,
since Rawhide was updated to 2.5.0 and no longer has patches.
2021-09-07 11:03:04 +02:00
120 changed files with 7065 additions and 634 deletions

View File

@ -3,38 +3,42 @@ name: Bug report
about: Report a possible bug in HTTPie
title: ''
labels: "new, bug"
assignees: ''
assignees: 'BoboTiG'
---
**Checklist**
## Checklist
- [ ] I've searched for similar issues.
- [ ] I'm using the latest version of HTTPie.
---
**What are the steps to reproduce the problem?**
## Minimal reproduction code and steps
1.
2.
3.
## Current result
**What is the expected result?**
## Expected result
**What happens instead?**
---
**Debug output**
## Debug output
Please re-run the command with `--debug`, then copy the entire command & output and paste both below:
```
```bash
$ http --debug <COMPLETE ARGUMENT LIST THAT TRIGGERS THE ERROR>
<COMPLETE OUTPUT>
```
## Additional information, screenshots, or code examples
**Provide any additional information, screenshots, or code examples below:**

View File

@ -6,19 +6,25 @@ labels: "new, enhancement"
assignees: ''
---
**Checklist**
## Checklist
- [ ] I've searched for similar feature requests.
---
**What enhancement would you like to see?**
## Enhancement request
**What problem does it solve?**
---
E.g. “I'm always frustrated when [...]”, “Im trying to do […] so that […]”.
## Problem it solves
E.g. “I'm always frustrated when […]”, “Im trying to do […] so that […]”.
**Provide any additional information, screenshots, or code examples below:**
---
## Additional information, screenshots, or code examples

View File

@ -1,35 +0,0 @@
name: Build
on: [push, pull_request]
jobs:
extras:
# Run coverage and extra tests only once
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.9
- run: python -m pip install --upgrade pip setuptools wheel
- run: make install
- run: make codestyle
- run: make test-cover
- run: make codecov-upload
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_REPO_TOKEN }}
- run: make test-dist
test:
# Run core HTTPie tests everywhere
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
python-version: [3.6, 3.7, 3.8, 3.9, "3.10.0-rc.1"]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- run: python -m pip install --upgrade pip setuptools wheel
- run: python -m pip install --upgrade '.[dev]'
- run: python -m pytest --verbose ./httpie ./tests

19
.github/workflows/code-style.yml vendored Normal file
View File

@ -0,0 +1,19 @@
on:
pull_request:
paths:
- .github/workflows/code-style.yml
- extras/*.py
- httpie/**/*.py
- setup.py
- tests/**/*.py
jobs:
code-style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.9
- run: make venv
- run: make codestyle

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

@ -0,0 +1,22 @@
on:
pull_request:
paths:
- .github/workflows/coverage.yml
- httpie/**/*.py
- setup.*
- tests/**/*.py
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: "3.10"
- run: make install
- run: make test-cover
- run: make codecov-upload
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_REPO_TOKEN }}
- run: make test-dist

View File

@ -0,0 +1,19 @@
on:
pull_request:
paths:
- "*.md"
- "**/*.md"
jobs:
doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
- name: Install the linter
run: sudo gem install mdl
- name: Check files
run: make doc-check

20
.github/workflows/docs-deploy.yml vendored Normal file
View File

@ -0,0 +1,20 @@
on:
push:
branches:
- master
paths:
- docs/README.md
- docs/config.json
release:
types:
- published
- unpublished
- deleted
jobs:
trigger-doc-build:
runs-on: ubuntu-latest
steps:
- name: Install HTTPie
run: sudo snap install --edge httpie
- name: Trigger new documentation build
run: http --ignore-stdin POST ${{ secrets.DOCS_UPDATE_VERCEL_HOOK }}

View File

@ -0,0 +1,26 @@
on:
push:
branches:
- master
paths:
- docs/installation/*
# Allow to call the workflow manually
workflow_dispatch:
jobs:
doc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.9
- run: make install
- run: make doc-update-install
- uses: Automattic/action-commit-to-branch@master
with:
branch: master
commit_message: Auto-update installation instructions in the docs
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

26
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,26 @@
on:
# Add a "Trigger" button to manually start the workflow.
workflow_dispatch:
inputs:
branch:
description: "The branch, tag or SHA to release from"
required: true
default: "master"
# It could be fully automated by uncommenting following lines.
# Let's see later if we are confident enough to try it :)
# release:
# types:
# - published
jobs:
new-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.9
- run: make publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}

View File

@ -0,0 +1,23 @@
on:
pull_request:
paths:
- .github/workflows/test-package-linux-snap.yml
- snapcraft.yaml
jobs:
snap:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
uses: snapcore/action-build@v1
id: snapcraft
- name: Install
run: sudo snap install --dangerous ${{ steps.snapcraft.outputs.snap }}
- name: Test
run: |
httpie.http --version
httpie.https --version
# Auto-aliases cannot be tested when installing a snap outside the store.
# http --version
# https --version

View File

@ -0,0 +1,17 @@
on:
pull_request:
paths:
- .github/workflows/test-package-mac-brew.yml
- docs/packaging/brew/httpie.rb
jobs:
brew:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Setup brew
run: |
brew developer on
brew update
- name: Build and test the formula
run: make brew-test

45
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,45 @@
on:
push:
branches:
- master
paths:
- .github/workflows/tests.yml
- httpie/**/*.py
- setup.*
- tests/**/*.py
pull_request:
paths:
- .github/workflows/tests.yml
- httpie/**/*.py
- setup.*
- tests/**/*.py
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
pyopenssl: [0, 1]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Windows setup
if: matrix.os == 'windows-latest'
run: |
python -m pip install --upgrade pip wheel
python -m pip install --upgrade '.[dev]'
python -m pytest --verbose ./httpie ./tests
env:
HTTPIE_TEST_WITH_PYOPENSSL: ${{ matrix.pyopenssl }}
- name: Linux & Mac setup
if: matrix.os != 'windows-latest'
run: |
make install
make test
env:
HTTPIE_TEST_WITH_PYOPENSSL: ${{ matrix.pyopenssl }}

7
.gitignore vendored
View File

@ -118,6 +118,7 @@ celerybeat.pid
.venv
env/
venv/
venv*/
ENV/
env.bak/
venv.bak/
@ -144,3 +145,9 @@ dmypy.json
/httpie.spec
/httpie-*.rpm
/httpie-*.tar.gz
# VS Code
.vscode/
# Windows Chocolatey
*.nupkg

View File

@ -2,10 +2,8 @@
# https://packit.dev/docs/configuration/
specfile_path: httpie.spec
actions:
# the current Fedora Rawhide specfile has some patches
# so we get it from @hroncok's (= churchyard in Fedora) fork for now
# once we have a new release, we'll use: https://src.fedoraproject.org/rpms/httpie/raw/rawhide/f/httpie.spec
post-upstream-clone: "wget https://src.fedoraproject.org/fork/churchyard/rpms/httpie/raw/packit/f/httpie.spec -O httpie.spec"
# get the current Fedora Rawhide specfile:
post-upstream-clone: "wget https://src.fedoraproject.org/rpms/httpie/raw/rawhide/f/httpie.spec -O httpie.spec"
jobs:
- job: copr_build
trigger: pull_request

View File

@ -3,8 +3,19 @@
This document records all notable changes to [HTTPie](https://httpie.io).
This project adheres to [Semantic Versioning](https://semver.org/).
## [2.6.0.dev0](https://github.com/httpie/httpie/compare/2.5.0...master) (unreleased)
- 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 `--response-charset` to allow overriding the response encoding for terminal display purposes. ([#1168](https://github.com/httpie/httpie/issues/1168))
- Added `--response-mime` to allow overriding the response mime type for coloring and formatting for the terminal. ([#1168](https://github.com/httpie/httpie/issues/1168))
- Improved handling of responses with incorrect `Content-Type`. ([#1110](https://github.com/httpie/httpie/issues/1110), [#1168](https://github.com/httpie/httpie/issues/1168))
- Installed plugins are now listed in `--debug` output. ([#1165](https://github.com/httpie/httpie/issues/1165))
- Fixed duplicate keys preservation of JSON data. ([#1163](https://github.com/httpie/httpie/issues/1163))
## [2.5.0](https://github.com/httpie/httpie/compare/2.4.0...2.5.0) (2021-09-06)
Blog post: [Whats new in HTTPie 2.5.0](https://httpie.io/blog/httpie-2.5.0)
- Added `--raw` to allow specifying the raw request body without extra processing as
an alternative to `stdin`. ([#534](https://github.com/httpie/httpie/issues/534))
- Added support for XML formatting. ([#1129](https://github.com/httpie/httpie/issues/1129))

View File

@ -68,7 +68,7 @@ members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
version 1.4, available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
<https://www.contributor-covenant.org/faq>

View File

@ -44,7 +44,7 @@ Consider also adding a [CHANGELOG](https://github.com/httpie/httpie/blob/master/
#### Getting the code
Go to https://github.com/httpie/httpie and fork the project repository.
Go to <https://github.com/httpie/httpie> and fork the project repository.
```bash
# Clone your fork
@ -89,7 +89,7 @@ a hack but it works™.)
You should now see `(httpie)` next to your shell prompt, and
the `http` command should point to your development copy:
```
```bash
(httpie) ~/Code/httpie $ which http
/Users/<user>/Code/httpie/venv/bin/http
(httpie) ~/Code/httpie $ http --version

View File

@ -25,7 +25,13 @@ export PATH := $(VENV_BIN):$(PATH)
all: uninstall-httpie install test
install: venv
install: venv install-reqs
install-reqs:
@echo $(H1)Updating package tools$(H1END)
$(VENV_PIP) install --upgrade pip wheel
@echo $(H1)Installing dev requirements$(H1END)
$(VENV_PIP) install --upgrade --editable '.[dev]'
@ -34,6 +40,7 @@ install: venv
@echo
clean:
@echo $(H1)Cleaning up$(H1END)
rm -rf $(VENV_ROOT)
@ -77,11 +84,11 @@ venv:
test:
@echo $(H1)Running tests$(HEADER_EXTRA)$(H1END)
$(VENV_BIN)/python -m pytest $(COV) ./httpie $(COV) ./tests --doctest-modules --verbose ./httpie ./tests
$(VENV_BIN)/python -m pytest $(COV)
@echo
test-cover: COV=--cov
test-cover: COV=--cov=httpie --cov=tests
test-cover: HEADER_EXTRA=' (with coverage)'
test-cover: test
@ -123,7 +130,7 @@ pycodestyle: codestyle
codestyle:
@echo $(H1)Running flake8$(H1END)
@[ -f $(VENV_BIN)/flake8 ] || $(VENV_PIP) install --upgrade --editable '.[dev]'
$(VENV_BIN)/flake8 httpie/ tests/ extras/ *.py
$(VENV_BIN)/flake8 httpie/ tests/ docs/packaging/brew/ *.py
@echo
@ -135,6 +142,16 @@ codecov-upload:
@echo
doc-check:
@echo $(H1)Running documentations checks$(H1END)
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
###############################################################################
@ -179,10 +196,17 @@ uninstall-httpie:
###############################################################################
brew-deps:
extras/brew-deps.py
docs/packaging/brew/brew-deps.py
brew-test:
@echo $(H1)Uninstalling httpie$(H1END)
- brew uninstall httpie
brew install --build-from-source ./extras/httpie.rb
@echo $(H1)Building from source…$(H1END)
- brew install --build-from-source ./docs/packaging/brew/httpie.rb
@echo $(H1)Verifying…$(H1END)
brew test httpie
@echo $(H1)Auditing…$(H1END)
brew audit --strict httpie

View File

@ -17,7 +17,7 @@ They use simple and natural syntax and provide formatted and colorized output.
[![Build](https://img.shields.io/github/workflow/status/httpie/httpie/Build?color=%23FA9BFA&label=Build)](https://github.com/httpie/httpie/actions)
[![Coverage](https://img.shields.io/codecov/c/github/httpie/httpie?style=flat&label=Coverage&color=%2373DC8C)](https://codecov.io/gh/httpie/httpie)
[![Twitter](https://img.shields.io/twitter/follow/httpie?style=flat&color=%234B78E6&logoColor=%234B78E6)](https://twitter.com/httpie)
[![Chat](https://img.shields.io/badge/chat-Discord-brightgreen?style=flat&label=Chat%20on&color=%23FA9BFA)](https://httpie.io/chat)
[![Chat](https://img.shields.io/badge/chat-Discord-brightgreen?style=flat&label=Chat%20on&color=%23FA9BFA)](https://httpie.io/discord)
<img src="https://raw.githubusercontent.com/httpie/httpie/master/docs/httpie-animation.gif" alt="HTTPie in action" width="100%"/>
@ -38,31 +38,31 @@ They use simple and natural syntax and provide formatted and colorized output.
- Persistent sessions
- `wget`-like downloads
[See for all features →](https://httpie.io/docs)
[See all features →](https://httpie.io/docs)
## Examples
Hello World:
```
```bash
$ 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:
```
```bash
$ 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):
```
```bash
$ 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):
```
```bash
$ http -a USERNAME POST https://api.github.com/repos/httpie/httpie/issues/83/comments body='HTTPie is awesome! :heart:'
```
@ -71,7 +71,7 @@ $ http -a USERNAME POST https://api.github.com/repos/httpie/httpie/issues/83/com
## Community & support
- Visit the [HTTPie website](https://httpie.io) for full documentation and useful links.
- Join our [Discord server](https://httpie.io/chat) 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.
- 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.

View File

@ -1,6 +1,6 @@
<div class='hidden-website'>
# HTTPie Documentation
# HTTPie documentation
</div>
@ -34,109 +34,321 @@ You are invited to submit fixes and improvements to the docs by editing [this fi
- Custom headers
- Persistent sessions
- Wget-like downloads
- Linux, macOS and Windows support
- Linux, macOS, Windows, and FreeBSD support
- Plugins
- Documentation
- Test coverage
## Installation
### macOS
<div data-installation-instructions>
On macOS, HTTPie can also be installed via [Homebrew](https://brew.sh/):
<!--
THE INSTALLATION SECTION IS GENERATED
Do not edit here, but in docs/installation/.
-->
- [Universal](#universal)
- [macOS](#macos)
- [Windows](#windows)
- [Linux](#linux)
- [FreeBSD](#freebsd)
### Universal
#### PyPi
Please make sure you have Python 3.6 or newer (`python --version`).
```bash
# Install
$ python -m pip install --upgrade pip wheel
$ python -m pip install httpie
```
```bash
# Upgrade
$ python -m pip install --upgrade pip wheel
$ python -m pip install --upgrade httpie
```
### macOS
#### Homebrew
To install [Homebrew](https://brew.sh/) follow [installation instructions](https://docs.brew.sh/Installation).
```bash
# Install
$ brew update
$ brew install httpie
```
A MacPorts *port* is also available:
```bash
# Upgrade
$ brew update
$ brew upgrade httpie
```
#### MacPorts
To install [MacPorts](https://www.macports.org/) follow [installation instructions](https://www.macports.org/install.php).
```bash
# Install
$ port selfupdate
$ port install httpie
```
```bash
# Upgrade
$ port selfupdate
$ port upgrade httpie
```
#### Snapcraft (macOS)
To install [Snapcraft](https://snapcraft.io/) follow [installation instructions](https://snapcraft.io/docs/installing-snapd).
```bash
# Install
$ snap install httpie
```
```bash
# Upgrade
$ snap refresh httpie
```
#### Spack (macOS)
To install [Spack](https://spack.readthedocs.io/en/latest/index.html) follow [installation instructions](https://spack.readthedocs.io/en/latest/getting_started.html#installation).
```bash
# Install
$ spack install httpie
```
```bash
# Upgrade
$ spack install httpie
```
### Windows
#### Chocolatey
To install [Chocolatey](https://chocolatey.org/) follow [installation instructions](https://chocolatey.org/install).
```bash
# Install
$ choco install httpie
```
```bash
# Upgrade
$ choco upgrade httpie
```
### Linux
Most Linux distributions provide a package that can be installed using the
system package manager, for example:
#### Snapcraft (Linux)
To install [Snapcraft](https://snapcraft.io/) follow [installation instructions](https://snapcraft.io/docs/installing-snapd).
```bash
# Debian, Ubuntu, etc.
# Install
$ snap install httpie
```
```bash
# Upgrade
$ snap refresh httpie
```
#### Linuxbrew
To install [Linuxbrew](https://docs.brew.sh/Homebrew-on-Linux) follow [installation instructions](https://docs.brew.sh/Homebrew-on-Linux#install).
```bash
# Install
$ brew update
$ brew install httpie
```
```bash
# Upgrade
$ brew update
$ 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
$ apt update
$ apt install httpie
```
```bash
# Fedora
# Upgrade
$ apt update
$ apt upgrade httpie
```
#### Fedora
```bash
# Install
$ dnf update
$ dnf install httpie
```
```bash
# CentOS, RHEL, ...
# Upgrade
$ dnf update
$ dnf upgrade httpie
```
#### CentOS and RHEL
Also works for other RHEL-derived distributions like ClearOS, Oracle Linux, etc.
```bash
# Install
$ yum update
$ yum install epel-release
$ yum install httpie
```
```bash
# Gentoo
# Upgrade
$ yum update
$ yum upgrade httpie
```
#### Alpine Linux
```bash
# Install
$ apk update
$ apk add httpie
```
```bash
# Upgrade
$ apk update
$ apk add --upgrade httpie
```
#### Gentoo
```bash
# Install
$ emerge --sync
$ emerge httpie
```
```bash
# Arch Linux
$ pacman -S httpie
# Upgrade
$ emerge --sync
$ emerge --update httpie
```
#### Arch Linux
Also works for other Arch-derived distributions like ArcoLinux, EndeavourOS, Artix Linux, etc.
```bash
# Install
$ pacman -Sy httpie
```
```bash
# Solus
$ eopkg install httpie
# Upgrade
$ pacman -Syu httpie
```
### Windows, etc.
A universal installation method (that works on Linux, macOS and Windows, and always provides the latest version) is to use [pip](https://pypi.org/project/pip/):
#### Void Linux
```bash
# Make sure we have an up-to-date version of pip and setuptools:
$ python -m pip install --upgrade pip setuptools
$ python -m pip install --upgrade httpie
# Install
$ xbps-install -Su
$ xbps-install -S httpie
```
(If `pip` installation fails for some reason, you can try
`easy_install httpie` as a fallback.)
Windows users can also install HTTPie with [Chocolatey](https://chocolatey.org):
```bash
$ choco upgrade httpie
# Upgrade
$ xbps-install -Su
$ xbps-install -Su httpie
```
### Python version
#### Spack (Linux)
Python version 3.6 or greater is required.
To install [Spack](https://spack.readthedocs.io/en/latest/index.html) follow [installation instructions](https://spack.readthedocs.io/en/latest/getting_started.html#installation).
```bash
# Install
$ spack install httpie
```
```bash
# Upgrade
$ spack install httpie
```
### FreeBSD
#### FreshPorts
```bash
# Install
$ pkg install www/py-httpie
```
```bash
# Upgrade
$ pkg upgrade www/py-httpie
```
<!-- /GENERATED SECTION -->
</div>
### Unstable version
You can also install the latest unreleased development version directly from the `master` branch on GitHub.
It is a work-in-progress of a future stable release so the experience might be not as smooth.
You can install it on Linux, macOS or Windows with `pip`:
You can install it on Linux, macOS, Windows, or FreeBSD with `pip`:
```bash
$ python -m pip install --upgrade https://github.com/httpie/httpie/archive/master.tar.gz
```
Or on macOS with Homebrew:
Or on macOS, and Linux, with Homebrew:
```bash
$ brew uninstall --force httpie
$ brew install --HEAD httpie
```
Verify that now you have the [current development version identifier](https://github.com/httpie/httpie/blob/master/httpie__init__.py#L6) with the `-dev` suffix, for example:
And even on macOS, and Linux, with Snapcraft:
```bash
$ snap remove httpie
$ 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:
```bash
$ http --version
# 2.5.0
# 2.6.0.dev0
```
## Usage
@ -235,7 +447,50 @@ Which looks similar to the actual `Request-Line` that is sent:
DELETE /delete HTTP/1.1
```
When the `METHOD` argument is omitted from the command, HTTPie defaults to either `GET` (with no request data) or `POST` (with request data).
In addition to the standard methods (`GET`, `POST`, `HEAD`, `PUT`, `PATCH`, `DELETE`, etc.), you can use custom method names, for example:
```bash
$ http AHOY pie.dev/post
```
There are no restrictions regarding which request methods can include a body. You can send an empty `POST` request:
```bash
$ http POST pie.dev/post
```
You can also make `GET` requests contaning a body:
```bash
$ http GET pie.dev/get hello=world
```
### Optional `GET` and `POST`
The `METHOD` argument is optional, and when you dont specify it, HTTPie defaults to:
- `GET` for requests without body
- `POST` for requests with body
Here we dont specify any request data, so both commands will send the same `GET` request:
```bash
$ http GET pie.dev/get
```
```bash
$ http pie.dev/get
```
Here, on the other hand, we do have some data, so both commands will make the same `POST` request:
```bash
$ http POST pie.dev/post hello=world
```
```bash
$ http pie.dev/post hello=world
```
## Request URL
@ -351,12 +606,12 @@ There are a few different *request item* types that provide a convenient mechani
They are key/value pairs specified after the URL. All have in common that they become part of the actual request that is sent and that their type is distinguished only by the separator used: `:`, `=`, `:=`, `==`, `@`, `=@`, and `:=@`. The ones with an `@` expect a file path as value.
| Item Type | Description |
| Item Type | Description |
| -----------------------------------------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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. |
| Data Fields `field=value`, `field=@file.txt` | 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) |
| 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. |
| Data Fields `field=value`, `field=@file.txt` | 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) |
| 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:
@ -411,10 +666,10 @@ Host: pie.dev
### Explicit JSON
You can use `--json, -j` to explicitly set `Accept` to `application/json` regardless of whether you are sending data (its a shortcut for setting the header via the usual header notation: `http url Accept:'application/json, */*;q=0.5'`).
You can use `--json, -j` to explicitly set `Accept` to `application/json` regardless of whether you are sending data (its a shortcut for setting the header via the usual header notation: `http url Accept:'application/json, */*;q=0.5'`).
Additionally, HTTPie will try to detect JSON responses even when the `Content-Type` is incorrectly `text/plain` or unknown.
### Non-string JSON fields
### Non-string JSON fields
Non-string JSON fields use the `:=` separator, which allows you to embed arbitrary JSON data into the resulting JSON object.
Additionally, text and raw JSON files can also be embedded into fields using `=@` and `:=@`:
@ -717,9 +972,9 @@ the [sessions](#sessions) feature.
$ http -a username pie.dev/basic-auth/username/password
```
### Empty password
### Empty password
```bash
```bash
$ http -a username: pie.dev/headers
```
@ -927,13 +1182,13 @@ By default, HTTPie only outputs the final response and the whole response
Print request and response headers:
```bash
$ http --print=Hh PUT pie.dev/put hello=world
$ http --print=Hh PUT pie.dev/put hello=world
```
### Verbose output
### Verbose output
`--verbose` can often be useful for debugging the request and generating documentation examples:
`--verbose` can often be useful for debugging the request and generating documentation examples:
```bash
$ http --verbose PUT pie.dev/put hello=world
PUT /put HTTP/1.1
@ -942,10 +1197,10 @@ It accepts a string of characters each of which represents a specific part of th
Content-Type: application/json
Host: pie.dev
User-Agent: HTTPie/0.2.7dev
{
"hello": "world"
}
{
"hello": "world"
}
HTTP/1.1 200 OK
Connection: keep-alive
@ -1158,32 +1413,35 @@ HTTPie does several things by default in order to make its terminal output easy
| `--pretty=format` | Apply formatting |
| `--pretty=none` | Disables output processing. Default for redirected output |
Formatting has the following effects:
- HTTP headers are sorted by name.
- JSON data is indented, sorted by keys, and unicode escapes are converted
to the characters they represent.
- XML and XHTML data is indented.
You can further control the applied formatting via the more granular [format options](#format-options).
You can further control the applied formatting via the more granular [format options](#format-options).
### Format options
### Format options
when formatting is applied. The following options are available:
The `--format-options=opt1:value,opt2:value` option allows you to control how the output should be formatted
when formatting is applied. The following options are available:
| ---------------: | :-----------: | ------------------------ |
| Option | Default value | Shortcuts |
| ---------------: | :-----------: | ------------------------ |
| `headers.sort` | `true` | `--sorted`, `--unsorted` |
| `json.format` | `true` | N/A |
| `json.indent` | `4` | N/A |
| `json.sort_keys` | `true` | `--sorted`, `--unsorted` |
| `xml.format` | `true` | N/A |
| `xml.indent` | `2` | N/A |
For example, this is how you would disable the default header and JSON key
sorting, and specify a custom JSON indent size:
```bash
$ http --format-options headers.sort:false,json.sort_keys:false,json.indent:2 pie.dev/get
| `xml.format` | `true` | N/A |
| `xml.indent` | `2` | N/A |
For example, this is how you would disable the default header and JSON key
sorting, and specify a custom JSON indent size:
```bash
$ http --format-options headers.sort:false,json.sort_keys:false,json.indent:2 pie.dev/get
```
```
There are also two shortcuts that allow you to quickly disable and re-enable
sorting-related format options (currently it means JSON keys and headers):
@ -1192,14 +1450,14 @@ You can further control the applied formatting via the more granular [format opt
This is something you will typically store as one of the default options in your [config](#config) file.
### Response `Content-Type`
The `--response-as=value` option allows you to override the response `Content-Type` sent by the server.
Binary data is also suppressed in redirected but prettified output.
The connection is closed as soon as we know that the response body is binary,
```bash
$ http pie.dev/bytes/2000
```
That makes it possible for HTTPie to print the response even when the server specifies the type incorrectly.
For example, the following request will force the response to be treated as XML:
```bash
$ http --response-as=application/xml pie.dev/get
```
And the following requests will force the response to use the [big5](https://docs.python.org/3/library/codecs.html#standard-encodings) encoding:
@ -1214,27 +1472,29 @@ sorting-related format options (currently it means JSON keys and headers):
Given the encoding is not sent by the server, HTTPie will auto-detect it.
- Formatting and colors arent applied (unless `--pretty` is specified).
### Redirected output
- Also, binary data isnt suppressed.
The reason is to make piping HTTPies output to another programs and downloading files work with no extra flags.
HTTPie uses a different set of defaults for redirected output than for [terminal output](#terminal-output).
The differences being:
- Formatting and colors arent applied (unless `--pretty` is specified).
- Only the response body is printed (unless one of the [output options](#output-options) is set).
- Also, binary data isnt suppressed.
Download a file:
The reason is to make piping HTTPies output to another programs and downloading files work with no extra flags.
Most of the time, only the raw response body is of an interest when the output is redirected.
$ http pie.dev/image/png > image.png
Download a file:
Download an image of an [Octocat](https://octodex.github.com/images/original.jpg), resize it using [ImageMagick](https://imagemagick.org/), and upload it elsewhere:
```bash
$ http octodex.github.com/images/original.jpg | convert - -resize 25% - | http example.org/Octocats
```
```bash
$ http pie.dev/image/png > image.png
```
Download an image of an [Octocat](https://octodex.github.com/images/original.jpg), resize it using [ImageMagick](https://imagemagick.org/), and upload it elsewhere:
```bash
$ http octodex.github.com/images/original.jpg | convert - -resize 25% - | http example.org/Octocats
```
Force colorizing and formatting, and show both the request and the response in `less` pager:
@ -1276,6 +1536,42 @@ function httpless {
TODO:
(both request/response)
- we look at content-type
- else we detect
- short texts default to utf8
(only response)
- --response-charset allows overwriting
- -->
## Download mode
HTTPie features a download mode in which it acts similarly to `wget`.
When enabled using the `--download, -d` flag, response headers are printed to the terminal (`stderr`), and a progress bar is shown while the response body is being saved to a file.
```bash
$ http --download https://github.com/httpie/httpie/archive/master.tar.gz
```
```http
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=httpie-master.tar.gz
Content-Length: 257336
Content-Type: application/x-gzip
Downloading 251.30 kB to "httpie-master.tar.gz"
Done. 251.30 kB in 2.73862s (91.76 kB/s)
```
### Downloaded filename
There are three mutually exclusive ways through which HTTPie determines
the output filename (with decreasing priority):
1. You can explicitly provide it via `--output, -o`. The file gets overwritten if it already exists (or appended to with `--continue, -c`).
2. The server may specify the filename in the optional `Content-Disposition` response header. Any leading dots are stripped from a server-provided filename.
3. The last resort HTTPie uses is to generate the filename from a combination of the request URL and the response `Content-Type`. The initial URL is always used as the basis for the generated filename — even if there has been one or more redirects.
@ -1463,40 +1759,40 @@ To set a cookie within a Session there are three options:
},
"cookies": {
"foo": {
"expires": null,
"path": "/",
"secure": false,
"expires": null,
"path": "/",
"secure": false,
"value": "bar"
}
}
}
```
}
```
Cookies will be set in the session file with the priority specified above.
For example, a cookie set through the command line will overwrite a cookie of the same name stored in the session file.
If the server returns a `Set-Cookie` header with a cookie of the same name, the returned cookie will overwrite the preexisting cookie.
Expired cookies are never stored.
If a cookie in a session file expires, it will be removed before sending a new request.
If the server expires an existing cookie, it will also be removed from the session file.
## Config
HTTPie uses a simple `config.json` file.
The file doesnt exist by default but you can create it manually.
### Config file directory
To see the exact location for your installation, run `http --debug` and look for `config_dir` in the output.
The default location of the configuration file on most platforms is `$XDG_CONFIG_HOME/httpie/config.json` (defaulting to `~/.config/httpie/config.json`).
For backwards compatibility, if the directory `~/.httpie` exists, the configuration file there will be used instead.
On Windows, the config file is located at `%APPDATA%\httpie\config.json`.
The config directory can be changed by setting the `$HTTPIE_CONFIG_DIR` environment variable:
Expired cookies are never stored.
If a cookie in a session file expires, it will be removed before sending a new request.
If the server expires an existing cookie, it will also be removed from the session file.
## Config
HTTPie uses a simple `config.json` file.
The file doesnt exist by default but you can create it manually.
### Config file directory
To see the exact location for your installation, run `http --debug` and look for `config_dir` in the output.
The default location of the configuration file on most platforms is `$XDG_CONFIG_HOME/httpie/config.json` (defaulting to `~/.config/httpie/config.json`).
For backwards compatibility, if the directory `~/.httpie` exists, the configuration file there will be used instead.
On Windows, the config file is located at `%APPDATA%\httpie\config.json`.
The config directory can be changed by setting the `$HTTPIE_CONFIG_DIR` environment variable:
```bash
$ export HTTPIE_CONFIG_DIR=/tmp/httpie
$ http pie.dev/get
@ -1632,8 +1928,8 @@ All changes are recorded in the [change log](#change-log).
- [CurliPie](https://curlipie.now.sh/) help convert cURL command line to HTTPie command line
#### Alternatives
- [httpcat](https://github.com/jakubroztocil/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.
### Contributing
@ -1659,7 +1955,7 @@ Helpers to convert from other client tools:
#### Alternatives
- [httpcat](https://github.com/jakubroztocil/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.
### Contributing

5
docs/config.json Normal file
View File

@ -0,0 +1,5 @@
{
"website": {
"master_and_released_docs_differ_after": "8f8851f1dbd511d3bc0ea0f6da7459045610afce"
}
}

View File

@ -0,0 +1,5 @@
Here we maintain a database of installation methods, from which we generate
the installation section in docs. If youd like add or update an installation method,
edit [methods.yml](./methods.yml), do not edit the main docs directly.
For HTTPie installation instructions see: <https://httpie.io/docs#installation>.

View File

@ -0,0 +1,85 @@
import re
import sys
from pathlib import Path
from typing import Dict
import yaml
from jinja2 import Template
Database = Dict[str, dict]
# Files
HERE = Path(__file__).parent
DB_FILE = HERE / 'methods.yml'
DOC_FILE = HERE.parent / 'README.md'
TPL_FILE = HERE / 'installation.jinja2'
# Database keys
KEY_DOC_STRUCTURE = 'docs-structure'
KEY_TOOLS = 'tools'
# Markers in-between content will be put.
MARKER_START = '<div data-installation-instructions>'
MARKER_END = '</div>'
def generate_documentation() -> str:
database = load_database()
structure = build_docs_structure(database)
template = Template(source=TPL_FILE.read_text(encoding='utf-8'))
output = template.render(structure=structure)
output = clean_template_output(output)
return output
def save_doc_file(content: str) -> None:
current_doc = load_doc_file()
marker_start = current_doc.find(MARKER_START) + len(MARKER_START)
assert marker_start > 0, 'cannot find the start marker'
marker_end = current_doc.find(MARKER_END, marker_start)
assert marker_start < marker_end, f'{marker_end=} < {marker_start=}'
updated_doc = (
current_doc[:marker_start]
+ '\n\n'
+ content
+ '\n\n'
+ current_doc[marker_end:]
)
if current_doc != updated_doc:
DOC_FILE.write_text(updated_doc, encoding='utf-8')
def build_docs_structure(database: Database):
tools = database[KEY_TOOLS]
assert len(tools) == len({tool['title'] for tool in tools.values()}), 'tool titles need to be unique'
tree = database[KEY_DOC_STRUCTURE]
structure = []
for platform, tools_ids in tree.items():
assert platform.isalnum(), f'{platform=} must be alpha-numeric for generated links to work'
platform_tools = [tools[tool_id] for tool_id in tools_ids]
structure.append((platform, platform_tools))
return structure
def clean_template_output(output):
output = '\n'.join(line.strip() for line in output.strip().splitlines())
output = re.sub('\n{3,}', '\n\n', output)
return output
def load_database() -> Database:
return yaml.safe_load(DB_FILE.read_text(encoding='utf-8'))
def load_doc_file() -> str:
return DOC_FILE.read_text(encoding='utf-8')
def main() -> int:
content = generate_documentation()
save_doc_file(content)
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -0,0 +1,37 @@
<!--
THE INSTALLATION SECTION IS GENERATED
Do not edit here, but in docs/installation/.
-->
{% for platform, tools in structure %}
- [{{ platform }}](#{{ platform.lower() }}){% endfor %} {# <= keep `endfor` here to prevent unwanted `\n` #}
{% for platform, tools in structure %}
### {{ platform }}
{% for tool in tools %}
#### {{ tool.title }}
{% if tool.note %}
{{ tool.note }}
{% endif %}
{% if tool.links.setup %}
To install [{{ tool.name }}]({{ tool.links.homepage }}) follow [installation instructions]({{ tool.links.setup }}).
{% endif %}
```bash
# Install
$ {{ tool.commands.install|join('\n$ ') }}
```
```bash
# Upgrade
$ {{ tool.commands.upgrade|join('\n$ ') }}
```
{% endfor %}
{% endfor %}
<!-- /GENERATED SECTION -->

View File

@ -0,0 +1,269 @@
# Database of HTTPie installation methods. Used to build the docs.
#
# We currently only include here methods for popular systems where we take care of the package,
# or have a good relationship with the maintainers.
#
# Each tool name should be unique (it becomes a linkable header).
# If a tools have `links.setup`, it also needs `links.homepage`.
# Some tools are available on multiple platforms, take into account when editing.
#
docs-structure:
Universal:
- pypi
macOS:
- brew-mac
- port
- snap-mac
- spack-mac
Windows:
- chocolatey
Linux:
- snap-linux
- brew-linux
- apt
- dnf
- yum
- apk
- emerge
- pacman
- xbps-install
- spack-linux
FreeBSD:
- pkg
tools:
apk:
title: Alpine Linux
name: apk
links:
homepage: https://wiki.alpinelinux.org/wiki/Alpine_Linux_package_management
package: https://pkgs.alpinelinux.org/package/edge/community/x86/httpie
commands:
install:
- apk update
- apk add httpie
upgrade:
- apk update
- apk add --upgrade httpie
apt:
title: Debian and Ubuntu
note: 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.
name: APT
links:
homepage: https://en.wikipedia.org/wiki/APT_(software)
package: https://packages.debian.org/sid/web/httpie
commands:
install:
- apt update
- apt install httpie
upgrade:
- apt update
- apt upgrade httpie
brew-mac:
title: Homebrew
name: Homebrew
links:
homepage: https://brew.sh/
setup: https://docs.brew.sh/Installation
package: https://formulae.brew.sh/formula/httpie
commands:
install:
- brew update
- brew install httpie
upgrade:
- brew update
- brew upgrade httpie
brew-linux:
title: Linuxbrew
name: Linuxbrew
links:
homepage: https://docs.brew.sh/Homebrew-on-Linux
setup: https://docs.brew.sh/Homebrew-on-Linux#install
package: https://formulae.brew.sh/formula/httpie
commands:
install:
- brew update
- brew install httpie
upgrade:
- brew update
- brew upgrade httpie
chocolatey:
title: Chocolatey
name: Chocolatey
links:
homepage: https://chocolatey.org/
setup: https://chocolatey.org/install
package: https://community.chocolatey.org/packages/httpie/
commands:
install:
- choco install httpie
upgrade:
- choco upgrade httpie
dnf:
title: Fedora
name: DNF
links:
homepage: https://fedoraproject.org/wiki/DNF
package: https://src.fedoraproject.org/rpms/httpie
commands:
install:
- dnf update
- dnf install httpie
upgrade:
- dnf update
- dnf upgrade httpie
emerge:
title: Gentoo
name: Portage
links:
homepage: https://wiki.gentoo.org/wiki/Portage
package: https://packages.gentoo.org/packages/net-misc/httpie
commands:
install:
- emerge --sync
- emerge httpie
upgrade:
- emerge --sync
- emerge --update httpie
pacman:
title: Arch Linux
name: pacman
note: Also works for other Arch-derived distributions like ArcoLinux, EndeavourOS, Artix Linux, etc.
links:
homepage: https://archlinux.org/pacman/
package: https://archlinux.org/packages/community/any/httpie/
commands:
install:
- pacman -Sy httpie
upgrade:
- pacman -Syu httpie
pkg:
title: FreshPorts
name: FreshPorts
links:
homepage: https://www.freebsd.org/cgi/man.cgi?query=pkg&sektion=8&n=1
package: https://www.freshports.org/www/py-httpie/
commands:
install:
- pkg install www/py-httpie
upgrade:
- pkg upgrade www/py-httpie
port:
title: MacPorts
name: MacPorts
links:
homepage: https://www.macports.org/
setup: https://www.macports.org/install.php
package: https://ports.macports.org/port/httpie/
commands:
install:
- port selfupdate
- port install httpie
upgrade:
- port selfupdate
- port upgrade httpie
pypi:
title: PyPi
name: pip
note: Please make sure you have Python 3.6 or newer (`python --version`).
links:
homepage: https://pypi.org/
# setup: https://pip.pypa.io/en/stable/installation/
package: https://pypi.org/project/httpie/
commands:
install:
- python -m pip install --upgrade pip wheel
- python -m pip install httpie
upgrade:
- python -m pip install --upgrade pip wheel
- python -m pip install --upgrade httpie
snap-linux:
title: Snapcraft (Linux)
name: Snapcraft
links:
homepage: https://snapcraft.io/
setup: https://snapcraft.io/docs/installing-snapd
package: https://snapcraft.io/httpie
commands:
install:
- snap install httpie
upgrade:
- snap refresh httpie
snap-mac:
title: Snapcraft (macOS)
name: Snapcraft
links:
homepage: https://snapcraft.io/
setup: https://snapcraft.io/docs/installing-snapd
package: https://snapcraft.io/httpie
commands:
install:
- snap install httpie
upgrade:
- snap refresh httpie
spack-linux:
title: Spack (Linux)
name: Spack
links:
homepage: https://spack.readthedocs.io/en/latest/index.html
setup: https://spack.readthedocs.io/en/latest/getting_started.html#installation
commands:
install:
- spack install httpie
upgrade:
- spack install httpie
spack-mac:
title: Spack (macOS)
name: Spack
links:
homepage: https://spack.readthedocs.io/en/latest/index.html
setup: https://spack.readthedocs.io/en/latest/getting_started.html#installation
commands:
install:
- spack install httpie
upgrade:
- spack install httpie
xbps-install:
title: Void Linux
name: XBPS
links:
homepage: https://docs.voidlinux.org/xbps/index.html
commands:
install:
- xbps-install -Su
- xbps-install -S httpie
upgrade:
- xbps-install -Su
- xbps-install -Su httpie
yum:
title: CentOS and RHEL
name: Yum
note: Also works for other RHEL-derived distributions like ClearOS, Oracle Linux, etc.
links:
homepage: http://yum.baseurl.org/
package: https://src.fedoraproject.org/rpms/httpie
commands:
install:
- yum update
- yum install epel-release
- yum install httpie
upgrade:
- yum update
- yum upgrade httpie

41
docs/markdownlint.rb Normal file
View File

@ -0,0 +1,41 @@
# Rules for <https://github.com/markdownlint/markdownlint>
# Load all rules by default
all
#
# Tweak rules
#
# MD002 First header should be a top level header
# Because we use HTML to hide them on the website.
exclude_rule 'MD002'
# MD013 Line length
exclude_rule 'MD013'
# MD014 Dollar signs used before commands without showing output
exclude_rule 'MD014'
# Tell the linter to use ordered lists:
# 1. Foo
# 2. Bar
# 3. Baz
#
# Instead of:
# 1. Foo
# 1. Bar
# 1. Baz
rule 'MD029', :style => :ordered
# MD033 Inline HTML
# TODO: Tweak elements when https://github.com/markdownlint/markdownlint/issues/118 will be done?
exclude_rule 'MD033'
# MD034 Bare URL used
# TODO: Remove when https://github.com/markdownlint/markdownlint/issues/328 will be fixed.
exclude_rule 'MD034'
# MD041 First line in file should be a top level header
# Because we use HTML to hide them on the website.
exclude_rule 'MD041'

49
docs/packaging/README.md Normal file
View File

@ -0,0 +1,49 @@
# 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 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 technical information about the HTTPie packaging, then you are at the good place.
## About
You are looking at the HTTPie packaging documentation, where you will find valuable information about how we manage to release HTTPie to lots of OSes, including technical data that may be worth reading if you are a package maintainer.
The overall release process starts simple:
1. Do the [PyPi](https://pypi.org/project/httpie/) publication.
2. Then, handle company-related tasks.
3. Finally, follow OS-specific steps, described in documents below, to send patches downstream.
## First, PyPi
Let's do the release on [PyPi](https://pypi.org/project/httpie/).
That is done quite easily by manually triggering the [release workflow](https://github.com/httpie/httpie/actions/workflows/release.yml).
## Then, company-specific tasks
- Update the HTTPie version bundled into termible ([example](https://github.com/httpie/termible/pull/1)).
## Finally, spread dowstream
Find out how we do release new versions for each and every supported OS in the following table.
A more complete state of deployment can be found on [repology](https://repology.org/project/httpie/versions), including unofficial packages.
| OS | Maintainer |
| -------------------------------------------: | -------------- |
| [Alpine](linux-alpine/) | **HTTPie** |
| [Arch Linux, and derived](linux-arch/) | trusted person |
| :construction: [AOSC OS](linux-aosc/) | **HTTPie** |
| [CentOS, RHEL, and derived](linux-centos/) | trusted person |
| [Debian, Ubuntu, and derived](linux-debian/) | trusted person |
| [Fedora](linux-fedora/) | trusted person |
| [Gentoo](linux-gentoo/) | **HTTPie** |
| :construction: [Homebrew, Linuxbrew](brew/) | **HTTPie** |
| :construction: [MacPorts](mac-ports/) | **HTTPie** |
| [Snapcraft](snapcraft/) | **HTTPie** |
| [Spack](spack/) | **HTTPie** |
| [Void Linux](linux-void/) | **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/).

View File

@ -0,0 +1,33 @@
# HTTPie on Homebrew, and Linuxbrew
Welcome to the documentation about **packaging HTTPie for Homebrew**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on Homebrew, then you can find them on [that page](https://httpie.io/docs#homebrew) ([that one](https://httpie.io/docs#linuxbrew) for Linuxbrew).
- If you are looking for technical information about the HTTPie packaging on Homebrew, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Homebrew. They apply to Linuxbrew as well.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
## Overall process
:construction: Work in progress.
First, update the current Formula:
```bash
make brew-deps
# Copy-paste content into downstream/mac/brew/httpie.rb
git add downstream/mac/brew/httpie.rb
git commit -s -m 'Update brew formula to XXX'
```
That [GitHub workflow](https://github.com/httpie/httpie/actions/workflows/test-package-mac-brew.yml) will test the formula when `downstream/mac/brew/httpie.rb` is changed in a pull request.
Then, open a pull request with those changes to the [downstream file]([ file](https://github.com/Homebrew/homebrew-core/blob/master/Formula/httpie.rb)).
## Hacking
:construction: Work in progress.

View File

@ -15,21 +15,22 @@ 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': '2.10',
'idna': '3.2',
}
# Note: Keep that list sorted.
PACKAGES = [
'certifi',
'charset-normalizer',
'defusedxml',
'httpie',
'idna',
'Pygments',
'PySocks',
'requests',
'requests-toolbelt',
'certifi',
'urllib3',
'idna',
'chardet',
'PySocks',
'defusedxml',
]

View File

@ -0,0 +1,74 @@
class Httpie < Formula
include Language::Python::Virtualenv
desc "User-friendly cURL replacement (command-line HTTP client)"
homepage "https://httpie.io/"
url "https://files.pythonhosted.org/packages/90/64/7ea8066309970f787653bdc8c5328272a5c4d06cbce3a07a6a5c3199c3d7/httpie-2.5.0.tar.gz"
sha256 "fe6a8bc50fb0635a84ebe1296a732e39357c3e1354541bf51a7057b4877e47f9"
license "BSD-3-Clause"
head "https://github.com/httpie/httpie.git"
bottle do
sha256 cellar: :any_skip_relocation, arm64_big_sur: "01115f69aff0399b3f73af09899a42a14343638a4624a35749059cc732c49cdc"
sha256 cellar: :any_skip_relocation, big_sur: "53f07157f00edf8193b7d4f74f247f53e1796fbc3e675cd2fbaa4b9dc2bad62c"
sha256 cellar: :any_skip_relocation, catalina: "7cf216fdee98208856d654060fdcad3968623d7ed27fcdeba27d3120354c9a9f"
sha256 cellar: :any_skip_relocation, mojave: "28adb5aed8c1c2b39c51789f242ff0dffde39073e161deb379c79184d787d063"
sha256 cellar: :any_skip_relocation, x86_64_linux: "91cb8c332c643bd8b1d0a8f3ec0acd4770b407991f6de1fd320d675f2b2e95ec"
end
depends_on "python@3.9"
resource "certifi" do
url "https://files.pythonhosted.org/packages/6d/78/f8db8d57f520a54f0b8a438319c342c61c22759d8f9a1cd2e2180b5e5ea9/certifi-2021.5.30.tar.gz"
sha256 "2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"
end
resource "charset-normalizer" do
url "https://files.pythonhosted.org/packages/e7/4e/2af0238001648ded297fb54ceb425ca26faa15b341b4fac5371d3938666e/charset-normalizer-2.0.4.tar.gz"
sha256 "f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"
end
resource "defusedxml" do
url "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz"
sha256 "1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"
end
resource "idna" do
url "https://files.pythonhosted.org/packages/cb/38/4c4d00ddfa48abe616d7e572e02a04273603db446975ab46bbcd36552005/idna-3.2.tar.gz"
sha256 "467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"
end
resource "Pygments" do
url "https://files.pythonhosted.org/packages/b7/b3/5cba26637fe43500d4568d0ee7b7362de1fb29c0e158d50b4b69e9a40422/Pygments-2.10.0.tar.gz"
sha256 "f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6"
end
resource "PySocks" do
url "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz"
sha256 "3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"
end
resource "requests" do
url "https://files.pythonhosted.org/packages/e7/01/3569e0b535fb2e4a6c384bdbed00c55b9d78b5084e0fb7f4d0bf523d7670/requests-2.26.0.tar.gz"
sha256 "b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
end
resource "requests-toolbelt" do
url "https://files.pythonhosted.org/packages/28/30/7bf7e5071081f761766d46820e52f4b16c8a08fef02d2eb4682ca7534310/requests-toolbelt-0.9.1.tar.gz"
sha256 "968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
end
resource "urllib3" do
url "https://files.pythonhosted.org/packages/4f/5a/597ef5911cb8919efe4d86206aa8b2658616d676a7088f0825ca08bd7cb8/urllib3-1.26.6.tar.gz"
sha256 "f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
end
def install
virtualenv_install_with_resources
end
test do
raw_url = "https://raw.githubusercontent.com/Homebrew/homebrew-core/HEAD/Formula/httpie.rb"
assert_match "PYTHONPATH", shell_output("#{bin}/http --ignore-stdin #{raw_url}")
end
end

View File

@ -0,0 +1,33 @@
# Contributor: Fabian Affolter <fabian@affolter-engineering.ch>
# Maintainer: Fabian Affolter <fabian@affolter-engineering.ch>
# Contributor: Daniel Isaksen <d@duniel.no>
# Contributor: Mickaël Schoentgen <mickael@apible.io>
pkgname=httpie
pkgver=2.5.0
pkgrel=0
pkgdesc="CLI, cURL-like tool"
url="https://httpie.org/"
arch="noarch"
license="BSD-3-Clause"
depends="python3 py3-setuptools py3-requests py3-pygments py3-requests-toolbelt py3-pysocks py3-defusedxml"
makedepends="py3-setuptools"
checkdepends="py3-pytest py3-pytest-httpbin py3-responses"
source="https://files.pythonhosted.org/packages/source/h/httpie/httpie-$pkgver.tar.gz"
# secfixes:
# 1.0.3-r0:
# - CVE-2019-10751
build() {
python3 setup.py build
}
check() {
python3 -m pytest ./httpie ./tests
}
package() {
python3 setup.py install --prefix=/usr --root="$pkgdir"
}
sha512sums="3bfe572b03bfde87d5a02f9ba238f9493b32e587c33fd30600a4dd6a45082eedcb2b507c7f1e3e75a423cbdcc1ff0556138897dffb7888d191834994eae9a2aa httpie-2.5.0.tar.gz"

View File

@ -0,0 +1,67 @@
# HTTPie on Alpine Linux
Welcome to the documentation about **packaging HTTPie for Alpine Linux**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on Alpine Linux, then you can find them on [that page](https://httpie.io/docs#alpine-linux).
- If you are looking for technical information about the HTTPie packaging on Alpine Linux, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Alpine Linux.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
## Overall process
Open a pull request to update the [downstream file](https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/community/httpie/APKBUILD) ([example](https://gitlab.alpinelinux.org/alpine/aports/-/merge_requests/25075)).
Notes:
- The `pkgrel` value must be set to `0`.
- The commit message must be `community/httpie: upgrade to XXX`.
- The commit must be signed-off (`git commit -s`).
## Hacking
Launch the docker image:
```bash
docker pull alpine
docker run -it --rm alpine
```
From inside the container:
```bash
# Install tools
apk add alpine-sdk sudo
# Add a user (password required)
adduser me
addgroup me abuild
echo "me ALL=(ALL) ALL" >> /etc/sudoers
# Switch user
su - me
# Create a private key (not used but required)
abuild-keygen -a -i
# Clone
git clone --depth=1 https://gitlab.alpinelinux.org/alpine/aports.git
cd aports/community/httpie
# Retrieve the patch of the latest HTTPie version
curl https://raw.githubusercontent.com/httpie/httpie/master/docs/packaging/linux-alpine/APKBUILD \
-o APKBUILD
# Build the package
abuild -r
# Install the package
sudo apk add --repository ~/packages/community httpie
# And test it!
http --version
https --version
```

View File

@ -0,0 +1,24 @@
# HTTPie on AOSC OS
Welcome to the documentation about **packaging HTTPie for AOSC OS**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for technical information about the HTTPie packaging on AOSC OS, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for AOSC OS.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
## Overall process
Open a pull request to update the [downstream file](https://github.com/AOSC-Dev/aosc-os-abbs/blob/stable/extra-web/httpie/spec) ([example](https://github.com/AOSC-Dev/aosc-os-abbs/commit/d0d3ba0bcea347387bb582a1b0b1b4e518720c80)).
Notes:
- The commit message must be `httpie: update to XXX`.
- The commit must be signed-off (`git commit -s`).
## Hacking
:construction: Work in progress.

View File

@ -0,0 +1,5 @@
VER=2.5.0
SRCS="tbl::https://github.com/httpie/httpie/archive/$VER.tar.gz"
CHKSUMS="sha256::66af56e0efc1ca6237323f1186ba34bca1be24e67a4319fd5df7228ab986faea"
REL=1
CHKUPDATE="anitya::id=1337"

View File

@ -0,0 +1,46 @@
# Maintainer: Jelle van der Waa <jelle@archlinux.org>
# Maintainer: daurnimator <daurnimator@archlinux.org>
# Contributor: Daniel Micay <danielmicay@gmail.com>
# Contributor: Thomas Weißschuh <thomas_weissschuh lavabit com>
pkgname=httpie
pkgver=2.5.0
pkgrel=1
pkgdesc="human-friendly CLI HTTP client for the API era"
url="https://github.com/httpie/httpie"
depends=('python-defusedxml'
'python-pygments'
'python-pysocks'
'python-requests'
'python-requests-toolbelt')
makedepends=('python-setuptools')
checkdepends=('python-pytest'
'python-pytest-httpbin'
'python-responses')
conflicts=(python-httpie)
replaces=(python-httpie python2-httpie)
license=('BSD')
arch=('any')
source=($pkgname-$pkgver.tar.gz::"https://github.com/httpie/httpie/archive/$pkgver.tar.gz")
sha256sums=('66af56e0efc1ca6237323f1186ba34bca1be24e67a4319fd5df7228ab986faea')
build() {
cd $pkgname-$pkgver
python3 setup.py build
}
package() {
cd $pkgname-$pkgver
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/httpie/LICENSE"
python3 setup.py install --root="$pkgdir" --optimize=1
# Fix upstream, include them in MANIFEST.in and use data_files in setup.py to install them automatically
# TODO: add zsh support
install -Dm644 extras/httpie-completion.bash "$pkgdir"/usr/share/bash-completion/completions/http
install -Dm644 extras/httpie-completion.fish "$pkgdir"/usr/share/fish/vendor_completions.d/http.fish
}
check() {
cd $pkgname-$pkgver
PYTHONDONTWRITEBYTECODE=1 python3 setup.py test
}

View File

@ -0,0 +1,22 @@
# HTTPie on Arch Linux, and derived
Welcome to the documentation about **packaging HTTPie for Arch Linux**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on Arch Linux, then you can find them on [that page](https://httpie.io/docs#arch-linux).
- If you are looking for technical information about the HTTPie packaging on Arch Linux, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Arch Linux. They apply to Arch-derived distributions as well, like ArcoLinux, EndeavourOS, Artix Linux, etc.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
## Overall process
Note: Sending patches downstream does not seem easy. We failed to find where is located the package file on <https://gitlab.archlinux.org>. So we are relying on the last maintainer, daurnimator, and it works pretty well so far.
Check <https://archlinux.org/packages/community/any/httpie/> and if the version is outdated, simply [report it](https://archlinux.org/packages/community/any/httpie/flag/).
## Hacking
Left blank on purpose, we will fill that section when we will have access to the downstream repository.

View File

@ -0,0 +1,26 @@
# HTTPie on CentOS, RHEL, and derived
Welcome to the documentation about **packaging HTTPie for CentOS and RHEL**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on CentOS, then you can find them on [that page](https://httpie.io/docs#centos-and-rhel).
- If you are looking for technical information about the HTTPie packaging on CentOS, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for CentOS. They apply to RHEL as well, and any RHEL-derived distributions like ClearOS, Oracle Linux, etc.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
The current maintainer is [Mikel Olasagasti](https://github.com/kaxero).
## Overall process
Same as [Fedora](../linux-fedora/README.md#overall-process).
## Q/A with Mikel
Q: What should we do to help seeing a new version on CentOS?
A: When a new release is published Miro and I get notified by [release-monitoring](https://release-monitoring.org/project/1337/), that fills a BugZilla ticket reporting a new version being available.
The system also tries to create a simple patch to update the spec file, but in the case of CentOS it needs some manual revision. For example for 2.5.0 `defuxedxml` dep is required. Maybe with CentOS-9 and some new macros that are available now in Fedora it can be automated same way. But even the bump can be automated, maintainers should check for license changes, new binaries/docs/ and so on.

View File

@ -0,0 +1,29 @@
# HTTPie on Debian, Ubuntu, and derived
Welcome to the documentation about **packaging HTTPie for Debian GNU/Linux**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on Debian GNU/Linux, then you can find them on [that page](https://httpie.io/docs#debian-and-ubuntu).
- If you are looking for technical information about the HTTPie packaging on Debian GNU/Linux, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Debian GNU/Linux. They apply to Ubuntu as well, and any Debian-derived distributions like MX Linux, Linux Mint, deepin, Pop!_OS, KDE neon, Zorin OS, elementary OS, Kubuntu, Devuan, Linux Lite, Peppermint OS, Lubuntu, antiX, Xubuntu, etc.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
The current maintainer is Bartosz Fenski.
## Overall process
Open a new bug on the Debian Bug Tracking System by sending an email:
- To: `Debian Bug Tracking System <submit@bugs.debian.org>`
- Subject: `httpie: Version XXX available`
- Message template ([example](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=993937)):
```email
Package: httpie
Severity: wishlist
<MESSAGE>
```

View File

@ -0,0 +1,48 @@
# HTTPie on Fedora
Welcome to the documentation about **packaging HTTPie for Fedora**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on Fedora, then you can find them on [that page](https://httpie.io/docs#fedora).
- If you are looking for technical information about the HTTPie packaging on Fedora, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Fedora.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
The current maintainer is [Miro Hrončok](https://github.com/hroncok).
## Overall process
We added the [.packit.yaml](https://github.com/httpie/httpie/blob/master/.packit.yaml) local file.
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.
It is also possible to follow [user feedbacks](https://bodhi.fedoraproject.org/updates/?packages=httpie) for all builds.
## Q/A with Miro
Q: What would the command to install the latest stable version look like?
A: Assuming the latest stable version is already propagated to Fedora:
```bash
# Note that yum is an alias to dnf.
$ sudo dnf install httpie
```
Q: Will dnf/yum upgrade then update to the latest?
A: Yes, assuming the same as above.
Q: Are new versions backported automatically?
A: No. The process is:
1. A new HTTPie release is created on Github.
2. A pull request for Fedora `rawhide` (the development version of Fedora, currently Fedora 35) is created.
3. A Fedora packager (usually Miro) sanity checks the pull request and merges, builds. HTTPie is updated in `rawhide` within 24 hours (sometimes more, for unrelated issues).
4. A Fedora packager decides whether the upgrade is suitable for stable Fedora releases (currently 34, 33), if so, merges the changes there.
5. (if the above is yes) The new version of HTTPie lands in `updates-testing` repo where it waits for user feedback and lands within ~1 week for broad availability.

View File

@ -0,0 +1,2 @@
DIST httpie-2.4.0.tar.gz 1772537 BLAKE2B 111451cc7dc353d5b586554f98ac715a3198f03e74d261944a5f021d2dcc948455500800222b323d182a2a067d0549bda7c318ab3a6c934b9a9beec64aff2db2 SHA512 44cc7ff4fe0f3d8c53a7dd750465f6b56c36f5bbac06d22b760579bd60949039e82313845699669a659ec91adc69dbeac22c06ddd63af64e6f2e0edecf3e732a
DIST httpie-2.5.0.tar.gz 1105177 BLAKE2B 6e16868c81522d4e6d2fc0a4e093c190f18ced720b35217930865ae3f8e168193cc33dfecc13c5d310f52647d6e79d17b247f56e56e8586d633a2d9502be66a7 SHA512 f14aa23fea7578181b9bd6ededea04de9ddf0b2f697b23f76d2d96e2c17b95617318c711750bad6af550400dbc03732ab17fdf84e59d577f33f073e600a55330

View File

@ -0,0 +1,78 @@
# HTTPie on Gentoo
Welcome to the documentation about **packaging HTTPie for Gentoo**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on Gentoo, then you can find them on [that page](https://httpie.io/docs#gentoo).
- If you are looking for technical information about the HTTPie packaging on Gentoo, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Gentoo.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
## Overall process
Open a pull request to create `httpie-XXX.ebuild` and update `Manifest`.
- Here is how to calculate the size and checksum (replace `2.5.0` with the correct version):
```bash
# Download
$ wget https://github.com/httpie/httpie/archive/2.5.0.tar.gz
# Size
$ stat --printf="%s\n" 2.5.0.tar.gz
1105177
# Checksum
$ openssl dgst -blake2b512 2.5.0.tar.gz
BLAKE2b512(2.5.0.tar.gz)= 6e16868c81522d4e6d2fc0a4e093c190f18ced720b35217930865ae3f8e168193cc33dfecc13c5d310f52647d6e79d17b247f56e56e8586d633a2d9502be66a7
```
- The commit message must be `net-misc/httpie: version bump to XXX`.
- The commit must be signed-off (`git commit -s`).
## Hacking
Launch the docker image:
```bash
docker pull gentoo/stage3
docker run -it --rm gentoo/stage3
```
From inside the container:
```bash
# Install tools
emerge --sync
emerge pkgcheck repoman
# Go to the package location
cd /var/db/repos/gentoo/net-misc/httpie
# Retrieve the patch of the latest HTTPie version
# (only files that were modified since the previous release)
curl https://raw.githubusercontent.com/httpie/httpie/master/docs/packaging/linux-gentoo/httpie-XXX.ebuild \
-o httpie-XXX.ebuild
curl https://raw.githubusercontent.com/httpie/httpie/master/docs/packaging/linux-gentoo/Manifest \
-o Manifest
curl https://raw.githubusercontent.com/httpie/httpie/master/docs/packaging/linux-gentoo/metadata.xml \
-o metadata.xml
# Basic checks
repoman manifest
repoman full -d -x
pkgcheck scan
# Build and install the package
emerge --with-test-deps httpie-XXX.ebuild
# Run the tests suite
ebuild httpie-XXX.ebuild clean test
# And test it!
http --version
https --version
```

View File

@ -0,0 +1,42 @@
# Copyright 1999-2021 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI=7
DISTUTILS_USE_SETUPTOOLS=rdepend
PYTHON_COMPAT=( python3_{8,9,10} )
PYTHON_REQ_USE="ssl(+)"
inherit bash-completion-r1 distutils-r1
DESCRIPTION="Modern command line HTTP client"
HOMEPAGE="https://httpie.io/ https://pypi.org/project/httpie/"
SRC_URI="https://github.com/httpie/httpie/archive/${PV}.tar.gz -> ${P}.tar.gz"
LICENSE="BSD"
SLOT="0"
KEYWORDS="~amd64 ~x86"
RDEPEND="
dev-python/defusedxml[${PYTHON_USEDEP}]
dev-python/pygments[${PYTHON_USEDEP}]
>=dev-python/requests-2.22.0[${PYTHON_USEDEP}]
>=dev-python/requests-toolbelt-0.9.1[${PYTHON_USEDEP}]
"
BDEPEND="
test? (
${RDEPEND}
dev-python/pyopenssl[${PYTHON_USEDEP}]
dev-python/pytest-httpbin[${PYTHON_USEDEP}]
dev-python/responses[${PYTHON_USEDEP}]
)
"
distutils_enable_tests pytest
python_install_all() {
newbashcomp extras/httpie-completion.bash http
insinto /usr/share/fish/vendor_completions.d
newins extras/httpie-completion.fish http.fish
distutils-r1_python_install_all
}

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE pkgmetadata SYSTEM "https://www.gentoo.org/dtd/metadata.dtd">
<pkgmetadata>
<maintainer type="person" proxied="yes">
<email>mickael@apible.io</email>
<name>Mickaël Schoentgen</name>
</maintainer>
<maintainer type="project" proxied="proxy">
<email>proxy-maint@gentoo.org</email>
<name>Proxy Maintainers</name>
</maintainer>
<longdescription lang="en">
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. It provides a simple http command
that allows for sending arbitrary HTTP requests using a simple
and natural syntax, and displays colorized output. HTTPie can be
used for testing, debugging, and generally interacting with HTTP
servers.
</longdescription>
<upstream>
<bugs-to>https://github.com/httpie/httpie/issues</bugs-to>
<changelog>https://raw.githubusercontent.com/httpie/httpie/master/CHANGELOG.md</changelog>
<doc>https://httpie.io/docs</doc>
<remote-id type="github">httpie/httpie</remote-id>
<remote-id type="pypi">httpie</remote-id>
</upstream>
</pkgmetadata>

View File

@ -0,0 +1,68 @@
# HTTPie on Void Linux
Welcome to the documentation about **packaging HTTPie for Void Linux**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on Void Linux, then you can find them on [that page](https://httpie.io/docs#void-linux).
- If you are looking for technical information about the HTTPie packaging on Void Linux, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Void Linux.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
## Overall process
Open a pull request to update the [downstream file](https://github.com/void-linux/void-packages/blob/master/srcpkgs/httpie/template) ([example](https://github.com/void-linux/void-packages/pull/32905)).
- The commit message must be `httpie: update to XXX.`.
- The commit must be signed-off (`git commit -s`).
## Hacking
Launch the docker image:
```bash
docker pull voidlinux/voidlinux
docker run -it --rm voidlinux/voidlinux
```
From inside the container:
```bash
# Sync and upgrade once, assume error comes from xbps update
xbps-install -Syu
# Install tools
xbps-install -y git xtools file util-linux binutils bsdtar coreutils
# Clone
git clone --depth=1 git://github.com/void-linux/void-packages.git void-packages-src
cd void-packages-src
# Retrieve the patch of the latest HTTPie version
curl https://raw.githubusercontent.com/httpie/httpie/master/docs/packaging/linux-void/template \
-o srcpkgs/httpie/template
# Check the package
xlint srcpkgs/httpie/template
# Link / to /masterdir
ln -s / masterdir
# Enable ethereal chroot-style
export XBPS_ALLOW_CHROOT_BREAKOUT=yes
./xbps-src binary-bootstrap
./xbps-src chroot
# Build the package
cd void-packages
export SOURCE_DATE_EPOCH=0
./xbps-src pkg httpie
# Install the package
xbps-install --repository=hostdir/binpkgs httpie
# And finally test it!
http --version
https --version
```

View File

@ -0,0 +1,28 @@
# Template file for 'httpie'
pkgname=httpie
version=2.5.0
revision=1
build_style=python3-module
hostmakedepends="python3-setuptools"
depends="python3-setuptools python3-requests python3-requests-toolbelt
python3-Pygments python3-pysocks python3-defusedxml"
short_desc="Human-friendly command line HTTP client"
maintainer="Mickaël Schoentgen <mickael@apible.io>"
license="BSD-3-Clause"
homepage="https://httpie.io/"
changelog="https://raw.githubusercontent.com/httpie/httpie/${version}/CHANGELOG.md"
distfiles="https://github.com/httpie/httpie/archive/${version}.tar.gz"
checksum=66af56e0efc1ca6237323f1186ba34bca1be24e67a4319fd5df7228ab986faea
make_check=no # needs pytest_httpbin which is not packaged
post_install() {
vcompletion extras/httpie-completion.bash bash http
vcompletion extras/httpie-completion.fish fish http
vlicense LICENSE
}
python3-httpie_package() {
build_style=meta
short_desc+=" (transitional dummy package)"
depends="httpie>=${version}_${revision}"
}

View File

@ -0,0 +1,49 @@
# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8::et:sw=4:ts=4:sts=4
PortSystem 1.0
PortGroup github 1.0
PortGroup python 1.0
github.setup httpie httpie 2.5.0
maintainers {g5pw @g5pw} openmaintainer
categories net
description HTTPie is a command line HTTP client, a user-friendly cURL replacement.
long_description HTTPie (pronounced aych-tee-tee-pie) is a command line HTTP \
client. Its goal is to make CLI interaction with web \
services as human-friendly as possible. It provides a simple \
http command that allows for sending arbitrary HTTP requests \
using a simple and natural syntax, and displays colorized \
responses. HTTPie can be used for testing, debugging, and \
generally interacting with HTTP servers.
platforms darwin
license BSD
homepage https://httpie.io/
variant python36 conflicts python37 python38 python39 description "Use Python 3.6" {}
variant python37 conflicts python36 python38 python39 description "Use Python 3.7" {}
variant python38 conflicts python36 python37 python39 description "Use Python 3.8" {}
variant python39 conflicts python36 python37 python38 description "Use Python 3.9" {}
if {[variant_isset python36]} {
python.default_version 36
} elseif {[variant_isset python37]} {
python.default_version 37
} elseif {[variant_isset python39]} {
python.default_version 39
} else {
default_variants +python38
python.default_version 38
}
depends_lib-append port:py${python.version}-requests \
port:py${python.version}-requests-toolbelt \
port:py${python.version}-pygments \
port:py${python.version}-socks \
port:py${python.version}-defusedxml
checksums rmd160 88d227d52199c232c0ddf704a219d1781b1e77ee \
sha256 00c4b7bbe7f65abe1473f37b39d9d9f8f53f44069a430ad143a404c01c2179fc \
size 1105185
python.link_binaries_suffix

View File

@ -0,0 +1,40 @@
# HTTPie on MacPorts
Welcome to the documentation about **packaging HTTPie for MacPorts**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on MacPorts, then you can find them on [that page](https://httpie.io/docs#macports).
- If you are looking for technical information about the HTTPie packaging on MacPorts, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for MacPorts.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
## Overall process
Open a pull request to update the [downstream file](https://github.com/macports/macports-ports/blob/master/net/httpie/Portfile) ([example](https://github.com/macports/macports-ports/pull/12167)).
- Here is how to calculate the size and checksums (replace `2.5.0` with the correct version):
```bash
# Download the archive
$ wget https://api.github.com/repos/httpie/httpie/tarball/2.5.0
# Size
$ stat --printf="%s\n" 2.5.0
1105185
# Checksums
$ openssl dgst -rmd160 2.5.0
RIPEMD160(2.5.0)= 88d227d52199c232c0ddf704a219d1781b1e77ee
$ openssl dgst -sha256 2.5.0
SHA256(2.5.0)= 00c4b7bbe7f65abe1473f37b39d9d9f8f53f44069a430ad143a404c01c2179fc
```
- The commit message must be `httpie: update to XXX`.
- The commit must be signed-off (`git commit -s`).
## Hacking
:construction: Work in progress.

View File

@ -0,0 +1,51 @@
# HTTPie on Snapcraft
Welcome to the documentation about **packaging HTTPie for Snapcraft**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on Snapcraft, then you can find them on [that page](https://httpie.io/docs#snapcraft-linux) ([that one](https://httpie.io/docs#snapcraft-macos) for macOS).
- If you are looking for technical information about the HTTPie packaging on Snapcraft, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Snapcraft. They apply to Snapcraft on Linux, macOS, and Windows.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
## 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/).
## Hacking
Launch the docker image:
```bash
docker pull ubuntu/latest
docker run -it --rm ubuntu/latest
```
From inside the container:
```bash
# Clone
git clone --depth=1 https://github.com/httpie/httpie.git
cd httpie
# Build
export SNAPCRAFT_BUILD_ENVIRONMENT_CPU=8
export SNAPCRAFT_BUILD_ENVIRONMENT_MEMORY=16G
snapcraft --debug
# Install
sudo snap install --dangerous httpie_XXX_amd64.snap
# Test
httpie.http --version
httpie.https --version
# Auto-aliases cannot be tested when installing a snap outside the store.
# http --version
# https --version
# Remove
sudo snap remove httpie
```

View File

@ -0,0 +1,54 @@
# HTTPie on Spack
Welcome to the documentation about **packaging HTTPie for Spack**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on Spack, then you can find them on [that page](https://httpie.io/docs#spack-linux) ([that one](https://httpie.io/docs#spack-macos) for macOS).
- If you are looking for technical information about the HTTPie packaging on Spack, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Spack. They apply to Spack on Linux, and macOS.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
## Overall process
Open a pull request to update the [downstream file](https://github.com/spack/spack/blob/develop/var/spack/repos/builtin/packages/httpie/package.py) ([example](https://github.com/spack/spack/pull/25888)).
- The commit message must be `httpie: add vXXX`.
- The commit must be signed-off (`git commit -s`).
## Hacking
Launch the docker image:
```bash
docker pull spack/centos7
docker run -it --rm spack/centos7
```
From inside the container:
```bash
# Clone
git clone --depth=1 https://github.com/spack/spack.git
cd spack
# Retrieve the patch of the latest HTTPie version
curl https://raw.githubusercontent.com/httpie/httpie/master/docs/packaging/spack/package.py \
-o var/spack/repos/builtin/packages/httpie/package.py
# Check the package
spack spec httpie
# Check available versions (it should show the new version)
spack versions httpie
# Install the package
spack install httpie@XXX
spack load httpie
# And test it!
http --version
https --version
```

View File

@ -0,0 +1,32 @@
# Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
from spack import *
class Httpie(PythonPackage):
"""Modern command line HTTP client."""
homepage = "https://httpie.io/"
pypi = "httpie/httpie-2.5.0.tar.gz"
version('2.5.0', sha256='fe6a8bc50fb0635a84ebe1296a732e39357c3e1354541bf51a7057b4877e47f9')
version('0.9.9', sha256='f1202e6fa60367e2265284a53f35bfa5917119592c2ab08277efc7fffd744fcb')
version('0.9.8', sha256='515870b15231530f56fe2164190581748e8799b66ef0fe36ec9da3396f0df6e1')
variant('socks', default=True,
description='Enable SOCKS proxy support')
depends_on('py-setuptools', type=('build', 'run'))
depends_on('py-defusedxml', type=('build', 'run'))
depends_on('py-pygments', type=('build', 'run'))
depends_on('py-requests', type=('build', 'run'))
depends_on('py-requests-toolbelt', type=('build', 'run'))
depends_on('py-pysocks', type=('build', 'run'), when="+socks")
# Concretization problem breaks this. Unconditional for now...
# https://github.com/spack/spack/issues/3628
# depends_on('py-argparse@1.2.1:', type=('build', 'run'),
# when='^python@:2.6,3.0:3.1')
depends_on('py-argparse@1.2.1:', type=('build', 'run'), when='^python@:2.6')

View File

@ -0,0 +1,45 @@
# HTTPie on Chocolatey
Welcome to the documentation about **packaging HTTPie for Chocolatey**.
- If you do not know HTTPie, have a look [here](https://httpie.io/cli).
- If you are looking for HTTPie installation or upgrade instructions on Chocolatey, then you can find them on [that page](https://httpie.io/docs#chocolatey).
- If you are looking for technical information about the HTTPie packaging on Chocolatey, then you are in a good place.
## About
This document contains technical details, where we describe how to create a patch for the latest HTTPie version for Chocolatey.
We will discuss setting up the environment, installing development tools, installing and testing changes before submitting a patch downstream.
## Overall process
After having successfully [built and tested](#hacking) the package, push it:
```bash
# Replace 2.5.0 with the correct version
choco push httpie.2.5.0.nupkg -s https://push.chocolatey.org/ --api-key=API_KEY
```
## Hacking
```bash
# Clone
git clone --depth=1 https://github.com/httpie/httpie.git
cd httpie/docs/packaging/windows-chocolatey
# Build
choco pack
# Check metadata
choco info httpie -s .
# Install
choco install httpie -y -dv -s "'.;https://community.chocolatey.org/api/v2/'"
# Test
http --version
https --version
# Remove
choco uninstall -y httpie
```

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>httpie</id>
<version>2.5.0</version>
<summary>Modern, user-friendly command-line HTTP client for the API era.</summary>
<description>
HTTPie *aitch-tee-tee-pie* is a user-friendly command-line HTTP client for the API era.
It comes with JSON support, syntax highlighting, persistent sessions, wget-like downloads, plugins, and more.
The project's goal is to make CLI interaction with web services as human-friendly as possible. HTTPie is designed for testing, debugging, and generally interacting with APIs and HTTP servers.
The `http` and `https` commands allow for creating and sending arbitrary HTTP requests. They use simple and natural syntax and provide formatted and colorized output.
Main features:
- Built-in JSON support
- Colorized and formatted terminal output
- Sensible defaults for the API era
- Persistent sessions
- Forms and file uploads
- HTTPS, proxies, and authentication support
- Support for arbitrary request data and headers
- Wget-like downloads
- Extensions API
- Expressive and intuitive syntax
- Linux, macOS, Windows, and FreeBSD support
- All that and more in 2 simple commands: `http` + `https`
</description>
<title>HTTPie</title>
<authors>HTTPie</authors>
<owners>Tiger-222</owners>
<copyright>2012-2021 Jakub Roztocil</copyright>
<licenseUrl>https://raw.githubusercontent.com/httpie/httpie/master/LICENSE</licenseUrl>
<iconUrl>https://pie-assets.s3.eu-central-1.amazonaws.com/LogoIcons/GB.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<releaseNotes>See the [changelog](https://github.com/httpie/httpie/blob/2.5.0/CHANGELOG.md).</releaseNotes>
<tags>httpie http https rest api client curl python ssl cli foss oss url</tags>
<projectUrl>https://httpie.io</projectUrl>
<packageSourceUrl>https://github.com/httpie/httpie</packageSourceUrl>
<projectSourceUrl>https://github.com/httpie/httpie</projectSourceUrl>
<docsUrl>https://httpie.io/docs</docsUrl>
<bugTrackerUrl>https://github.com/httpie/httpie/issues</bugTrackerUrl>
<dependencies>
<dependency id="python3" version="3.6" />
</dependencies>
</metadata>
<files>
<file src="tools\**" target="tools" />
</files>
</package>

View File

@ -0,0 +1,6 @@
$ErrorActionPreference = 'Stop';
$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
$nuspecPath = "$(Join-Path (Split-Path -parent $toolsDir) ($env:ChocolateyPackageName + ".nuspec"))"
[XML]$nuspec = Get-Content $nuspecPath
$pipVersion = $nuspec.package.metadata.version
py -m pip install "$($env:ChocolateyPackageName)==$($pipVersion)" --disable-pip-version-check

View File

@ -0,0 +1,2 @@
$ErrorActionPreference = 'Stop';
py -m pip uninstall -y $env:ChocolateyPackageName --disable-pip-version-check

View File

@ -1,76 +0,0 @@
# The latest Homebrew formula as submitted to Homebrew/homebrew-core.
# Only useful for testing until it gets accepted by homebrew maintainers.
# (It will need to be updated from the repo version before next release.)
#
# https://github.com/Homebrew/homebrew-core/blob/master/Formula/httpie.rb
#
class Httpie < Formula
include Language::Python::Virtualenv
desc "User-friendly cURL replacement (command-line HTTP client)"
homepage "https://httpie.io/"
url "https://files.pythonhosted.org/packages/17/3a/90fb6702e600f5ba7d38d147bbc0b0a1e47159e3e244737319c98c140420/httpie-2.4.0.tar.gz"
sha256 "4d1bf5779cf6c9007351cfcaa20bd19947267dc026af09246db6006a8927d8c6"
license "BSD-3-Clause"
head "https://github.com/httpie/httpie.git"
bottle do
rebuild 1
sha256 cellar: :any_skip_relocation, arm64_big_sur: "a01ce8767f6ea88eb8e7894347ba64eb29294053a8ee91eed44dfaf0ab5e7ea2"
sha256 cellar: :any_skip_relocation, big_sur: "bdffeff349595ed3c528ed791d568e308b0877246b49e05e867143ba3415a70f"
sha256 cellar: :any_skip_relocation, catalina: "ba0627d70f0ee49c64677f5554881ebd56371f47d45196b6564680089ce69152"
sha256 cellar: :any_skip_relocation, mojave: "0b87901e88bdcf53c55c5138677087b4621c5aaf1fca67b53b730d5a2fd5a40a"
sha256 cellar: :any_skip_relocation, high_sierra: "87e7348b6fb40fd8e4f7597937952469601962189e62d321b8cb4fa421e035ef"
end
depends_on "python@3.9"
resource "Pygments" do
url "https://files.pythonhosted.org/packages/e1/86/8059180e8217299079d8719c6e23d674aadaba0b1939e25e0cc15dcf075b/Pygments-2.7.4.tar.gz"
sha256 "df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337"
end
resource "requests" do
url "https://files.pythonhosted.org/packages/6b/47/c14abc08432ab22dc18b9892252efaf005ab44066de871e72a38d6af464b/requests-2.25.1.tar.gz"
sha256 "27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"
end
resource "requests-toolbelt" do
url "https://files.pythonhosted.org/packages/28/30/7bf7e5071081f761766d46820e52f4b16c8a08fef02d2eb4682ca7534310/requests-toolbelt-0.9.1.tar.gz"
sha256 "968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"
end
resource "certifi" do
url "https://files.pythonhosted.org/packages/06/a9/cd1fd8ee13f73a4d4f491ee219deeeae20afefa914dfb4c130cfc9dc397a/certifi-2020.12.5.tar.gz"
sha256 "1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"
end
resource "urllib3" do
url "https://files.pythonhosted.org/packages/d7/8d/7ee68c6b48e1ec8d41198f694ecdc15f7596356f2ff8e6b1420300cf5db3/urllib3-1.26.3.tar.gz"
sha256 "de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
end
resource "idna" do
url "https://files.pythonhosted.org/packages/ea/b7/e0e3c1c467636186c39925827be42f16fee389dc404ac29e930e9136be70/idna-2.10.tar.gz"
sha256 "b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"
end
resource "chardet" do
url "https://files.pythonhosted.org/packages/ee/2d/9cdc2b527e127b4c9db64b86647d567985940ac3698eeabc7ffaccb4ea61/chardet-4.0.0.tar.gz"
sha256 "0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"
end
resource "PySocks" do
url "https://files.pythonhosted.org/packages/bd/11/293dd436aea955d45fc4e8a35b6ae7270f5b8e00b53cf6c024c83b657a11/PySocks-1.7.1.tar.gz"
sha256 "3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"
end
def install
virtualenv_install_with_resources
end
test do
raw_url = "https://raw.githubusercontent.com/Homebrew/homebrew-core/HEAD/Formula/httpie.rb"
assert_match "PYTHONPATH", shell_output("#{bin}/http --ignore-stdin #{raw_url}")
end
end

View File

@ -3,6 +3,6 @@ HTTPie: command-line HTTP client for the API era.
"""
__version__ = '2.5.0'
__version__ = '2.6.0.dev0'
__author__ = 'Jakub Roztocil'
__licence__ = 'BSD'

View File

@ -75,6 +75,8 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
) -> argparse.Namespace:
self.env = env
self.args, no_options = super().parse_known_args(args, namespace)
if self.args.prompt:
return self.args
if self.args.debug:
self.args.traceback = True
self.has_stdin_data = (
@ -311,7 +313,7 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
self.error('Request body (from stdin, --raw or a file) and request '
'data (key=value) cannot be mixed. Pass '
'--ignore-stdin to let key/value take priority. '
'See https://httpie.org/doc#scripting for details.')
'See https://httpie.io/docs#scripting for details.')
def _guess_method(self):
"""Set `args.method` if not specified to either POST or GET
@ -457,7 +459,8 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
self.error('--continue requires --output to be specified')
def _process_format_options(self):
format_options = self.args.format_options or []
parsed_options = PARSED_DEFAULT_FORMAT_OPTIONS
for options_group in self.args.format_options or []:
for options_group in format_options:
parsed_options = parse_format_options(options_group, defaults=parsed_options)
self.args.format_options = parsed_options

View File

@ -242,3 +242,19 @@ PARSED_DEFAULT_FORMAT_OPTIONS = parse_format_options(
s=','.join(DEFAULT_FORMAT_OPTIONS),
defaults=None,
)
def response_charset_type(encoding: str) -> str:
try:
''.encode(encoding)
except LookupError:
raise argparse.ArgumentTypeError(
f'{encoding!r} is not a supported encoding')
return encoding
def response_mime_type(mime_type: str) -> str:
if mime_type.count('/') != 1:
raise argparse.ArgumentTypeError(
f'{mime_type!r} doesnt look like a mime type; use type/subtype')
return mime_type

View File

@ -2,14 +2,14 @@
CLI arguments definition.
"""
from argparse import (FileType, OPTIONAL, SUPPRESS, ZERO_OR_MORE)
from argparse import FileType, OPTIONAL, SUPPRESS, ZERO_OR_MORE
from textwrap import dedent, wrap
from .. import __doc__, __version__
from .argparser import HTTPieArgumentParser
from .argtypes import (
KeyValueArgType, SessionNameValidator,
readable_file_arg,
readable_file_arg, response_charset_type, response_mime_type,
)
from .constants import (
DEFAULT_FORMAT_OPTIONS, OUTPUT_OPTIONS,
@ -30,7 +30,7 @@ from ..ssl import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS
parser = HTTPieArgumentParser(
prog='http',
description=f'{__doc__.strip()} <https://httpie.org>',
description=f'{__doc__.strip()} <https://httpie.io>',
epilog=dedent('''
For every --OPTION there is also a --no-OPTION that reverts OPTION
to its default value.
@ -73,6 +73,7 @@ positional.add_argument(
positional.add_argument(
dest='url',
metavar='URL',
nargs=OPTIONAL,
help='''
The scheme defaults to 'http://' if the URL does not include one.
(You can override this with: --default-scheme=https)
@ -96,7 +97,7 @@ positional.add_argument(
':' HTTP headers:
Referer:http://httpie.org Cookie:foo=bar User-Agent:bacon/1.0
Referer:https://httpie.io Cookie:foo=bar User-Agent:bacon/1.0
'==' URL parameters to be appended to the request URI:
@ -252,7 +253,7 @@ output_processing.add_argument(
dest='style',
metavar='STYLE',
default=DEFAULT_STYLE,
choices=AVAILABLE_STYLES,
choices=sorted(AVAILABLE_STYLES),
help='''
Output coloring style (default is "{default}"). It can be One of:
@ -309,6 +310,31 @@ output_processing.add_argument(
'''
)
output_processing.add_argument(
'--response-charset',
metavar='ENCODING',
type=response_charset_type,
help='''
Override the response encoding for terminal display purposes, e.g.:
--response-charset=utf8
--response-charset=big5
'''
)
output_processing.add_argument(
'--response-mime',
metavar='MIME_TYPE',
type=response_mime_type,
help='''
Override the response mime type for coloring and formatting for the terminal, e.g.:
--response-mime=application/json
--response-mime=text/xml
'''
)
output_processing.add_argument(
'--format-options',
@ -686,9 +712,11 @@ network.add_argument(
'--chunked',
default=False,
action='store_true',
help="""
help='''
Enable streaming via chunked transfer encoding.
The Transfer-Encoding header is set to chunked.
"""
'''
)
#######################################################################
@ -813,3 +841,12 @@ troubleshooting.add_argument(
'''
)
troubleshooting.add_argument(
'--prompt',
action='store_true',
default=False,
help='''
Start the shell!
'''
)

View File

@ -15,7 +15,7 @@ from .dicts import (
RequestQueryParamsDict,
)
from .exceptions import ParseError
from ..utils import get_content_type, load_json_preserve_order
from ..utils import get_content_type, load_json_preserve_order_and_dupe_keys
class RequestItems:
@ -150,6 +150,6 @@ def load_text_file(item: KeyValueArg) -> str:
def load_json(arg: KeyValueArg, contents: str) -> JSONType:
try:
return load_json_preserve_order(contents)
return load_json_preserve_order_and_dupe_keys(contents)
except ValueError as e:
raise ParseError(f'{arg.orig!r}: {e}')

View File

@ -12,7 +12,7 @@ import requests
import urllib3
from . import __version__
from .cli.dicts import RequestHeadersDict
from .constants import UTF8
from .encoding import UTF8
from .plugins.registry import plugin_manager
from .sessions import get_httpie_session
from .ssl import AVAILABLE_SSL_VERSION_ARG_MAPPING, HTTPieHTTPSAdapter

View File

@ -2,3 +2,53 @@ import sys
is_windows = 'win32' in str(sys.platform).lower()
try:
from functools import cached_property
except ImportError:
# Can be removed once we drop Python <3.8 support.
# Taken from `django.utils.functional.cached_property`.
class cached_property:
"""
Decorator that converts a method with a single self argument into a
property cached on the instance.
A cached property can be made out of an existing method:
(e.g. ``url = cached_property(get_absolute_url)``).
The optional ``name`` argument is obsolete as of Python 3.6 and will be
deprecated in Django 4.0 (#30127).
"""
name = None
@staticmethod
def func(instance):
raise TypeError(
'Cannot use cached_property instance without calling '
'__set_name__() on it.'
)
def __init__(self, func, name=None):
self.real_func = func
self.__doc__ = getattr(func, '__doc__')
def __set_name__(self, owner, name):
if self.name is None:
self.name = name
self.func = self.real_func
elif name != self.name:
raise TypeError(
"Cannot assign the same cached_property to two different names "
"(%r and %r)." % (self.name, name)
)
def __get__(self, instance, cls=None):
"""
Call the function and put the return value in instance.__dict__ so that
subsequent attribute access on the instance returns the cached value
instead of calling cached_property.__get__().
"""
if instance is None:
return self
res = instance.__dict__[self.name] = self.func(instance)
return res

View File

@ -5,7 +5,7 @@ from typing import Union
from . import __version__
from .compat import is_windows
from .constants import UTF8
from .encoding import UTF8
ENV_XDG_CONFIG_HOME = 'XDG_CONFIG_HOME'

View File

@ -1,2 +0,0 @@
# UTF-8 encoding name
UTF8 = 'utf-8'

View File

@ -11,7 +11,7 @@ except ImportError:
from .compat import is_windows
from .config import DEFAULT_CONFIG_DIR, Config, ConfigFileError
from .constants import UTF8
from .encoding import UTF8
from .utils import repr_dict

View File

@ -29,6 +29,10 @@ def main(args: List[Union[str, bytes]] = sys.argv, env=Environment()) -> ExitSta
Return exit status code.
"""
if '--prompt' in args:
from .prompt.cli import cli
return cli(sys.argv[2:])
program_name, *args = args
env.program_name = os.path.basename(program_name)
args = decode_raw_args(args, env.stdin_encoding)
@ -227,6 +231,8 @@ def print_debug_info(env: Environment):
])
env.stderr.write('\n\n')
env.stderr.write(repr(env))
env.stderr.write('\n\n')
env.stderr.write(repr(plugin_manager))
env.stderr.write('\n')

50
httpie/encoding.py Normal file
View File

@ -0,0 +1,50 @@
from typing import Union
from charset_normalizer import from_bytes
from charset_normalizer.constant import TOO_SMALL_SEQUENCE
UTF8 = 'utf-8'
ContentBytes = Union[bytearray, bytes]
def detect_encoding(content: ContentBytes) -> str:
"""
We default to UTF-8 if text too short, because the detection
can return a random encoding leading to confusing results
given the `charset_normalizer` version (< 2.0.5).
>>> too_short = ']"foo"'
>>> detected = from_bytes(too_short.encode()).best().encoding
>>> detected
'ascii'
>>> too_short.encode().decode(detected)
']"foo"'
"""
encoding = UTF8
if len(content) > TOO_SMALL_SEQUENCE:
match = from_bytes(bytes(content)).best()
if match:
encoding = match.encoding
return encoding
def smart_decode(content: ContentBytes, encoding: str) -> str:
"""Decode `content` using the given `encoding`.
If no `encoding` is provided, the best effort is to guess it from `content`.
Unicode errors are replaced.
"""
if not encoding:
encoding = detect_encoding(content)
return content.decode(encoding, 'replace')
def smart_encode(content: str, encoding: str) -> bytes:
"""Encode `content` using the given `encoding`.
Unicode errors are replaced.
"""
return content.encode(encoding, 'replace')

View File

@ -1,39 +1,33 @@
from abc import ABCMeta, abstractmethod
from typing import Iterable, Optional
from typing import Iterable
from urllib.parse import urlsplit
from .constants import UTF8
from .utils import split_cookies
from .utils import split_cookies, parse_content_type_header
from .compat import cached_property
class HTTPMessage(metaclass=ABCMeta):
class HTTPMessage:
"""Abstract class for HTTP messages."""
def __init__(self, orig):
self._orig = orig
@abstractmethod
def iter_body(self, chunk_size: int) -> Iterable[bytes]:
"""Return an iterator over the body."""
raise NotImplementedError
@abstractmethod
def iter_lines(self, chunk_size: int) -> Iterable[bytes]:
"""Return an iterator over the body yielding (`line`, `line_feed`)."""
raise NotImplementedError
@property
@abstractmethod
def headers(self) -> str:
"""Return a `str` with the message's headers."""
raise NotImplementedError
@property
@abstractmethod
def encoding(self) -> Optional[str]:
"""Return a `str` with the message's encoding, if known."""
@property
def body(self) -> bytes:
"""Return a `bytes` with the message's body."""
raise NotImplementedError()
@cached_property
def encoding(self) -> str:
ct, params = parse_content_type_header(self.content_type)
return params.get('charset', '')
@property
def content_type(self) -> str:
@ -82,16 +76,6 @@ class HTTPResponse(HTTPMessage):
)
return '\r\n'.join(headers)
@property
def encoding(self):
return self._orig.encoding or UTF8
@property
def body(self):
# Only now the response body is fetched.
# Shouldn't be touched unless the body is actually needed.
return self._orig.content
class HTTPRequest(HTTPMessage):
"""A :class:`requests.models.Request` wrapper."""
@ -125,10 +109,6 @@ class HTTPRequest(HTTPMessage):
headers = '\r\n'.join(headers).strip()
return headers
@property
def encoding(self):
return UTF8
@property
def body(self):
body = self._orig.body

View File

@ -9,10 +9,12 @@ import pygments.token
from pygments.formatters.terminal import TerminalFormatter
from pygments.formatters.terminal256 import Terminal256Formatter
from pygments.lexer import Lexer
from pygments.lexers.data import JsonLexer
from pygments.lexers.special import TextLexer
from pygments.lexers.text import HttpLexer as PygmentsHttpLexer
from pygments.util import ClassNotFound
from ..lexers.json import EnhancedJsonLexer
from ...compat import is_windows
from ...context import Environment
from ...plugins import FormatterPlugin
@ -60,6 +62,7 @@ class ColorFormatter(FormatterPlugin):
http_lexer = PygmentsHttpLexer()
formatter = TerminalFormatter()
else:
from ..lexers.http import SimplifiedHTTPLexer
http_lexer = SimplifiedHTTPLexer()
formatter = Terminal256Formatter(
style=self.get_style_class(color_scheme)
@ -151,57 +154,14 @@ def get_lexer(
else:
lexer = pygments.lexers.get_lexer_by_name('json')
# Use our own JSON lexer: it supports JSON bodies preceded by non-JSON data
# as well as legit JSON bodies.
if isinstance(lexer, JsonLexer):
lexer = EnhancedJsonLexer()
return lexer
class SimplifiedHTTPLexer(pygments.lexer.RegexLexer):
"""Simplified HTTP lexer for Pygments.
It only operates on headers and provides a stronger contrast between
their names and values than the original one bundled with Pygments
(:class:`pygments.lexers.text import HttpLexer`), especially when
Solarized color scheme is used.
"""
name = 'HTTP'
aliases = ['http']
filenames = ['*.http']
tokens = {
'root': [
# Request-Line
(r'([A-Z]+)( +)([^ ]+)( +)(HTTP)(/)(\d+\.\d+)',
pygments.lexer.bygroups(
pygments.token.Name.Function,
pygments.token.Text,
pygments.token.Name.Namespace,
pygments.token.Text,
pygments.token.Keyword.Reserved,
pygments.token.Operator,
pygments.token.Number
)),
# Response Status-Line
(r'(HTTP)(/)(\d+\.\d+)( +)(\d{3})( +)(.+)',
pygments.lexer.bygroups(
pygments.token.Keyword.Reserved, # 'HTTP'
pygments.token.Operator, # '/'
pygments.token.Number, # Version
pygments.token.Text,
pygments.token.Number, # Status code
pygments.token.Text,
pygments.token.Name.Exception, # Reason
)),
# Header
(r'(.*?)( *)(:)( *)(.+)', pygments.lexer.bygroups(
pygments.token.Name.Attribute, # Name
pygments.token.Text,
pygments.token.Operator, # Colon
pygments.token.Text,
pygments.token.String # Value
))
]
}
class Solarized256Style(pygments.style.Style):
"""
solarized256

View File

@ -17,15 +17,16 @@ class JSONFormatter(FormatterPlugin):
]
if (self.kwargs['explicit_json']
or any(token in mime for token in maybe_json)):
from ..utils import load_prefixed_json
try:
obj = json.loads(body)
data_prefix, json_obj = load_prefixed_json(body)
except ValueError:
pass # Invalid JSON, ignore.
else:
# Indent, sort keys by name, and avoid
# unicode escapes to improve readability.
body = json.dumps(
obj=obj,
body = data_prefix + json.dumps(
obj=json_obj,
sort_keys=self.format_options['json']['sort_keys'],
ensure_ascii=False,
indent=self.format_options['json']['indent']

View File

@ -1,7 +1,7 @@
import sys
from typing import TYPE_CHECKING, Optional
from ...constants import UTF8
from ...encoding import UTF8
from ...plugins import FormatterPlugin
if TYPE_CHECKING:
@ -25,7 +25,7 @@ def pretty_xml(document: 'Document',
}
if standalone is not None and sys.version_info >= (3, 9):
kwargs['standalone'] = standalone
body = document.toprettyxml(**kwargs).decode()
body = document.toprettyxml(**kwargs).decode(kwargs['encoding'])
# Remove blank lines automatically added by `toprettyxml()`.
return '\n'.join(line for line in body.splitlines() if line.strip())

View File

View File

@ -0,0 +1,49 @@
import pygments
class SimplifiedHTTPLexer(pygments.lexer.RegexLexer):
"""Simplified HTTP lexer for Pygments.
It only operates on headers and provides a stronger contrast between
their names and values than the original one bundled with Pygments
(:class:`pygments.lexers.text import HttpLexer`), especially when
Solarized color scheme is used.
"""
name = 'HTTP'
aliases = ['http']
filenames = ['*.http']
tokens = {
'root': [
# Request-Line
(r'([A-Z]+)( +)([^ ]+)( +)(HTTP)(/)(\d+\.\d+)',
pygments.lexer.bygroups(
pygments.token.Name.Function,
pygments.token.Text,
pygments.token.Name.Namespace,
pygments.token.Text,
pygments.token.Keyword.Reserved,
pygments.token.Operator,
pygments.token.Number
)),
# Response Status-Line
(r'(HTTP)(/)(\d+\.\d+)( +)(\d{3})( +)(.+)',
pygments.lexer.bygroups(
pygments.token.Keyword.Reserved, # 'HTTP'
pygments.token.Operator, # '/'
pygments.token.Number, # Version
pygments.token.Text,
pygments.token.Number, # Status code
pygments.token.Text,
pygments.token.Name.Exception, # Reason
)),
# Header
(r'(.*?)( *)(:)( *)(.+)', pygments.lexer.bygroups(
pygments.token.Name.Attribute, # Name
pygments.token.Text,
pygments.token.Operator, # Colon
pygments.token.Text,
pygments.token.String # Value
))
]
}

View File

@ -0,0 +1,31 @@
import re
from pygments.lexer import bygroups, using, RegexLexer
from pygments.lexers.data import JsonLexer
from pygments.token import Token
PREFIX_TOKEN = Token.Error
PREFIX_REGEX = r'[^{\["]+'
class EnhancedJsonLexer(RegexLexer):
"""
Enhanced JSON lexer for Pygments.
It adds support for eventual data prefixing the actual JSON body.
"""
name = 'JSON'
flags = re.IGNORECASE | re.DOTALL
tokens = {
'root': [
# Eventual non-JSON data prefix followed by actual JSON body.
# FIX: data prefix + number (integer or float) are not correctly handled.
(
fr'({PREFIX_REGEX})' + r'((?:[{\["]|true|false|null).+)',
bygroups(PREFIX_TOKEN, using(JsonLexer))
),
# JSON body.
(r'.+', using(JsonLexer)),
],
}

View File

@ -2,10 +2,10 @@ from abc import ABCMeta, abstractmethod
from itertools import chain
from typing import Callable, Iterable, Union
from ..context import Environment
from ..constants import UTF8
from ..models import HTTPMessage
from .processing import Conversion, Formatting
from ..context import Environment
from ..encoding import smart_decode, smart_encode, UTF8
from ..models import HTTPMessage
BINARY_SUPPRESSED_NOTICE = (
@ -98,8 +98,16 @@ class EncodedStream(BaseStream):
"""
CHUNK_SIZE = 1
def __init__(self, env=Environment(), **kwargs):
def __init__(
self,
env=Environment(),
mime_overwrite: str = None,
encoding_overwrite: str = None,
**kwargs
):
super().__init__(**kwargs)
self.mime = mime_overwrite or self.msg.content_type
self.encoding = encoding_overwrite or self.msg.encoding
if env.stdout_isatty:
# Use the encoding supported by the terminal.
output_encoding = env.stdout_encoding
@ -113,8 +121,8 @@ class EncodedStream(BaseStream):
for line, lf in self.msg.iter_lines(self.CHUNK_SIZE):
if b'\0' in line:
raise BinarySuppressedError()
yield line.decode(self.msg.encoding) \
.encode(self.output_encoding, 'replace') + lf
line = smart_decode(line, self.encoding)
yield smart_encode(line, self.output_encoding) + lf
class PrettyStream(EncodedStream):
@ -136,7 +144,6 @@ class PrettyStream(EncodedStream):
super().__init__(**kwargs)
self.formatting = formatting
self.conversion = conversion
self.mime = self.msg.content_type.split(';')[0]
def get_headers(self) -> bytes:
return self.formatting.format_headers(
@ -167,9 +174,9 @@ class PrettyStream(EncodedStream):
if not isinstance(chunk, str):
# Text when a converter has been used,
# otherwise it will always be bytes.
chunk = chunk.decode(self.msg.encoding, 'replace')
chunk = smart_decode(chunk, self.encoding)
chunk = self.formatting.format_body(content=chunk, mime=self.mime)
return chunk.encode(self.output_encoding, 'replace')
return smart_encode(chunk, self.output_encoding)
class BufferedPrettyStream(PrettyStream):

37
httpie/output/utils.py Normal file
View File

@ -0,0 +1,37 @@
import json
import re
from typing import Tuple
from ..utils import load_json_preserve_order_and_dupe_keys
from .lexers.json import PREFIX_REGEX
def load_prefixed_json(data: str) -> Tuple[str, json.JSONDecoder]:
"""Simple JSON loading from `data`.
"""
# First, the full data.
try:
return '', load_json_preserve_order_and_dupe_keys(data)
except ValueError:
pass
# Then, try to find the start of the actual body.
data_prefix, body = parse_prefixed_json(data)
try:
return data_prefix, load_json_preserve_order_and_dupe_keys(body)
except ValueError:
raise ValueError('Invalid JSON')
def parse_prefixed_json(data: str) -> Tuple[str, str]:
"""Find the potential JSON body from `data`.
Sometimes the JSON body is prefixed with a XSSI magic string, specific to the server.
Return a tuple (data prefix, actual JSON body).
"""
matches = re.findall(PREFIX_REGEX, data)
data_prefix = matches[0] if matches else ''
body = data[len(data_prefix):]
return data_prefix, body

View File

@ -5,7 +5,7 @@ from typing import IO, TextIO, Tuple, Type, Union
import requests
from ..context import Environment
from ..models import HTTPRequest, HTTPResponse
from ..models import HTTPRequest, HTTPResponse, HTTPMessage
from .processing import Conversion, Formatting
from .streams import (
BaseStream, BufferedPrettyStream, EncodedStream, PrettyStream, RawStream,
@ -97,16 +97,17 @@ def build_output_stream_for_message(
with_headers: bool,
with_body: bool,
):
stream_class, stream_kwargs = get_stream_type_and_kwargs(
env=env,
args=args,
)
message_class = {
message_type = {
requests.PreparedRequest: HTTPRequest,
requests.Response: HTTPResponse,
}[type(requests_message)]
stream_class, stream_kwargs = get_stream_type_and_kwargs(
env=env,
args=args,
message_type=message_type,
)
yield from stream_class(
msg=message_class(requests_message),
msg=message_type(requests_message),
with_headers=with_headers,
with_body=with_body,
**stream_kwargs,
@ -120,7 +121,8 @@ def build_output_stream_for_message(
def get_stream_type_and_kwargs(
env: Environment,
args: argparse.Namespace
args: argparse.Namespace,
message_type: Type[HTTPMessage],
) -> Tuple[Type['BaseStream'], dict]:
"""Pick the right stream type and kwargs for it based on `env` and `args`.
@ -134,23 +136,27 @@ def get_stream_type_and_kwargs(
else RawStream.CHUNK_SIZE
)
}
elif args.prettify:
stream_class = PrettyStream if args.stream else BufferedPrettyStream
stream_kwargs = {
'env': env,
'conversion': Conversion(),
'formatting': Formatting(
env=env,
groups=args.prettify,
color_scheme=args.style,
explicit_json=args.json,
format_options=args.format_options,
)
}
else:
stream_class = EncodedStream
stream_kwargs = {
'env': env
'env': env,
}
if message_type is HTTPResponse:
stream_kwargs.update({
'mime_overwrite': args.response_mime,
'encoding_overwrite': args.response_charset,
})
if args.prettify:
stream_class = PrettyStream if args.stream else BufferedPrettyStream
stream_kwargs.update({
'conversion': Conversion(),
'formatting': Formatting(
env=env,
groups=args.prettify,
color_scheme=args.style,
explicit_json=args.json,
format_options=args.format_options,
)
})
return stream_class, stream_kwargs

View File

@ -1,5 +1,7 @@
class BasePlugin:
from typing import Tuple
class BasePlugin:
# The name of the plugin, eg. "My auth".
name = None
@ -53,7 +55,7 @@ class AuthPlugin(BasePlugin):
# then this is `None`.
raw_auth = None
def get_auth(self, username=None, password=None):
def get_auth(self, username: str = None, password: str = None):
"""
If `auth_parse` is set to `True`, then `username`
and `password` contain the parsed credentials.
@ -93,7 +95,7 @@ class TransportPlugin(BasePlugin):
class ConverterPlugin(BasePlugin):
"""
Possibly converts response data for prettified terminal display.
Possibly converts binary response data for prettified terminal display.
See httpie-msgpack for an example converter plugin:
@ -101,14 +103,21 @@ class ConverterPlugin(BasePlugin):
"""
def __init__(self, mime):
def __init__(self, mime: str):
self.mime = mime
def convert(self, content_bytes):
def convert(self, body: bytes) -> Tuple[str, str]:
"""
Convert a binary body to a textual representation for the terminal
and return a tuple containing the new Content-Type and content, e.g.:
('application/json', '{}')
"""
raise NotImplementedError
@classmethod
def supports(cls, mime):
def supports(cls, mime: str) -> bool:
raise NotImplementedError

View File

@ -4,6 +4,7 @@ from typing import Dict, List, Type
from pkg_resources import iter_entry_points
from ..utils import repr_dict
from . import AuthPlugin, ConverterPlugin, FormatterPlugin
from .base import BasePlugin, TransportPlugin
@ -65,5 +66,13 @@ class PluginManager(list):
def get_transport_plugins(self) -> List[Type[TransportPlugin]]:
return self.filter(TransportPlugin)
def __str__(self):
return repr_dict({
'adapters': self.get_transport_plugins(),
'auth': self.get_auth_plugins(),
'converters': self.get_converters(),
'formatters': self.get_formatters(),
})
def __repr__(self):
return f'<PluginManager: {list(self)}>'
return f'<{type(self).__name__} {self}>'

1
httpie/prompt Submodule

Submodule httpie/prompt added at 8922a77156

View File

@ -52,7 +52,7 @@ def get_httpie_session(
class Session(BaseConfigDict):
helpurl = 'https://httpie.org/doc#sessions'
helpurl = 'https://httpie.io/docs#sessions'
about = 'HTTPie session file'
def __init__(self, path: Union[str, Path]):
@ -112,7 +112,7 @@ class Session(BaseConfigDict):
@cookies.setter
def cookies(self, jar: RequestsCookieJar):
# <https://docs.python.org/2/library/cookielib.html#cookie-objects>
# <https://docs.python.org/3/library/cookielib.html#cookie-objects>
stored_attrs = ['value', 'path', 'secure', 'expires']
self['cookies'] = {}
for cookie in jar:

View File

@ -1,19 +1,67 @@
import json
import mimetypes
import re
import sys
import time
from collections import OrderedDict
from http.cookiejar import parse_ns_headers
from pprint import pformat
from typing import List, Optional, Tuple
import re
from typing import Any, List, Optional, Tuple
import requests.auth
RE_COOKIE_SPLIT = re.compile(r', (?=[^ ;]+=)')
Item = Tuple[str, Any]
Items = List[Item]
def load_json_preserve_order(s):
return json.loads(s, object_pairs_hook=OrderedDict)
class JsonDictPreservingDuplicateKeys(OrderedDict):
"""A specialized JSON dict preserving duplicate keys."""
# Python versions prior to 3.8 suffer from an issue with multiple keys with the same name.
# `json.dumps(obj, indent=N, sort_keys=True)` will output sorted keys when they are unique, and
# duplicate keys will be outputted as they were defined in the original data.
# See <https://bugs.python.org/issue23493#msg400929> for the behavior change between Python versions.
SUPPORTS_SORTING = sys.version_info >= (3, 8)
def __init__(self, items: Items):
self._items = items
self._ensure_items_used()
def _ensure_items_used(self) -> None:
"""HACK: Force `json.dumps()` to use `self.items()` instead of an empty dict.
Two JSON encoders are available on CPython: pure-Python (1) and C (2) implementations.
(1) The pure-python implementation will do a simple `if not dict: return '{}'`,
and we could fake that check by implementing the `__bool__()` method.
Source:
- <https://github.com/python/cpython/blob/9d318ad/Lib/json/encoder.py#L334-L336>
(2) On the other hand, the C implementation will do a check on the number of
items contained inside the dict, using a verification on `dict->ma_used`, which
is updated only when an item is added/removed from the dict. For that case,
there is no workaround but to add an item into the dict.
Sources:
- <https://github.com/python/cpython/blob/9d318ad/Modules/_json.c#L1581-L1582>
- <https://github.com/python/cpython/blob/9d318ad/Include/cpython/dictobject.h#L53>
- <https://github.com/python/cpython/blob/9d318ad/Include/cpython/dictobject.h#L17-L18>
To please both implementations, we simply add one item to the dict.
"""
if self._items:
self['__hack__'] = '__hack__'
def items(self) -> Items:
"""Return all items, duplicate ones included.
"""
return self._items
def load_json_preserve_order_and_dupe_keys(s):
return json.loads(s, object_pairs_hook=JsonDictPreservingDuplicateKeys)
def repr_dict(d: dict) -> str:
@ -141,3 +189,21 @@ def _max_age_to_expires(cookies, now):
max_age = cookie.get('max-age')
if max_age and max_age.isdigit():
cookie['expires'] = now + float(max_age)
def parse_content_type_header(header):
"""Borrowed from requests."""
tokens = header.split(';')
content_type, params = tokens[0].strip(), tokens[1:]
params_dict = {}
items_to_strip = "\"' "
for param in params:
param = param.strip()
if param:
key, value = param, True
index_of_equals = param.find("=")
if index_of_equals != -1:
key = param[:index_of_equals].strip(items_to_strip)
value = param[index_of_equals + 1:].strip(items_to_strip)
params_dict[key.lower()] = value
return content_type, params_dict

View File

@ -7,8 +7,10 @@
[tool:pytest]
# <https://docs.pytest.org/en/latest/customize.html>
norecursedirs = tests/fixtures .*
addopts = --tb=native --doctest-modules
testpaths = httpie tests
norecursedirs = tests/fixtures
addopts = --tb=native --doctest-modules --verbose
xfail_strict = True
[flake8]

View File

@ -6,8 +6,10 @@ from setuptools import setup, find_packages
import httpie
# Note: keep requirements here to ease distributions packaging
tests_require = [
'pexpect',
'pytest',
'pytest-httpbin>=0.0.6',
'responses',
@ -19,17 +21,25 @@ dev_require = [
'flake8-deprecated',
'flake8-mutable',
'flake8-tuple',
'mdformat',
'jinja2',
'pyopenssl',
'pytest-cov',
'pyyaml',
'twine',
'wheel',
]
install_requires = [
'charset_normalizer>=2.0.0',
'defusedxml>=0.6.0',
'requests[socks]>=2.22.0',
'Pygments>=2.5.2',
'requests-toolbelt>=0.9.1',
'setuptools',
# Prompt
'click>=5.0',
'parsimonious>=0.6.2',
'prompt-toolkit>=2.0.0,<3.0.0',
'pyyaml>=3.0',
]
install_requires_win_only = [
'colorama>=0.2.4',
@ -75,6 +85,7 @@ setup(
'console_scripts': [
'http = httpie.__main__:main',
'https = httpie.__main__:main',
'http-prompt=httpie.prompt.cli:cli',
],
},
python_requires='>=3.6',

114
snapcraft.yaml Normal file
View File

@ -0,0 +1,114 @@
name: httpie
title: HTTPie
summary: Modern, user-friendly command-line HTTP client for the API era
description: |
HTTPie *aitch-tee-tee-pie* is a user-friendly command-line HTTP client
for the API era.
It comes with JSON support, syntax highlighting, persistent sessions,
wget-like downloads, plugins, and more.
The project's goal is to make CLI interaction with web services as
human-friendly as possible. HTTPie is designed for testing, debugging,
and generally interacting with APIs & HTTP servers.
The http & https commands allow for creating and sending arbitrary HTTP
requests. They use simple and natural syntax and provide formatted and
colorized output.
Main features:
- Built-in JSON support
- Colorized and formatted terminal output
- Sensible defaults for the API era
- Persistent sessions
- Forms and file uploads
- HTTPS, proxies, and authentication support
- Support for arbitrary request data and headers
- Wget-like downloads
- Extensions API
- Expressive and intuitive syntax
- Linux, macOS, Windows, and FreeBSD support
- All that & more in 2 simple commands: http + https
Links
- Documentation: https://httpie.io/docs
- Try in browser: https://httpie.io/run
- GitHub: https://github.com/httpie/httpie
- Twitter: https://twitter.com/httpie
- Discord: https://httpie.io/chat
license: BSD-3-Clause-LBNL
# Automatically change the current version based on the source code
adopt-info: httpie
# https://snapcraft.io/docs/snapcraft-top-level-metadata#heading--icon
# icon:
base: core20
confinement: strict
grade: stable
compression: lzo
parts:
httpie:
source: .
plugin: python
# Guess the current version from sources
override-pull: |
snapcraftctl pull
snapcraftctl set-version $(grep '__version__' httpie/__init__.py | cut -d"'" -f2)
override-build: |
snapcraftctl build
echo "Adding HTTPie plugins ..."
python -m pip install httpie-unixsocket
python -m pip install httpie-snapdsocket
echo "Removing no more needed modules ..."
python -m pip uninstall -y pip wheel
override-prime: |
snapcraftctl prime
echo "Removing useless files ..."
packages=$SNAPCRAFT_PRIME/lib/python3.8/site-packages
rm -rfv $packages/_distutils_hack
rm -rfv $packages/pkg_resources/tests
rm -rfv $packages/requests_unixsocket/test*
rm -rfv $packages/setuptools
echo "Compiling pyc files ..."
python -m compileall -f $packages
echo "Copying extra files ..."
cp $SNAPCRAFT_PART_SRC/extras/httpie-completion.bash $SNAPCRAFT_PRIME/bin/
plugs:
dot-config-httpie:
interface: personal-files
write:
- $HOME/.config/httpie
dot-httpie:
interface: personal-files
write:
- $HOME/.httpie
apps:
http:
command: bin/http
plugs: &plugs
- dot-config-httpie
- dot-httpie
- home
- network
- removable-media
completer: bin/httpie-completion.bash
environment:
LC_ALL: C.UTF-8
https:
command: bin/https
plugs: *plugs
completer: bin/httpie-completion.bash
environment:
LC_ALL: C.UTF-8

View File

@ -1,4 +1,6 @@
import os
import socket
import pytest
from pytest_httpbin import certs
@ -41,3 +43,19 @@ def httpbin_with_chunked_support(_httpbin_with_chunked_support_available):
if _httpbin_with_chunked_support_available:
return HTTPBIN_WITH_CHUNKED_SUPPORT
pytest.skip(f'{HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN} not resolvable')
@pytest.fixture(autouse=True, scope='session')
def pyopenssl_inject():
"""
Injects `pyOpenSSL` module to make sure `requests` will use it.
<https://github.com/psf/requests/pull/5443#issuecomment-645740394>
"""
if os.getenv('HTTPIE_TEST_WITH_PYOPENSSL', '0') == '1':
try:
import urllib3.contrib.pyopenssl
urllib3.contrib.pyopenssl.inject_into_urllib3()
except ModuleNotFoundError:
pytest.fail('Missing "pyopenssl" module.')
yield

View File

@ -1,7 +1,8 @@
"""Test data"""
from pathlib import Path
from httpie.constants import UTF8
from httpie.encoding import UTF8
from httpie.output.formatters.xml import pretty_xml, parse_xml
def patharg(path):
@ -16,6 +17,7 @@ def patharg(path):
FIXTURES_ROOT = Path(__file__).parent
FILE_PATH = FIXTURES_ROOT / 'test.txt'
JSON_FILE_PATH = FIXTURES_ROOT / 'test.json'
JSON_WITH_DUPE_KEYS_FILE_PATH = FIXTURES_ROOT / 'test_with_dupe_keys.json'
BIN_FILE_PATH = FIXTURES_ROOT / 'test.bin'
XML_FILES_PATH = FIXTURES_ROOT / 'xmldata'
XML_FILES_VALID = list((XML_FILES_PATH / 'valid').glob('*_raw.xml'))
@ -34,3 +36,5 @@ FILE_CONTENT = FILE_PATH.read_text(encoding=UTF8).strip()
JSON_FILE_CONTENT = JSON_FILE_PATH.read_text(encoding=UTF8)
BIN_FILE_CONTENT = BIN_FILE_PATH.read_bytes()
UNICODE = FILE_CONTENT
XML_DATA_RAW = '<?xml version="1.0" encoding="utf-8"?><root><e>text</e></root>'
XML_DATA_FORMATTED = pretty_xml(parse_xml(XML_DATA_RAW))

View File

@ -0,0 +1 @@
{"key":15,"key":15,"key":3,"key":7}

0
tests/prompt/__init__.py Normal file
View File

59
tests/prompt/base.py Normal file
View File

@ -0,0 +1,59 @@
import os
import shutil
import sys
import tempfile
import unittest
class TempAppDirTestCase(unittest.TestCase):
"""Set up temporary app data and config directories before every test
method, and delete them afterwards.
"""
def setUp(self):
# Create a temp dir that will contain data and config directories
self.temp_dir = tempfile.mkdtemp()
if sys.platform == 'win32':
self.homes = {
# subdir_name: envvar_name
'data': 'LOCALAPPDATA',
'config': 'LOCALAPPDATA'
}
else:
self.homes = {
# subdir_name: envvar_name
'data': 'XDG_DATA_HOME',
'config': 'XDG_CONFIG_HOME'
}
# Used to restore
self.orig_envvars = {}
for subdir_name, envvar_name in self.homes.items():
if envvar_name in os.environ:
self.orig_envvars[envvar_name] = os.environ[envvar_name]
os.environ[envvar_name] = os.path.join(self.temp_dir, subdir_name)
def tearDown(self):
# Restore envvar values
for name in self.homes.values():
if name in self.orig_envvars:
os.environ[name] = self.orig_envvars[name]
else:
del os.environ[name]
shutil.rmtree(self.temp_dir)
def make_tempfile(self, data='', subdir_name=''):
"""Create a file under self.temp_dir and return the path."""
full_tempdir = os.path.join(self.temp_dir, subdir_name)
if not os.path.exists(full_tempdir):
os.makedirs(full_tempdir)
if isinstance(data, str):
data = data.encode()
with tempfile.NamedTemporaryFile(dir=full_tempdir, delete=False) as f:
f.write(data)
return f.name

View File

@ -0,0 +1,161 @@
from httpie.prompt.context import Context
def test_creation():
context = Context('http://example.com')
assert context.url == 'http://example.com'
assert context.options == {}
assert context.headers == {}
assert context.querystring_params == {}
assert context.body_params == {}
assert not context.should_exit
def test_creation_with_longer_url():
context = Context('http://example.com/a/b/c/index.html')
assert context.url == 'http://example.com/a/b/c/index.html'
assert context.options == {}
assert context.headers == {}
assert context.querystring_params == {}
assert context.body_params == {}
assert not context.should_exit
def test_eq():
c1 = Context('http://localhost')
c2 = Context('http://localhost')
assert c1 == c2
c1.options['--verify'] = 'no'
assert c1 != c2
def test_copy():
c1 = Context('http://localhost')
c2 = c1.copy()
assert c1 == c2
assert c1 is not c2
def test_update():
c1 = Context('http://localhost')
c1.headers['Accept'] = 'application/json'
c1.querystring_params['flag'] = '1'
c1.body_params.update({
'name': 'John Doe',
'email': 'john@example.com'
})
c2 = Context('http://example.com')
c2.headers['Content-Type'] = 'text/html'
c2.body_params['name'] = 'John Smith'
c1.update(c2)
assert c1.url == 'http://example.com'
assert c1.headers == {
'Accept': 'application/json',
'Content-Type': 'text/html'
}
assert c1.querystring_params == {'flag': '1'}
assert c1.body_params == {
'name': 'John Smith',
'email': 'john@example.com'
}
def test_spec():
c = Context('http://localhost', spec={
'paths': {
'/users': {
'get': {
'parameters': [
{'name': 'username', 'in': 'path'},
{'name': 'since', 'in': 'query'},
{'name': 'Accept'}
]
}
},
'/orgs/{org}': {
'get': {
'parameters': [
{'name': 'org', 'in': 'path'},
{'name': 'featured', 'in': 'query'},
{'name': 'X-Foo', 'in': 'header'}
]
}
}
}
})
assert c.url == 'http://localhost'
root_children = list(sorted(c.root.children))
assert len(root_children) == 2
assert root_children[0].name == 'orgs'
assert root_children[1].name == 'users'
orgs_children = list(sorted(root_children[0].children))
assert len(orgs_children) == 1
org_children = list(sorted(list(orgs_children)[0].children))
assert len(org_children) == 2
assert org_children[0].name == 'X-Foo'
assert org_children[1].name == 'featured'
users_children = list(sorted(root_children[1].children))
assert len(users_children) == 2
assert users_children[0].name == 'Accept'
assert users_children[1].name == 'since'
def test_override():
"""Parameters can be defined at path level
"""
c = Context('http://localhost', spec={
'paths': {
'/users': {
'parameters': [
{'name': 'username', 'in': 'query'},
{'name': 'Accept', 'in': 'header'}
],
'get': {
'parameters': [
{'name': 'custom1', 'in': 'query'}
]
},
'post': {
'parameters': [
{'name': 'custom2', 'in': 'query'},
]
},
},
'/orgs': {
'parameters': [
{'name': 'username', 'in': 'query'},
{'name': 'Accept', 'in': 'header'}
],
'get': {}
}
}
})
assert c.url == 'http://localhost'
root_children = list(sorted(c.root.children))
# one path
assert len(root_children) == 2
assert root_children[0].name == 'orgs'
assert root_children[1].name == 'users'
orgs_methods = list(sorted(list(root_children)[0].children))
# path parameters are used even if no method parameter
assert len(orgs_methods) == 2
assert next(filter(lambda i: i.name == 'username', orgs_methods), None) is not None
assert next(filter(lambda i: i.name == 'Accept', orgs_methods), None) is not None
users_methods = list(sorted(list(root_children)[1].children))
# path and methods parameters are merged
assert len(users_methods) == 4
assert next(filter(lambda i: i.name == 'username', users_methods), None) is not None
assert next(filter(lambda i: i.name == 'custom1', users_methods), None) is not None
assert next(filter(lambda i: i.name == 'custom2', users_methods), None) is not None
assert next(filter(lambda i: i.name == 'Accept', users_methods), None) is not None

View File

@ -0,0 +1,162 @@
from httpie.prompt.context import Context
from httpie.prompt.context import transform as t
def test_extract_args_for_httpie_main_get():
c = Context('http://localhost/things')
c.headers.update({
'Authorization': 'ApiKey 1234',
'Accept': 'text/html'
})
c.querystring_params.update({
'page': '2',
'limit': '10'
})
args = t.extract_args_for_httpie_main(c, method='get')
assert args == ['GET', 'http://localhost/things', 'limit==10', 'page==2',
'Accept:text/html', 'Authorization:ApiKey 1234']
def test_extract_args_for_httpie_main_post():
c = Context('http://localhost/things')
c.headers.update({
'Authorization': 'ApiKey 1234',
'Accept': 'text/html'
})
c.options.update({
'--verify': 'no',
'--form': None
})
c.body_params.update({
'full name': 'Jane Doe',
'email': 'jane@example.com'
})
args = t.extract_args_for_httpie_main(c, method='post')
assert args == ['--form', '--verify', 'no',
'POST', 'http://localhost/things',
'email=jane@example.com', 'full name=Jane Doe',
'Accept:text/html', 'Authorization:ApiKey 1234']
def test_extract_raw_json_args_for_httpie_main_post():
c = Context('http://localhost/things')
c.body_json_params.update({
'enabled': True,
'items': ['foo', 'bar'],
'object': {
'id': 10,
'name': 'test'
}
})
args = t.extract_args_for_httpie_main(c, method='post')
assert args == ['POST', 'http://localhost/things',
'enabled:=true', 'items:=["foo", "bar"]',
'object:={"id": 10, "name": "test"}']
def test_format_to_httpie_get():
c = Context('http://localhost/things')
c.headers.update({
'Authorization': 'ApiKey 1234',
'Accept': 'text/html'
})
c.querystring_params.update({
'page': '2',
'limit': '10',
'name': ['alice', 'bob bob']
})
output = t.format_to_httpie(c, method='get')
assert output == ("http GET http://localhost/things "
"limit==10 name==alice 'name==bob bob' page==2 "
"Accept:text/html 'Authorization:ApiKey 1234'\n")
def test_format_to_httpie_post():
c = Context('http://localhost/things')
c.headers.update({
'Authorization': 'ApiKey 1234',
'Accept': 'text/html'
})
c.options.update({
'--verify': 'no',
'--form': None
})
c.body_params.update({
'full name': 'Jane Doe',
'email': 'jane@example.com'
})
output = t.format_to_httpie(c, method='post')
assert output == ("http --form --verify=no POST http://localhost/things "
"email=jane@example.com 'full name=Jane Doe' "
"Accept:text/html 'Authorization:ApiKey 1234'\n")
def test_format_to_http_prompt_1():
c = Context('http://localhost/things')
c.headers.update({
'Authorization': 'ApiKey 1234',
'Accept': 'text/html'
})
c.querystring_params.update({
'page': '2',
'limit': '10'
})
output = t.format_to_http_prompt(c)
assert output == ("cd http://localhost/things\n"
"limit==10\n"
"page==2\n"
"Accept:text/html\n"
"'Authorization:ApiKey 1234'\n")
def test_format_to_http_prompt_2():
c = Context('http://localhost/things')
c.headers.update({
'Authorization': 'ApiKey 1234',
'Accept': 'text/html'
})
c.options.update({
'--verify': 'no',
'--form': None
})
c.body_params.update({
'full name': 'Jane Doe',
'email': 'jane@example.com'
})
output = t.format_to_http_prompt(c)
assert output == ("--form\n"
"--verify=no\n"
"cd http://localhost/things\n"
"email=jane@example.com\n"
"'full name=Jane Doe'\n"
"Accept:text/html\n"
"'Authorization:ApiKey 1234'\n")
def test_format_raw_json_string_to_http_prompt():
c = Context('http://localhost/things')
c.body_json_params.update({
'bar': 'baz',
})
output = t.format_to_http_prompt(c)
assert output == ("cd http://localhost/things\n"
"bar:='\"baz\"'\n")
def test_extract_httpie_options():
c = Context('http://localhost')
c.options.update({
'--verify': 'no',
'--form': None
})
output = t._extract_httpie_options(c, excluded_keys=['--form'])
assert output == ['--verify', 'no']

319
tests/prompt/test_cli.py Normal file
View File

@ -0,0 +1,319 @@
import json
import os
import sys
import unittest
from unittest.mock import patch, DEFAULT
from click.testing import CliRunner
from requests.models import Response
from .base import TempAppDirTestCase
from httpie.prompt import xdg
from httpie.prompt.context import Context
from httpie.prompt.cli import cli, execute, ExecutionListener
def run_and_exit(cli_args=None, prompt_commands=None):
"""Run http-prompt executable, execute some prompt commands, and exit."""
if cli_args is None:
cli_args = []
# Make sure last command is 'exit'
if prompt_commands is None:
prompt_commands = ['exit']
else:
prompt_commands += ['exit']
# Fool cli() so that it believes we're running from CLI instead of pytest.
# We will restore it at the end of the function.
orig_argv = sys.argv
sys.argv = ['http-prompt'] + cli_args
try:
with patch.multiple('httpie.prompt.cli',
prompt=DEFAULT, execute=DEFAULT) as mocks:
mocks['execute'].side_effect = execute
# prompt() is mocked to return the command in 'prompt_commands' in
# sequence, i.e., prompt() returns prompt_commands[i-1] when it is
# called for the ith time
mocks['prompt'].side_effect = prompt_commands
result = CliRunner().invoke(cli, cli_args)
context = mocks['execute'].call_args[0][1]
return result, context
finally:
sys.argv = orig_argv
class TestCli(TempAppDirTestCase):
def test_without_args(self):
result, context = run_and_exit(['http://localhost'])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://localhost')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {})
def test_incomplete_url1(self):
result, context = run_and_exit(['://example.com'])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {})
def test_incomplete_url2(self):
result, context = run_and_exit(['//example.com'])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {})
def test_incomplete_url3(self):
result, context = run_and_exit(['example.com'])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {})
def test_httpie_oprions(self):
url = 'http://example.com'
custom_args = '--auth value: name=foo'
result, context = run_and_exit([url] + custom_args.split())
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(context.options, {'--auth': 'value:'})
self.assertEqual(context.body_params, {'name': 'foo'})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {})
def test_persistent_context(self):
result, context = run_and_exit(['//example.com', 'name=bob', 'id==10'])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {'name': 'bob'})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {'id': ['10']})
result, context = run_and_exit()
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {'name': 'bob'})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {'id': ['10']})
def test_cli_args_bypasses_persistent_context(self):
result, context = run_and_exit(['//example.com', 'name=bob', 'id==10'])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {'name': 'bob'})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {'id': ['10']})
result, context = run_and_exit(['//example.com', 'sex=M'])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {'sex': 'M'})
self.assertEqual(context.headers, {})
def test_config_file(self):
# Config file is not there at the beginning
config_path = os.path.join(xdg.get_config_dir(), 'config.py')
self.assertFalse(os.path.exists(config_path))
# After user runs it for the first time, a default config file should
# be created
result, context = run_and_exit(['//example.com'])
self.assertEqual(result.exit_code, 0)
self.assertTrue(os.path.exists(config_path))
def test_cli_arguments_with_spaces(self):
result, context = run_and_exit(['example.com', "name=John Doe",
"Authorization:Bearer API KEY"])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.querystring_params, {})
self.assertEqual(context.body_params, {'name': 'John Doe'})
self.assertEqual(context.headers, {'Authorization': 'Bearer API KEY'})
def test_spec_from_local(self):
spec_filepath = self.make_tempfile(json.dumps({
'paths': {
'/users': {},
'/orgs': {}
}
}))
result, context = run_and_exit(['example.com', "--spec",
spec_filepath])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(set([n.name for n in context.root.children]),
set(['users', 'orgs']))
def test_spec_basePath(self):
spec_filepath = self.make_tempfile(json.dumps({
'basePath': '/api/v1',
'paths': {
'/users': {},
'/orgs': {}
}
}))
result, context = run_and_exit(['example.com', "--spec",
spec_filepath])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
lv1_names = set([node.name for node in context.root.ls()])
lv2_names = set([node.name for node in context.root.ls('api')])
lv3_names = set([node.name for node in context.root.ls('api', 'v1')])
self.assertEqual(lv1_names, set(['api']))
self.assertEqual(lv2_names, set(['v1']))
self.assertEqual(lv3_names, set(['users', 'orgs']))
def test_spec_from_http(self):
spec_url = 'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json'
result, context = run_and_exit(['https://api.github.com', '--spec',
spec_url])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'https://api.github.com')
top_level_paths = set([n.name for n in context.root.children])
self.assertIn('repos', top_level_paths)
self.assertIn('users', top_level_paths)
def test_spec_from_http_only(self):
spec_url = (
'https://api.apis.guru/v2/specs/medium.com/1.0.0/swagger.json')
result, context = run_and_exit(['--spec', spec_url])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'https://api.medium.com/v1')
lv1_names = set([node.name for node in context.root.ls()])
lv2_names = set([node.name for node in context.root.ls('v1')])
self.assertEqual(lv1_names, set(['v1']))
self.assertEqual(lv2_names, set(['me', 'publications', 'users']))
def test_spec_with_trailing_slash(self):
spec_filepath = self.make_tempfile(json.dumps({
'basePath': '/api',
'paths': {
'/': {},
'/users/': {}
}
}))
result, context = run_and_exit(['example.com', "--spec",
spec_filepath])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
lv1_names = set([node.name for node in context.root.ls()])
lv2_names = set([node.name for node in context.root.ls('api')])
self.assertEqual(lv1_names, set(['api']))
self.assertEqual(lv2_names, set(['/', 'users/']))
def test_env_only(self):
env_filepath = self.make_tempfile(
"cd http://example.com\nname=bob\nid==10")
result, context = run_and_exit(["--env", env_filepath])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {'name': 'bob'})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {'id': ['10']})
def test_env_with_url(self):
env_filepath = self.make_tempfile(
"cd http://example.com\nname=bob\nid==10")
result, context = run_and_exit(["--env", env_filepath,
'other_example.com'])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://other_example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {'name': 'bob'})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {'id': ['10']})
def test_env_with_options(self):
env_filepath = self.make_tempfile(
"cd http://example.com\nname=bob\nid==10")
result, context = run_and_exit(["--env", env_filepath,
'other_example.com', 'name=alice'])
self.assertEqual(result.exit_code, 0)
self.assertEqual(context.url, 'http://other_example.com')
self.assertEqual(context.options, {})
self.assertEqual(context.body_params, {'name': 'alice'})
self.assertEqual(context.headers, {})
self.assertEqual(context.querystring_params, {'id': ['10']})
@patch('httpie.prompt.cli.prompt')
@patch('httpie.prompt.cli.execute')
def test_press_ctrl_d(self, execute_mock, prompt_mock):
prompt_mock.side_effect = EOFError
execute_mock.side_effect = execute
result = CliRunner().invoke(cli, [])
self.assertEqual(result.exit_code, 0)
class TestExecutionListenerSetCookies(unittest.TestCase):
def setUp(self):
self.listener = ExecutionListener({})
self.response = Response()
self.response.cookies.update({
'username': 'john',
'sessionid': 'abcd'
})
self.context = Context('http://localhost')
self.context.headers['Cookie'] = 'name="John Doe"; sessionid=xyz'
def test_auto(self):
self.listener.cfg['set_cookies'] = 'auto'
self.listener.response_returned(self.context, self.response)
self.assertEqual(self.context.headers['Cookie'],
'name="John Doe"; sessionid=abcd; username=john')
@patch('httpie.prompt.cli.click.confirm')
def test_ask_and_yes(self, confirm_mock):
confirm_mock.return_value = True
self.listener.cfg['set_cookies'] = 'ask'
self.listener.response_returned(self.context, self.response)
self.assertEqual(self.context.headers['Cookie'],
'name="John Doe"; sessionid=abcd; username=john')
@patch('httpie.prompt.cli.click.confirm')
def test_ask_and_no(self, confirm_mock):
confirm_mock.return_value = False
self.listener.cfg['set_cookies'] = 'ask'
self.listener.response_returned(self.context, self.response)
self.assertEqual(self.context.headers['Cookie'],
'name="John Doe"; sessionid=xyz')
def test_off(self):
self.listener.cfg['set_cookies'] = 'off'
self.listener.response_returned(self.context, self.response)
self.assertEqual(self.context.headers['Cookie'],
'name="John Doe"; sessionid=xyz')

View File

@ -0,0 +1,130 @@
# -*- coding: utf-8 -*-
import unittest
from prompt_toolkit.document import Document
from httpie.prompt.completer import HttpPromptCompleter
from httpie.prompt.context import Context
class TestCompleter(unittest.TestCase):
def setUp(self):
self.context = Context('http://localhost', spec={
'paths': {
'/users': {},
'/users/{username}': {},
'/users/{username}/events': {},
'/users/{username}/orgs': {},
'/orgs': {},
'/orgs/{org}': {},
'/orgs/{org}/events': {},
'/orgs/{org}/members': {}
}
})
self.completer = HttpPromptCompleter(self.context)
self.completer_event = None
def get_completions(self, command):
if not isinstance(command, str):
command = command.decode()
position = len(command)
completions = self.completer.get_completions(
Document(text=command, cursor_position=position),
self.completer_event)
return [c.text for c in completions]
def test_header_name(self):
result = self.get_completions('ctype')
self.assertEqual(result[0], 'Content-Type')
def test_header_value(self):
result = self.get_completions('Content-Type:json')
self.assertEqual(result[0], 'application/json')
def test_verify_option(self):
result = self.get_completions('--vfy')
self.assertEqual(result[0], '--verify')
def test_preview_then_action(self):
result = self.get_completions('httpie po')
self.assertEqual(result[0], 'post')
def test_rm_body_param(self):
self.context.body_params['my_name'] = 'dont_care'
result = self.get_completions('rm -b ')
self.assertEqual(result[0], 'my_name')
def test_rm_body_json_param(self):
self.context.body_json_params['number'] = 2
result = self.get_completions('rm -b ')
self.assertEqual(result[0], 'number')
def test_rm_querystring_param(self):
self.context.querystring_params['my_name'] = 'dont_care'
result = self.get_completions('rm -q ')
self.assertEqual(result[0], 'my_name')
def test_rm_header(self):
self.context.headers['Accept'] = 'dont_care'
result = self.get_completions('rm -h ')
self.assertEqual(result[0], 'Accept')
def test_rm_option(self):
self.context.options['--form'] = None
result = self.get_completions('rm -o ')
self.assertEqual(result[0], '--form')
def test_querystring_with_chinese(self):
result = self.get_completions('name==王')
self.assertFalse(result)
def test_header_with_spanish(self):
result = self.get_completions('X-Custom-Header:Jesú')
self.assertFalse(result)
def test_options_method(self):
result = self.get_completions('opt')
self.assertEqual(result[0], 'options')
def test_ls_no_path(self):
result = self.get_completions('ls ')
self.assertEqual(result, ['orgs', 'users'])
def test_ls_no_path_substring(self):
result = self.get_completions('ls o')
self.assertEqual(result, ['orgs'])
def test_ls_absolute_path(self):
result = self.get_completions('ls /users/1/')
self.assertEqual(result, ['events', 'orgs'])
def test_ls_absolute_path_substring(self):
result = self.get_completions('ls /users/1/e')
self.assertEqual(result, ['events'])
def test_ls_relative_path(self):
self.context.url = 'http://localhost/orgs'
result = self.get_completions('ls 1/')
self.assertEqual(result, ['events', 'members'])
def test_cd_no_path(self):
result = self.get_completions('cd ')
self.assertEqual(result, ['orgs', 'users'])
def test_cd_no_path_substring(self):
result = self.get_completions('cd o')
self.assertEqual(result, ['orgs'])
def test_cd_absolute_path(self):
result = self.get_completions('cd /users/1/')
self.assertEqual(result, ['events', 'orgs'])
def test_cd_absolute_path_substring(self):
result = self.get_completions('cd /users/1/e')
self.assertEqual(result, ['events'])
def test_cd_relative_path(self):
self.context.url = 'http://localhost/orgs'
result = self.get_completions('cd 1/')
self.assertEqual(result, ['events', 'members'])

View File

@ -0,0 +1,70 @@
import hashlib
import os
from .base import TempAppDirTestCase
from httpie.prompt import config
def _hash_file(path):
with open(path, 'rb') as f:
data = f.read()
return hashlib.sha1(data).hexdigest()
class TestConfig(TempAppDirTestCase):
def test_initialize(self):
# Config file doesn't exist at first
expected_path = config.get_user_config_path()
self.assertFalse(os.path.exists(expected_path))
# Config file should exist after initialization
copied, actual_path = config.initialize()
self.assertTrue(copied)
self.assertEqual(actual_path, expected_path)
self.assertTrue(os.path.exists(expected_path))
# Change config file and hash the content to see if it's changed
with open(expected_path, 'a') as f:
f.write('dont_care\n')
orig_hash = _hash_file(expected_path)
# Make sure it's fine to call config.initialize() twice
copied, actual_path = config.initialize()
self.assertFalse(copied)
self.assertEqual(actual_path, expected_path)
self.assertTrue(os.path.exists(expected_path))
# Make sure config file is unchanged
new_hash = _hash_file(expected_path)
self.assertEqual(new_hash, orig_hash)
def test_load_default(self):
cfg = config.load_default()
self.assertEqual(cfg['command_style'], 'solarized')
self.assertFalse(cfg['output_style'])
self.assertEqual(cfg['pager'], 'less')
def test_load_user(self):
copied, path = config.initialize()
self.assertTrue(copied)
with open(path, 'w') as f:
f.write("\ngreeting = 'hello!'\n")
cfg = config.load_user()
self.assertEqual(cfg, {'greeting': 'hello!'})
def test_load(self):
copied, path = config.initialize()
self.assertTrue(copied)
with open(path, 'w') as f:
f.write("pager = 'more'\n"
"greeting = 'hello!'\n")
cfg = config.load()
self.assertEqual(cfg['command_style'], 'solarized')
self.assertFalse(cfg['output_style'])
self.assertEqual(cfg['pager'], 'more')
self.assertEqual(cfg['greeting'], 'hello!')

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from .base import TempAppDirTestCase
from httpie.prompt.context import Context
from httpie.prompt.contextio import save_context, load_context
class TestContextIO(TempAppDirTestCase):
def test_save_and_load_context_non_ascii(self):
c = Context('http://localhost')
c.headers.update({
'User-Agent': 'Ö',
'Authorization': '中文'
})
save_context(c)
c = Context('http://0.0.0.0')
load_context(c)
self.assertEqual(c.url, 'http://localhost')
self.assertEqual(c.headers, {
'User-Agent': 'Ö',
'Authorization': '中文'
})

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
"""Test if http-prompt is installed correctly."""
import subprocess
import pytest
from subprocess import PIPE
from .utils import get_http_prompt_path
from httpie.prompt import __version__
def run_http_prompt(args):
"""Run http-prompt from terminal."""
bin_path = get_http_prompt_path()
p = subprocess.Popen([bin_path] + args, stdin=PIPE, stdout=PIPE)
return p.communicate()
@pytest.mark.slow
def test_help():
out, err = run_http_prompt(['--help'])
assert out.startswith(b'Usage: http-prompt')
@pytest.mark.slow
def test_version():
out, err = run_http_prompt(['--version'])
version = __version__
if hasattr(version, 'encode'):
version = version.encode('ascii')
assert out.rstrip() == version

View File

@ -0,0 +1,79 @@
import os
import sys
import pexpect
import pytest
from .base import TempAppDirTestCase
from .utils import get_http_prompt_path
from httpie.prompt import config
class TestInteraction(TempAppDirTestCase):
def setUp(self):
super(TestInteraction, self).setUp()
# Use temporary directory as user config home.
# Will restore it in tearDown().
self.orig_config_home = os.getenv('XDG_CONFIG_HOME')
os.environ['XDG_CONFIG_HOME'] = self.temp_dir
# Make sure pexpect uses the same terminal environment
self.orig_term = os.getenv('TERM')
os.environ['TERM'] = 'screen-256color'
def tearDown(self):
super(TestInteraction, self).tearDown()
os.environ['XDG_CONFIG_HOME'] = self.orig_config_home
if self.orig_term:
os.environ['TERM'] = self.orig_term
else:
os.environ.pop('TERM', None)
def write_config(self, content):
config_path = config.get_user_config_path()
with open(config_path, 'a') as f:
f.write(content)
@pytest.mark.skipif(sys.platform == 'win32',
reason="pexpect doesn't work well on Windows")
@pytest.mark.slow
def test_interaction(self):
bin_path = get_http_prompt_path()
child = pexpect.spawn(bin_path, env=os.environ)
# TODO: Test more interaction
child.sendline('exit')
child.expect_exact('Goodbye!', timeout=20)
child.close()
@pytest.mark.skipif(sys.platform == 'win32',
reason="pexpect doesn't work well on Windows")
@pytest.mark.slow
def test_vi_mode(self):
self.write_config('vi = True\n')
bin_path = get_http_prompt_path()
child = pexpect.spawn(bin_path, env=os.environ)
child.expect_exact('http://localhost:8000>')
# Enter 'htpie', switch to command mode (ESC),
# move two chars left (hh), and insert (i) a 't'
child.send('htpie')
child.send('\x1b')
child.sendline('hhit')
child.expect_exact('http http://localhost:8000')
# Enter 'exit'
child.send('\x1b')
child.send('i')
child.sendline('exit')
child.expect_exact('Goodbye!', timeout=20)
child.close()

793
tests/prompt/test_lexer.py Normal file
View File

@ -0,0 +1,793 @@
import unittest
from pygments.token import Keyword, String, Text, Error, Name, Operator
from httpie.prompt.lexer import HttpPromptLexer
class LexerTestCase(unittest.TestCase):
def setUp(self):
self.lexer = HttpPromptLexer()
def get_tokens(self, text, filter_spaces=True):
tokens = self.lexer.get_tokens(text)
tokens = filter(lambda t: t[1], tokens)
if filter_spaces:
tokens = filter(lambda t: t[1].strip(), tokens)
return list(tokens)
class TestLexer_mutation(LexerTestCase):
def test_querystring(self):
self.assertEqual(self.get_tokens('foo==bar'), [
(Name, 'foo'),
(Operator, '=='),
(String, 'bar')
])
def test_body_param(self):
self.assertEqual(self.get_tokens('foo=bar'), [
(Name, 'foo'),
(Operator, '='),
(String, 'bar')
])
def test_header(self):
self.assertEqual(self.get_tokens('Accept:application/json'), [
(Name, 'Accept'),
(Operator, ':'),
(String, 'application/json')
])
def test_json_integer(self):
self.assertEqual(self.get_tokens('number:=1'), [
(Name, 'number'),
(Operator, ':='),
(String, '1')
])
def test_json_boolean(self):
self.assertEqual(self.get_tokens('enabled:=true'), [
(Name, 'enabled'),
(Operator, ':='),
(String, 'true')
])
def test_json_string(self):
self.assertEqual(self.get_tokens('name:="foo bar"'), [
(Name, 'name'),
(Operator, ':='),
(Text, '"'),
(String, 'foo bar'),
(Text, '"')
])
def test_json_array(self):
self.assertEqual(self.get_tokens('list:=[1,"two"]'), [
(Name, 'list'),
(Operator, ':='),
(String, '[1,"two"]'),
])
def test_json_array_quoted(self):
self.assertEqual(self.get_tokens("""list:='[1,"two"]'"""), [
(Name, 'list'),
(Operator, ':='),
(Text, "'"),
(String, '[1,"two"]'),
(Text, "'"),
])
def test_json_object(self):
self.assertEqual(self.get_tokens('object:={"id":123,"name":"foo"}'), [
(Name, 'object'),
(Operator, ':='),
(String, '{"id":123,"name":"foo"}'),
])
def test_json_object_quoted(self):
self.assertEqual(self.get_tokens("""object:='{"id": 123}'"""), [
(Name, 'object'),
(Operator, ':='),
(Text, "'"),
(String, '{"id": 123}'),
(Text, "'")
])
def test_json_escaped_colon(self):
self.assertEqual(self.get_tokens(r'where[id\:gt]:=2'), [
(Name, r'where[id\:gt]'),
(Operator, ':='),
(String, '2')
])
def test_body_param_escaped_equal(self):
self.assertEqual(self.get_tokens(r'foo\=bar=hello'), [
(Name, r'foo\=bar'),
(Operator, '='),
(String, 'hello')
])
def test_parameter_name_including_http_method_name(self):
self.assertEqual(self.get_tokens('heading==hello'), [
(Name, 'heading'),
(Operator, '=='),
(String, 'hello')
])
class TestLexer_cd(LexerTestCase):
def test_simple(self):
self.assertEqual(self.get_tokens('cd api/v1'), [
(Keyword, 'cd'),
(String, 'api/v1')
])
def test_double_quoted(self):
self.assertEqual(self.get_tokens('cd "api/v 1"'), [
(Keyword, 'cd'),
(Text, '"'),
(String, 'api/v 1'),
(Text, '"')
])
def test_single_quoted(self):
self.assertEqual(self.get_tokens("cd 'api/v 1'"), [
(Keyword, 'cd'),
(Text, "'"),
(String, 'api/v 1'),
(Text, "'")
])
def test_escape(self):
self.assertEqual(self.get_tokens(r"cd api/v\ 1"), [
(Keyword, 'cd'),
(String, r'api/v\ 1')
])
def test_second_path(self):
self.assertEqual(self.get_tokens(r"cd api v1"), [
(Keyword, 'cd'),
(String, 'api'),
(Error, 'v'),
(Error, '1')
])
def test_leading_trailing_spaces(self):
self.assertEqual(self.get_tokens(' cd api/v1 '), [
(Keyword, 'cd'),
(String, 'api/v1')
])
class TestLexer_ls(LexerTestCase):
def test_no_path(self):
self.assertEqual(self.get_tokens('ls'), [
(Keyword, 'ls')
])
def test_path(self):
self.assertEqual(self.get_tokens('ls api/v1'), [
(Keyword, 'ls'),
(String, 'api/v1')
])
def test_second_path(self):
self.assertEqual(self.get_tokens(r"ls api v1"), [
(Keyword, 'ls'),
(String, 'api'),
(Error, 'v'),
(Error, '1')
])
def test_leading_trailing_spaces(self):
self.assertEqual(self.get_tokens(' ls api/v1 '), [
(Keyword, 'ls'),
(String, 'api/v1')
])
def test_redirect(self):
self.assertEqual(self.get_tokens('ls api/v1 > endpoints.txt'), [
(Keyword, 'ls'),
(String, 'api/v1'),
(Operator, '>'),
(String, 'endpoints.txt')
])
class TestLexer_env(LexerTestCase):
def test_env_simple(self):
self.assertEqual(self.get_tokens('env'), [
(Keyword, 'env'),
])
def test_env_with_spaces(self):
self.assertEqual(self.get_tokens(' env '), [
(Keyword, 'env'),
])
def test_env_write(self):
self.assertEqual(self.get_tokens('env > /tmp/file.txt'), [
(Keyword, 'env'), (Operator, '>'),
(String, '/tmp/file.txt')
])
def test_env_append(self):
self.assertEqual(self.get_tokens('env >> /tmp/file.txt'), [
(Keyword, 'env'), (Operator, '>>'),
(String, '/tmp/file.txt')
])
def test_env_write_quoted_filename(self):
self.assertEqual(self.get_tokens('env > "/tmp/my file.txt"'), [
(Keyword, 'env'), (Operator, '>'),
(Text, '"'), (String, '/tmp/my file.txt'), (Text, '"')
])
def test_env_append_escaped_filename(self):
self.assertEqual(self.get_tokens(r'env >> /tmp/my\ file.txt'), [
(Keyword, 'env'), (Operator, '>>'),
(String, r'/tmp/my\ file.txt')
])
def test_env_pipe(self):
self.assertEqual(self.get_tokens('env | grep name'), [
(Keyword, 'env'), (Operator, '|'),
(Text, 'grep'), (Text, 'name')
])
class TestLexer_rm(LexerTestCase):
def test_header(self):
self.assertEqual(self.get_tokens('rm -h Accept'), [
(Keyword, 'rm'),
(Name, '-h'),
(String, 'Accept')
])
def test_header_escaped(self):
self.assertEqual(self.get_tokens(r'rm -h Custom\ Header'), [
(Keyword, 'rm'),
(Name, '-h'),
(String, r'Custom\ Header')
])
def test_querystring(self):
self.assertEqual(self.get_tokens('rm -q page'), [
(Keyword, 'rm'),
(Name, '-q'),
(String, 'page')
])
def test_querystring_double_quoted(self):
self.assertEqual(self.get_tokens('rm -q "page size"'), [
(Keyword, 'rm'),
(Name, '-q'),
(Text, '"'),
(String, 'page size'),
(Text, '"')
])
def test_body_param(self):
self.assertEqual(self.get_tokens('rm -b name'), [
(Keyword, 'rm'),
(Name, '-b'),
(String, 'name')
])
def test_body_param_single_quoted(self):
self.assertEqual(self.get_tokens("rm -b 'first name'"), [
(Keyword, 'rm'),
(Name, '-b'),
(Text, "'"),
(String, 'first name'),
(Text, "'")
])
def test_option(self):
self.assertEqual(self.get_tokens('rm -o --json'), [
(Keyword, 'rm'),
(Name, '-o'),
(String, '--json')
])
def test_reset(self):
self.assertEqual(self.get_tokens('rm *'), [
(Keyword, 'rm'),
(Name, '*')
])
def test_option_leading_trailing_spaces(self):
self.assertEqual(self.get_tokens(' rm -o --json '), [
(Keyword, 'rm'),
(Name, '-o'),
(String, '--json')
])
def test_invalid_type(self):
self.assertEqual(self.get_tokens('rm -a foo'), [
(Keyword, 'rm'),
(Error, '-'), (Error, 'a'),
(Error, 'f'), (Error, 'o'), (Error, 'o')
])
class TestLexer_help(LexerTestCase):
def test_help_simple(self):
self.assertEqual(self.get_tokens('help'), [
(Keyword, 'help')
])
def test_help_with_spaces(self):
self.assertEqual(self.get_tokens(' help '), [
(Keyword, 'help')
])
class TestLexer_source(LexerTestCase):
def test_source_simple_filename(self):
self.assertEqual(self.get_tokens('source file.txt'), [
(Keyword, 'source'), (String, 'file.txt')
])
def test_source_with_spaces(self):
self.assertEqual(self.get_tokens(' source file.txt '), [
(Keyword, 'source'), (String, 'file.txt')
])
def test_source_quoted_filename(self):
self.assertEqual(self.get_tokens("source '/tmp/my file.txt'"), [
(Keyword, 'source'),
(Text, "'"), (String, '/tmp/my file.txt'), (Text, "'")
])
def test_source_escaped_filename(self):
self.assertEqual(self.get_tokens(r"source /tmp/my\ file.txt"), [
(Keyword, 'source'), (String, r'/tmp/my\ file.txt')
])
class TestLexer_exec(LexerTestCase):
def test_exec_simple_filename(self):
self.assertEqual(self.get_tokens('exec file.txt'), [
(Keyword, 'exec'), (String, 'file.txt')
])
def test_exec_with_spaces(self):
self.assertEqual(self.get_tokens(' exec file.txt '), [
(Keyword, 'exec'), (String, 'file.txt')
])
def test_exec_quoted_filename(self):
self.assertEqual(self.get_tokens("exec '/tmp/my file.txt'"), [
(Keyword, 'exec'),
(Text, "'"), (String, '/tmp/my file.txt'), (Text, "'")
])
def test_exec_escaped_filename(self):
self.assertEqual(self.get_tokens(r"exec /tmp/my\ file.txt"), [
(Keyword, 'exec'), (String, r'/tmp/my\ file.txt')
])
class TestLexer_exit(LexerTestCase):
def test_exit_simple(self):
self.assertEqual(self.get_tokens('exit'), [
(Keyword, 'exit')
])
def test_exit_with_spaces(self):
self.assertEqual(self.get_tokens(' exit '), [
(Keyword, 'exit')
])
class TestLexerPreview(LexerTestCase):
def test_httpie_without_action(self):
cmd = 'httpie http://example.com name=jack'
self.assertEqual(self.get_tokens(cmd), [
(Keyword, 'httpie'),
(String, 'http://example.com'),
(Name, 'name'), (Operator, '='), (String, 'jack')
])
def test_httpie_without_action_and_url(self):
cmd = 'httpie name=jack Accept:*/*'
self.assertEqual(self.get_tokens(cmd), [
(Keyword, 'httpie'),
(Name, 'name'), (Operator, '='), (String, 'jack'),
(Name, 'Accept'), (Operator, ':'), (String, '*/*')
])
def test_httpie_absolute_url(self):
cmd = 'httpie post http://example.com name=jack'
self.assertEqual(self.get_tokens(cmd), [
(Keyword, 'httpie'), (Keyword, 'post'),
(String, 'http://example.com'),
(Name, 'name'), (Operator, '='), (String, 'jack')
])
def test_httpie_option_first(self):
self.assertEqual(self.get_tokens('httpie post --form name=jack'), [
(Keyword, 'httpie'), (Keyword, 'post'),
(Name, '--form'),
(Name, 'name'), (Operator, '='), (String, 'jack')
])
def test_httpie_body_param_first(self):
self.assertEqual(self.get_tokens('httpie post name=jack --form'), [
(Keyword, 'httpie'), (Keyword, 'post'),
(Name, 'name'), (Operator, '='), (String, 'jack'),
(Name, '--form')
])
def test_httpie_options(self):
self.assertEqual(self.get_tokens('httpie options test --body'), [
(Keyword, 'httpie'), (Keyword, 'options'),
(String, 'test'), (Name, '--body')
])
def test_httpie_relative_path(self):
tokens = self.get_tokens('httpie /api/test name==foo',
filter_spaces=False)
self.assertEqual(tokens, [
(Keyword, 'httpie'), (Text, ' '),
(String, '/api/test'), (Text, ' '),
(Name, 'name'), (Operator, '=='), (String, 'foo'),
(Text, '\n')
])
class TestShellCode(LexerTestCase):
def test_unquoted_querystring(self):
self.assertEqual(self.get_tokens('`echo name`==john'), [
(Text, '`'),
(Name.Builtin, 'echo'),
(Text, 'name'),
(Text, '`'),
(Operator, '=='),
(String, 'john')
])
self.assertEqual(self.get_tokens('name==`echo john`'), [
(Name, 'name'),
(Operator, '=='),
(Text, '`'),
(Name.Builtin, 'echo'),
(Text, 'john'),
(Text, '`')
])
def test_unquoted_bodystring(self):
self.assertEqual(self.get_tokens('`echo name`=john'), [
(Text, '`'),
(Name.Builtin, 'echo'),
(Text, 'name'),
(Text, '`'),
(Operator, '='),
(String, 'john')
])
self.assertEqual(self.get_tokens('name=`echo john`'), [
(Name, 'name'),
(Operator, '='),
(Text, '`'),
(Name.Builtin, 'echo'),
(Text, 'john'),
(Text, '`')
])
def test_header_option_value(self):
self.assertEqual(self.get_tokens('Accept:`echo "application/json"`'), [
(Name, 'Accept'),
(Operator, ':'),
(Text, '`'),
(Name.Builtin, 'echo'),
(String.Double, '"application/json"'),
(Text, '`'),
])
def test_httpie_body_param(self):
self.assertEqual(self.get_tokens('httpie post name=`echo john`'), [
(Keyword, 'httpie'),
(Keyword, 'post'),
(Name, 'name'),
(Operator, '='),
(Text, '`'),
(Name.Builtin, 'echo'),
(Text, 'john'),
(Text, '`'),
])
def test_httpie_post_pipe(self):
self.assertEqual(self.get_tokens('httpie post | tee "/tmp/test"'), [
(Keyword, 'httpie'),
(Keyword, 'post'),
(Operator, '|'),
(Text, 'tee'),
(String.Double, '"/tmp/test"'),
])
def test_post_pipe(self):
self.assertEqual(self.get_tokens('post | tee "/tmp/test"'), [
(Keyword, 'post'),
(Operator, '|'),
(Text, 'tee'),
(String.Double, '"/tmp/test"'),
])
class TestLexerPreviewRedirection(LexerTestCase):
def test_httpie_write(self):
self.assertEqual(self.get_tokens('httpie > file.txt'), [
(Keyword, 'httpie'),
(Operator, '>'), (String, 'file.txt')
])
def test_httpie_write_without_spaces(self):
self.assertEqual(self.get_tokens('httpie>file.txt'), [
(Keyword, 'httpie'),
(Operator, '>'), (String, 'file.txt')
])
def test_httpie_append(self):
self.assertEqual(self.get_tokens('httpie >> file.txt'), [
(Keyword, 'httpie'),
(Operator, '>>'), (String, 'file.txt')
])
def test_httpie_append_without_spaces(self):
self.assertEqual(self.get_tokens('httpie>>file.txt'), [
(Keyword, 'httpie'),
(Operator, '>>'), (String, 'file.txt')
])
def test_httpie_write_with_post_param(self):
self.assertEqual(self.get_tokens('httpie post name=jack > file.txt'), [
(Keyword, 'httpie'), (Keyword, 'post'),
(Name, 'name'), (Operator, '='), (String, 'jack'),
(Operator, '>'), (String, 'file.txt')
])
def test_httpie_append_with_post_param(self):
self.assertEqual(self.get_tokens('httpie post name=doe >> file.txt'), [
(Keyword, 'httpie'), (Keyword, 'post'),
(Name, 'name'), (Operator, '='), (String, 'doe'),
(Operator, '>>'), (String, 'file.txt')
])
def test_httpie_write_quoted_filename(self):
self.assertEqual(self.get_tokens("httpie > 'my file.txt'"), [
(Keyword, 'httpie'), (Operator, '>'),
(Text, "'"), (String, 'my file.txt'), (Text, "'")
])
def test_httpie_append_quoted_filename(self):
self.assertEqual(self.get_tokens('httpie >> "my file.txt"'), [
(Keyword, 'httpie'), (Operator, '>>'),
(Text, '"'), (String, 'my file.txt'), (Text, '"')
])
def test_httpie_append_with_many_params(self):
command = ("httpie post --auth user:pass --verify=no "
"name='john doe' page==2 >> file.txt")
self.assertEqual(self.get_tokens(command), [
(Keyword, 'httpie'), (Keyword, 'post'),
(Name, '--auth'), (String, 'user:pass'),
(Name, '--verify'), (Operator, '='), (String, 'no'),
(Name, 'name'), (Operator, '='),
(Text, "'"), (String, 'john doe'), (Text, "'"),
(Name, 'page'), (Operator, '=='), (String, '2'),
(Operator, '>>'), (String, 'file.txt')
])
def test_curl_write(self):
self.assertEqual(self.get_tokens('curl > file.txt'), [
(Keyword, 'curl'),
(Operator, '>'), (String, 'file.txt')
])
def test_curl_write_without_spaces(self):
self.assertEqual(self.get_tokens('curl>file.txt'), [
(Keyword, 'curl'),
(Operator, '>'), (String, 'file.txt')
])
def test_curl_append(self):
self.assertEqual(self.get_tokens('curl >> file.txt'), [
(Keyword, 'curl'),
(Operator, '>>'), (String, 'file.txt')
])
def test_curl_append_without_spaces(self):
self.assertEqual(self.get_tokens('curl>>file.txt'), [
(Keyword, 'curl'),
(Operator, '>>'), (String, 'file.txt')
])
def test_curl_write_with_post_param(self):
self.assertEqual(self.get_tokens('curl post name=jack > file.txt'), [
(Keyword, 'curl'), (Keyword, 'post'),
(Name, 'name'), (Operator, '='), (String, 'jack'),
(Operator, '>'), (String, 'file.txt')
])
def test_curl_append_with_post_param(self):
self.assertEqual(self.get_tokens('curl post name=doe >> file.txt'), [
(Keyword, 'curl'), (Keyword, 'post'),
(Name, 'name'), (Operator, '='), (String, 'doe'),
(Operator, '>>'), (String, 'file.txt')
])
def test_curl_write_quoted_filename(self):
self.assertEqual(self.get_tokens("curl > 'my file.txt'"), [
(Keyword, 'curl'), (Operator, '>'),
(Text, "'"), (String, 'my file.txt'), (Text, "'")
])
def test_curl_append_quoted_filename(self):
self.assertEqual(self.get_tokens('curl >> "my file.txt"'), [
(Keyword, 'curl'), (Operator, '>>'),
(Text, '"'), (String, 'my file.txt'), (Text, '"')
])
def test_curl_append_with_many_params(self):
command = ("curl post --auth user:pass --verify=no "
"name='john doe' page==2 >> file.txt")
self.assertEqual(self.get_tokens(command), [
(Keyword, 'curl'), (Keyword, 'post'),
(Name, '--auth'), (String, 'user:pass'),
(Name, '--verify'), (Operator, '='), (String, 'no'),
(Name, 'name'), (Operator, '='),
(Text, "'"), (String, 'john doe'), (Text, "'"),
(Name, 'page'), (Operator, '=='), (String, '2'),
(Operator, '>>'), (String, 'file.txt')
])
class TestLexerAction(LexerTestCase):
def test_get(self):
self.assertEqual(self.get_tokens('get'), [
(Keyword, 'get')
])
def test_post_with_spaces(self):
self.assertEqual(self.get_tokens(' post '), [
(Keyword, 'post')
])
def test_capital_head(self):
self.assertEqual(self.get_tokens('HEAD'), [
(Keyword, 'HEAD')
])
def test_delete_random_capitals(self):
self.assertEqual(self.get_tokens('dElETe'), [
(Keyword, 'dElETe')
])
def test_patch(self):
self.assertEqual(self.get_tokens('patch'), [
(Keyword, 'patch')
])
def test_get_with_querystring_params(self):
command = 'get page==10 id==200'
self.assertEqual(self.get_tokens(command), [
(Keyword, 'get'),
(Name, 'page'), (Operator, '=='), (String, '10'),
(Name, 'id'), (Operator, '=='), (String, '200')
])
def test_capital_get_with_querystring_params(self):
command = 'GET page==10 id==200'
self.assertEqual(self.get_tokens(command), [
(Keyword, 'GET'),
(Name, 'page'), (Operator, '=='), (String, '10'),
(Name, 'id'), (Operator, '=='), (String, '200')
])
def test_post_with_body_params(self):
command = 'post name="john doe" username=john'
self.assertEqual(self.get_tokens(command), [
(Keyword, 'post'), (Name, 'name'), (Operator, '='),
(Text, '"'), (String, 'john doe'), (Text, '"'),
(Name, 'username'), (Operator, '='), (String, 'john')
])
def test_post_with_spaces_and_body_params(self):
command = ' post name="john doe" username=john '
self.assertEqual(self.get_tokens(command), [
(Keyword, 'post'), (Name, 'name'), (Operator, '='),
(Text, '"'), (String, 'john doe'), (Text, '"'),
(Name, 'username'), (Operator, '='), (String, 'john')
])
def test_options(self):
self.assertEqual(self.get_tokens('options'), [
(Keyword, 'options')
])
def test_post_relative_path(self):
tokens = self.get_tokens('post /api/test name=foo',
filter_spaces=False)
self.assertEqual(tokens, [
(Keyword, 'post'), (Text, ' '),
(String, '/api/test'), (Text, ' '),
(Name, 'name'), (Operator, '='), (String, 'foo'),
(Text, '\n')
])
class TestLexerActionRedirection(LexerTestCase):
def test_get_write(self):
self.assertEqual(self.get_tokens('get > file.txt'), [
(Keyword, 'get'), (Operator, '>'), (String, 'file.txt')
])
def test_get_write_quoted_filename(self):
self.assertEqual(self.get_tokens('get > "/tmp/my file.txt"'), [
(Keyword, 'get'), (Operator, '>'),
(Text, '"'), (String, '/tmp/my file.txt'), (Text, '"')
])
def test_get_append(self):
self.assertEqual(self.get_tokens('get >> file.txt'), [
(Keyword, 'get'), (Operator, '>>'), (String, 'file.txt')
])
def test_get_append_escaped_filename(self):
self.assertEqual(self.get_tokens(r'get >> /tmp/my\ file.txt'), [
(Keyword, 'get'), (Operator, '>>'),
(String, r'/tmp/my\ file.txt')
])
def test_post_append_with_spaces(self):
self.assertEqual(self.get_tokens(' post >> file.txt'), [
(Keyword, 'post'), (Operator, '>>'), (String, 'file.txt')
])
def test_capital_head_write(self):
self.assertEqual(self.get_tokens('HEAD > file.txt'), [
(Keyword, 'HEAD'), (Operator, '>'), (String, 'file.txt')
])
def test_get_append_with_querystring_params(self):
command = 'get page==10 id==200 >> /tmp/file.txt'
self.assertEqual(self.get_tokens(command), [
(Keyword, 'get'),
(Name, 'page'), (Operator, '=='), (String, '10'),
(Name, 'id'), (Operator, '=='), (String, '200'),
(Operator, '>>'), (String, '/tmp/file.txt')
])
def test_post_write_escaped_filename_with_body_params(self):
command = r'post name="john doe" username=john > /tmp/my\ file.txt'
self.assertEqual(self.get_tokens(command), [
(Keyword, 'post'), (Name, 'name'), (Operator, '='),
(Text, '"'), (String, 'john doe'), (Text, '"'),
(Name, 'username'), (Operator, '='), (String, 'john'),
(Operator, '>'), (String, r'/tmp/my\ file.txt')
])
def test_post_append_with_spaces_and_body_params(self):
command = ' post name="john doe" username=john >> /tmp/file.txt '
self.assertEqual(self.get_tokens(command), [
(Keyword, 'post'), (Name, 'name'), (Operator, '='),
(Text, '"'), (String, 'john doe'), (Text, '"'),
(Name, 'username'), (Operator, '='), (String, 'john'),
(Operator, '>>'), (String, '/tmp/file.txt')
])

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