forked from extern/httpie-cli
Compare commits
1 Commits
2.6.0
...
mickael/os
Author | SHA1 | Date | |
---|---|---|---|
279e387d86 |
7
.github/workflows/docs-update-install.yml
vendored
7
.github/workflows/docs-update-install.yml
vendored
@ -3,7 +3,6 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/docs-update-install.yml
|
|
||||||
- docs/installation/*
|
- docs/installation/*
|
||||||
|
|
||||||
# Allow to call the workflow manually
|
# Allow to call the workflow manually
|
||||||
@ -22,10 +21,6 @@ jobs:
|
|||||||
- uses: Automattic/action-commit-to-branch@master
|
- uses: Automattic/action-commit-to-branch@master
|
||||||
with:
|
with:
|
||||||
branch: master
|
branch: master
|
||||||
commit_message: |
|
commit_message: Auto-update installation instructions in the docs
|
||||||
Auto-update install docs
|
|
||||||
|
|
||||||
Via .github/workflows/docs-update-install.yml
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
22
.github/workflows/release-snap.yml
vendored
22
.github/workflows/release-snap.yml
vendored
@ -1,22 +0,0 @@
|
|||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
branch:
|
|
||||||
description: "The branch, tag or SHA to release from"
|
|
||||||
required: true
|
|
||||||
default: "master"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
snap:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.inputs.branch }}
|
|
||||||
- uses: snapcore/action-build@v1
|
|
||||||
id: build
|
|
||||||
- uses: snapcore/action-publish@v1
|
|
||||||
with:
|
|
||||||
store_login: ${{ secrets.SNAP_STORE_LOGIN }}
|
|
||||||
snap: ${{ steps.build.outputs.snap }}
|
|
||||||
release: edge
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -17,8 +17,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
ref: ${{ github.event.inputs.branch }}
|
|
||||||
- uses: actions/setup-python@v2
|
- uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.9
|
||||||
|
@ -3,15 +3,14 @@
|
|||||||
This document records all notable changes to [HTTPie](https://httpie.io).
|
This document records all notable changes to [HTTPie](https://httpie.io).
|
||||||
This project adheres to [Semantic Versioning](https://semver.org/).
|
This project adheres to [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
## [2.6.0](https://github.com/httpie/httpie/compare/2.5.0...2.6.0) (2021-10-14)
|
## [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 support for formatting & coloring of JSON bodies preceded by non-JSON data (e.g., an XXSI prefix). ([#1130](https://github.com/httpie/httpie/issues/1130))
|
||||||
- Added charset auto-detection when `Content-Type` doesn’t include it. ([#1110](https://github.com/httpie/httpie/issues/1110), [#1168](https://github.com/httpie/httpie/issues/1168))
|
|
||||||
- Added `--response-charset` to allow overriding the response encoding for terminal display purposes. ([#1168](https://github.com/httpie/httpie/issues/1168))
|
- Added `--response-charset` to allow overriding the response encoding for terminal display purposes. ([#1168](https://github.com/httpie/httpie/issues/1168))
|
||||||
- Added `--response-mime` to allow overriding the response mime type for coloring and formatting for the terminal. ([#1168](https://github.com/httpie/httpie/issues/1168))
|
- Added `--response-mime` to allow overriding the response mime type for coloring and formatting for the terminal. ([#1168](https://github.com/httpie/httpie/issues/1168))
|
||||||
- Added the ability to silence warnings through using `-q` or `--quiet` twice (e.g. `-qq`) ([#1175](https://github.com/httpie/httpie/issues/1175))
|
- Improved handling of responses with incorrect `Content-Type`. ([#1110](https://github.com/httpie/httpie/issues/1110), [#1168](https://github.com/httpie/httpie/issues/1168))
|
||||||
- Added installed plugin list to `--debug` output. ([#1165](https://github.com/httpie/httpie/issues/1165))
|
- Installed plugins are now listed in `--debug` output. ([#1165](https://github.com/httpie/httpie/issues/1165))
|
||||||
- Fixed duplicate keys preservation in JSON data. ([#1163](https://github.com/httpie/httpie/issues/1163))
|
- 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)
|
## [2.5.0](https://github.com/httpie/httpie/compare/2.4.0...2.5.0) (2021-09-06)
|
||||||
|
|
||||||
|
172
docs/README.md
172
docs/README.md
@ -63,13 +63,13 @@ Do not edit here, but in docs/installation/.
|
|||||||
Please make sure you have Python 3.6 or newer (`python --version`).
|
Please make sure you have Python 3.6 or newer (`python --version`).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ python -m pip install --upgrade pip wheel
|
$ python -m pip install --upgrade pip wheel
|
||||||
$ python -m pip install httpie
|
$ python -m pip install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ python -m pip install --upgrade pip wheel
|
$ python -m pip install --upgrade pip wheel
|
||||||
$ python -m pip install --upgrade httpie
|
$ python -m pip install --upgrade httpie
|
||||||
```
|
```
|
||||||
@ -78,47 +78,61 @@ $ python -m pip install --upgrade httpie
|
|||||||
|
|
||||||
#### Homebrew
|
#### Homebrew
|
||||||
|
|
||||||
To install [Homebrew](https://brew.sh/), see [its installation](https://docs.brew.sh/Installation).
|
To install [Homebrew](https://brew.sh/) follow [installation instructions](https://docs.brew.sh/Installation).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ brew update
|
$ brew update
|
||||||
$ brew install httpie
|
$ brew install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ brew update
|
$ brew update
|
||||||
$ brew upgrade httpie
|
$ brew upgrade httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
#### MacPorts
|
#### MacPorts
|
||||||
|
|
||||||
To install [MacPorts](https://www.macports.org/), see [its installation](https://www.macports.org/install.php).
|
To install [MacPorts](https://www.macports.org/) follow [installation instructions](https://www.macports.org/install.php).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ port selfupdate
|
$ port selfupdate
|
||||||
$ port install httpie
|
$ port install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ port selfupdate
|
$ port selfupdate
|
||||||
$ port upgrade httpie
|
$ port upgrade httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Spack (macOS)
|
#### Snapcraft (macOS)
|
||||||
|
|
||||||
To install [Spack](https://spack.readthedocs.io/en/latest/index.html), see [its installation](https://spack.readthedocs.io/en/latest/getting_started.html#installation).
|
To install [Snapcraft](https://snapcraft.io/) follow [installation instructions](https://snapcraft.io/docs/installing-snapd).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# 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
|
$ spack install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ spack install httpie
|
$ spack install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -126,15 +140,15 @@ $ spack install httpie
|
|||||||
|
|
||||||
#### Chocolatey
|
#### Chocolatey
|
||||||
|
|
||||||
To install [Chocolatey](https://chocolatey.org/), see [its installation](https://chocolatey.org/install).
|
To install [Chocolatey](https://chocolatey.org/) follow [installation instructions](https://chocolatey.org/install).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ choco install httpie
|
$ choco install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ choco upgrade httpie
|
$ choco upgrade httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -142,30 +156,30 @@ $ choco upgrade httpie
|
|||||||
|
|
||||||
#### Snapcraft (Linux)
|
#### Snapcraft (Linux)
|
||||||
|
|
||||||
To install [Snapcraft](https://snapcraft.io/), see [its installation](https://snapcraft.io/docs/installing-snapd).
|
To install [Snapcraft](https://snapcraft.io/) follow [installation instructions](https://snapcraft.io/docs/installing-snapd).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ snap install httpie
|
$ snap install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ snap refresh httpie
|
$ snap refresh httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Linuxbrew
|
#### Linuxbrew
|
||||||
|
|
||||||
To install [Linuxbrew](https://docs.brew.sh/Homebrew-on-Linux), see [its installation](https://docs.brew.sh/Homebrew-on-Linux#install).
|
To install [Linuxbrew](https://docs.brew.sh/Homebrew-on-Linux) follow [installation instructions](https://docs.brew.sh/Homebrew-on-Linux#install).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ brew update
|
$ brew update
|
||||||
$ brew install httpie
|
$ brew install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ brew update
|
$ brew update
|
||||||
$ brew upgrade httpie
|
$ brew upgrade httpie
|
||||||
```
|
```
|
||||||
@ -175,13 +189,13 @@ $ brew upgrade httpie
|
|||||||
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.
|
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
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ apt update
|
$ apt update
|
||||||
$ apt install httpie
|
$ apt install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ apt update
|
$ apt update
|
||||||
$ apt upgrade httpie
|
$ apt upgrade httpie
|
||||||
```
|
```
|
||||||
@ -189,13 +203,13 @@ $ apt upgrade httpie
|
|||||||
#### Fedora
|
#### Fedora
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ dnf update
|
$ dnf update
|
||||||
$ dnf install httpie
|
$ dnf install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ dnf update
|
$ dnf update
|
||||||
$ dnf upgrade httpie
|
$ dnf upgrade httpie
|
||||||
```
|
```
|
||||||
@ -205,14 +219,14 @@ $ dnf upgrade httpie
|
|||||||
Also works for other RHEL-derived distributions like ClearOS, Oracle Linux, etc.
|
Also works for other RHEL-derived distributions like ClearOS, Oracle Linux, etc.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ yum update
|
$ yum update
|
||||||
$ yum install epel-release
|
$ yum install epel-release
|
||||||
$ yum install httpie
|
$ yum install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ yum update
|
$ yum update
|
||||||
$ yum upgrade httpie
|
$ yum upgrade httpie
|
||||||
```
|
```
|
||||||
@ -220,13 +234,13 @@ $ yum upgrade httpie
|
|||||||
#### Alpine Linux
|
#### Alpine Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ apk update
|
$ apk update
|
||||||
$ apk add httpie
|
$ apk add httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ apk update
|
$ apk update
|
||||||
$ apk add --upgrade httpie
|
$ apk add --upgrade httpie
|
||||||
```
|
```
|
||||||
@ -234,13 +248,13 @@ $ apk add --upgrade httpie
|
|||||||
#### Gentoo
|
#### Gentoo
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ emerge --sync
|
$ emerge --sync
|
||||||
$ emerge httpie
|
$ emerge httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ emerge --sync
|
$ emerge --sync
|
||||||
$ emerge --update httpie
|
$ emerge --update httpie
|
||||||
```
|
```
|
||||||
@ -250,40 +264,40 @@ $ emerge --update httpie
|
|||||||
Also works for other Arch-derived distributions like ArcoLinux, EndeavourOS, Artix Linux, etc.
|
Also works for other Arch-derived distributions like ArcoLinux, EndeavourOS, Artix Linux, etc.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ pacman -Sy httpie
|
$ pacman -Sy httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ pacman -Syu httpie
|
$ pacman -Syu httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Void Linux
|
#### Void Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ xbps-install -Su
|
$ xbps-install -Su
|
||||||
$ xbps-install -S httpie
|
$ xbps-install -S httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ xbps-install -Su
|
$ xbps-install -Su
|
||||||
$ xbps-install -Su httpie
|
$ xbps-install -Su httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Spack (Linux)
|
#### Spack (Linux)
|
||||||
|
|
||||||
To install [Spack](https://spack.readthedocs.io/en/latest/index.html), see [its installation](https://spack.readthedocs.io/en/latest/getting_started.html#installation).
|
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
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ spack install httpie
|
$ spack install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ spack install httpie
|
$ spack install httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -292,12 +306,12 @@ $ spack install httpie
|
|||||||
#### FreshPorts
|
#### FreshPorts
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ pkg install www/py-httpie
|
$ pkg install www/py-httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ pkg upgrade www/py-httpie
|
$ pkg upgrade www/py-httpie
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -334,7 +348,7 @@ Verify that now you have the [current development version identifier](https://gi
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ http --version
|
$ http --version
|
||||||
# 2.6.0
|
# 2.6.0.dev0
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@ -1233,13 +1247,6 @@ This doesn’t affect output to a file via `--output` or `--download`.
|
|||||||
### Conditional body download
|
### Conditional body download
|
||||||
|
|
||||||
As an optimization, the response body is downloaded from the server only if it’s part of the output.
|
As an optimization, the response body is downloaded from the server only if it’s part of the output.
|
||||||
# Print the intermediary requests/responses differently than the final one:
|
|
||||||
$ http -A digest -a foo:bar --all -p Hh -P H pie.dev/digest-auth/auth/foo/bar
|
|
||||||
```
|
|
||||||
|
|
||||||
### Conditional body download
|
|
||||||
|
|
||||||
As an optimization, the response body is downloaded from the server only if it’s part of the output.
|
|
||||||
This is similar to performing a `HEAD` request, except that it applies to any HTTP method you use.
|
This is similar to performing a `HEAD` request, except that it applies to any HTTP method you use.
|
||||||
|
|
||||||
Let’s say that there is an API that returns the whole resource when it is updated, but you are only interested in the response headers to see the status code after an update:
|
Let’s say that there is an API that returns the whole resource when it is updated, but you are only interested in the response headers to see the status code after an update:
|
||||||
@ -1290,8 +1297,7 @@ The universal method for passing request data is through redirected `stdin`
|
|||||||
```
|
```
|
||||||
|
|
||||||
You can also use a Bash *here string*:
|
You can also use a Bash *here string*:
|
||||||
You can also use a Bash *here string*:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ http pie.dev/post <<<'{"name": "John"}'
|
$ http pie.dev/post <<<'{"name": "John"}'
|
||||||
```
|
```
|
||||||
@ -1401,32 +1407,16 @@ $ http --chunked pie.dev/post @files/data.xml
|
|||||||
Use one of these options to control output processing:
|
Use one of these options to control output processing:
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
|
|
||||||
Syntax highlighting is applied to HTTP headers and bodies (where it makes sense).
|
|
||||||
You can choose your preferred color scheme via the --style option if you don’t like the default one.
|
|
||||||
There are dozens of styles available, here are just a few notable ones:
|
|
||||||
|
|
||||||
| Style | Description |
|
|
||||||
| --------: | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `auto` | Follows your terminal ANSI color styles. This is the default style used by HTTPie |
|
|
||||||
| `default` | Default styles of the underlying Pygments library. Not actually used by default by HTTPie. You can enable it with `--style=default` |
|
|
||||||
| `monokai` | A popular color scheme. Enable with `--style=monokai` |
|
|
||||||
| `fruity` | A bold, colorful scheme. Enable with `--style=fruity` |
|
|
||||||
| … | See `$ http --help` for all the possible `--style` values |
|
|
||||||
|
|
||||||
Use one of these options to control output processing:
|
|
||||||
|
|
||||||
| Option | Description |
|
|
||||||
| ----------------: | ------------------------------------------------------------- |
|
|
||||||
| `--pretty=all` | Apply both colors and formatting. Default for terminal output |
|
|
||||||
| ----------------: | ------------------------------------------------------------- |
|
| ----------------: | ------------------------------------------------------------- |
|
||||||
| `--pretty=all` | Apply both colors and formatting. Default for terminal output |
|
| `--pretty=all` | Apply both colors and formatting. Default for terminal output |
|
||||||
| `--pretty=colors` | Apply colors |
|
| `--pretty=colors` | Apply colors |
|
||||||
| `--pretty=format` | Apply formatting |
|
| `--pretty=format` | Apply formatting |
|
||||||
| `--pretty=none` | Disables output processing. Default for redirected output |
|
| `--pretty=none` | Disables output processing. Default for redirected output |
|
||||||
|
|
||||||
|
Formatting has the following effects:
|
||||||
|
|
||||||
- HTTP headers are sorted by name.
|
- HTTP headers are sorted by name.
|
||||||
$ http --response-mime=text/yaml pie.dev/get
|
- JSON data is indented, sorted by keys, and unicode escapes are converted
|
||||||
to the characters they represent.
|
to the characters they represent.
|
||||||
- XML and XHTML data is indented.
|
- XML and XHTML data is indented.
|
||||||
|
|
||||||
@ -1446,12 +1436,6 @@ Use one of these options to control output processing:
|
|||||||
| `xml.format` | `true` | N/A |
|
| `xml.format` | `true` | N/A |
|
||||||
| `xml.indent` | `2` | N/A |
|
| `xml.indent` | `2` | N/A |
|
||||||
|
|
||||||
| `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
|
For example, this is how you would disable the default header and JSON key
|
||||||
sorting, and specify a custom JSON indent size:
|
sorting, and specify a custom JSON indent size:
|
||||||
|
|
||||||
@ -1488,6 +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.
|
Given the encoding is not sent by the server, HTTPie will auto-detect it.
|
||||||
|
|
||||||
|
### Redirected output
|
||||||
|
|
||||||
|
HTTPie uses a different set of defaults for redirected output than for [terminal output](#terminal-output).
|
||||||
|
The differences being:
|
||||||
|
|
||||||
|
- Formatting and colors aren’t applied (unless `--pretty` is specified).
|
||||||
|
- Only the response body is printed (unless one of the [output options](#output-options) is set).
|
||||||
|
- Also, binary data isn’t suppressed.
|
||||||
|
|
||||||
|
The reason is to make piping HTTPie’s 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.
|
||||||
|
|
||||||
|
Download a file:
|
||||||
|
|
||||||
|
```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:
|
Force colorizing and formatting, and show both the request and the response in `less` pager:
|
||||||
|
|
||||||
@ -1550,13 +1557,20 @@ Content-Type: application/octet-stream
|
|||||||
```
|
```
|
||||||
|
|
||||||
```http
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
Content-Disposition: attachment; filename=httpie-master.tar.gz
|
Content-Disposition: attachment; filename=httpie-master.tar.gz
|
||||||
Content-Length: 257336
|
Content-Length: 257336
|
||||||
### Downloaded filename
|
Content-Type: application/x-gzip
|
||||||
|
|
||||||
Downloading 251.30 kB to "httpie-master.tar.gz"
|
Downloading 251.30 kB to "httpie-master.tar.gz"
|
||||||
There are three mutually exclusive ways through which HTTPie determines
|
Done. 251.30 kB in 2.73862s (91.76 kB/s)
|
||||||
the output filename (with decreasing priority):
|
```
|
||||||
|
|
||||||
|
### 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`).
|
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.
|
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.
|
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.
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
Here we maintain a database of contributors, from which we generate credits on release blog posts and social medias.
|
|
||||||
|
|
||||||
For the HTTPie blog see: <https://httpie.io/blog>.
|
|
@ -1,280 +0,0 @@
|
|||||||
"""
|
|
||||||
Generate the contributors database.
|
|
||||||
|
|
||||||
FIXME: replace `requests` calls with the HTTPie API, when available.
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from copy import deepcopy
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from subprocess import check_output
|
|
||||||
from time import sleep
|
|
||||||
from typing import Any, Dict, Optional, Set
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
FullNames = Set[str]
|
|
||||||
GitHubLogins = Set[str]
|
|
||||||
Person = Dict[str, str]
|
|
||||||
People = Dict[str, Person]
|
|
||||||
UserInfo = Dict[str, Any]
|
|
||||||
|
|
||||||
CO_AUTHORS = re.compile(r'Co-authored-by: ([^<]+) <').finditer
|
|
||||||
API_URL = 'https://api.github.com'
|
|
||||||
REPO = OWNER = 'httpie'
|
|
||||||
REPO_URL = f'{API_URL}/repos/{REPO}/{OWNER}'
|
|
||||||
|
|
||||||
HERE = Path(__file__).parent
|
|
||||||
DB_FILE = HERE / 'people.json'
|
|
||||||
|
|
||||||
DEFAULT_PERSON: Person = {'committed': [], 'reported': [], 'github': '', 'twitter': ''}
|
|
||||||
SKIPPED_LABELS = {'invalid'}
|
|
||||||
|
|
||||||
GITHUB_TOKEN = os.getenv('GITHUB_TOKEN')
|
|
||||||
assert GITHUB_TOKEN, 'GITHUB_TOKEN envar is missing'
|
|
||||||
|
|
||||||
|
|
||||||
class FinishedForNow(Exception):
|
|
||||||
"""Raised when remaining GitHub rate limit is zero."""
|
|
||||||
|
|
||||||
|
|
||||||
def main(previous_release: str, current_release: str) -> int:
|
|
||||||
since = release_date(previous_release)
|
|
||||||
until = release_date(current_release)
|
|
||||||
|
|
||||||
contributors = load_awesome_people()
|
|
||||||
try:
|
|
||||||
committers = find_committers(since, until)
|
|
||||||
reporters = find_reporters(since, until)
|
|
||||||
except Exception as exc:
|
|
||||||
# We want to save what we fetched so far. So pass.
|
|
||||||
print(' !! ', exc)
|
|
||||||
|
|
||||||
try:
|
|
||||||
merge_all_the_people(current_release, contributors, committers, reporters)
|
|
||||||
fetch_missing_users_details(contributors)
|
|
||||||
except FinishedForNow:
|
|
||||||
# We want to save what we fetched so far. So pass.
|
|
||||||
print(' !! Committers:', committers)
|
|
||||||
print(' !! Reporters:', reporters)
|
|
||||||
exit_status = 1
|
|
||||||
else:
|
|
||||||
exit_status = 0
|
|
||||||
|
|
||||||
save_awesome_people(contributors)
|
|
||||||
return exit_status
|
|
||||||
|
|
||||||
|
|
||||||
def find_committers(since: str, until: str) -> FullNames:
|
|
||||||
url = f'{REPO_URL}/commits'
|
|
||||||
page = 1
|
|
||||||
per_page = 100
|
|
||||||
params = {
|
|
||||||
'since': since,
|
|
||||||
'until': until,
|
|
||||||
'per_page': per_page,
|
|
||||||
}
|
|
||||||
committers: FullNames = set()
|
|
||||||
|
|
||||||
while 'there are commits':
|
|
||||||
params['page'] = page
|
|
||||||
data = fetch(url, params=params)
|
|
||||||
|
|
||||||
for item in data:
|
|
||||||
commit = item['commit']
|
|
||||||
committers.add(commit['author']['name'])
|
|
||||||
debug(' >>> Commit', item['html_url'])
|
|
||||||
for co_author in CO_AUTHORS(commit['message']):
|
|
||||||
name = co_author.group(1)
|
|
||||||
committers.add(name)
|
|
||||||
|
|
||||||
if len(data) < per_page:
|
|
||||||
break
|
|
||||||
page += 1
|
|
||||||
|
|
||||||
return committers
|
|
||||||
|
|
||||||
|
|
||||||
def find_reporters(since: str, until: str) -> GitHubLogins:
|
|
||||||
url = f'{API_URL}/search/issues'
|
|
||||||
page = 1
|
|
||||||
per_page = 100
|
|
||||||
params = {
|
|
||||||
'q': f'repo:{REPO}/{OWNER} is:issue closed:{since}..{until}',
|
|
||||||
'per_page': per_page,
|
|
||||||
}
|
|
||||||
reporters: GitHubLogins = set()
|
|
||||||
|
|
||||||
while 'there are issues':
|
|
||||||
params['page'] = page
|
|
||||||
data = fetch(url, params=params)
|
|
||||||
|
|
||||||
for item in data['items']:
|
|
||||||
# Filter out unwanted labels.
|
|
||||||
if any(label['name'] in SKIPPED_LABELS for label in item['labels']):
|
|
||||||
continue
|
|
||||||
debug(' >>> Issue', item['html_url'])
|
|
||||||
reporters.add(item['user']['login'])
|
|
||||||
|
|
||||||
if len(data['items']) < per_page:
|
|
||||||
break
|
|
||||||
page += 1
|
|
||||||
|
|
||||||
return reporters
|
|
||||||
|
|
||||||
|
|
||||||
def merge_all_the_people(release: str, contributors: People, committers: FullNames, reporters: GitHubLogins) -> None:
|
|
||||||
"""
|
|
||||||
>>> contributors = {'Alice': new_person(github='alice', twitter='alice')}
|
|
||||||
>>> merge_all_the_people('2.6.0', contributors, {}, {})
|
|
||||||
>>> contributors
|
|
||||||
{'Alice': {'committed': [], 'reported': [], 'github': 'alice', 'twitter': 'alice'}}
|
|
||||||
|
|
||||||
>>> contributors = {'Bob': new_person(github='bob', twitter='bob')}
|
|
||||||
>>> merge_all_the_people('2.6.0', contributors, {'Bob'}, {'bob'})
|
|
||||||
>>> contributors
|
|
||||||
{'Bob': {'committed': ['2.6.0'], 'reported': ['2.6.0'], 'github': 'bob', 'twitter': 'bob'}}
|
|
||||||
|
|
||||||
>>> contributors = {'Charlotte': new_person(github='charlotte', twitter='charlotte', committed=['2.5.0'], reported=['2.5.0'])}
|
|
||||||
>>> merge_all_the_people('2.6.0', contributors, {'Charlotte'}, {'charlotte'})
|
|
||||||
>>> contributors
|
|
||||||
{'Charlotte': {'committed': ['2.5.0', '2.6.0'], 'reported': ['2.5.0', '2.6.0'], 'github': 'charlotte', 'twitter': 'charlotte'}}
|
|
||||||
|
|
||||||
"""
|
|
||||||
# Update known contributors.
|
|
||||||
for name, details in contributors.items():
|
|
||||||
if name in committers:
|
|
||||||
if release not in details['committed']:
|
|
||||||
details['committed'].append(release)
|
|
||||||
committers.remove(name)
|
|
||||||
if details['github'] in reporters:
|
|
||||||
if release not in details['reported']:
|
|
||||||
details['reported'].append(release)
|
|
||||||
reporters.remove(details['github'])
|
|
||||||
|
|
||||||
# Add new committers.
|
|
||||||
for name in committers:
|
|
||||||
user_info = user(fullname=name)
|
|
||||||
contributors[name] = new_person(
|
|
||||||
github=user_info['login'],
|
|
||||||
twitter=user_info['twitter_username'],
|
|
||||||
committed=[release],
|
|
||||||
)
|
|
||||||
if user_info['login'] in reporters:
|
|
||||||
contributors[name]['reported'].append(release)
|
|
||||||
reporters.remove(user_info['login'])
|
|
||||||
|
|
||||||
# Add new reporters.
|
|
||||||
for github_username in reporters:
|
|
||||||
user_info = user(github_username=github_username)
|
|
||||||
contributors[user_info['name'] or user_info['login']] = new_person(
|
|
||||||
github=github_username,
|
|
||||||
twitter=user_info['twitter_username'],
|
|
||||||
reported=[release],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def release_date(release: str) -> str:
|
|
||||||
date = check_output(['git', 'log', '-1', '--format=%ai', release], text=True).strip()
|
|
||||||
return datetime.strptime(date, '%Y-%m-%d %H:%M:%S %z').isoformat()
|
|
||||||
|
|
||||||
|
|
||||||
def load_awesome_people() -> People:
|
|
||||||
try:
|
|
||||||
with DB_FILE.open(encoding='utf-8') as fh:
|
|
||||||
return json.load(fh)
|
|
||||||
except (FileNotFoundError, ValueError):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
|
|
||||||
def fetch(url: str, params: Optional[Dict[str, str]] = None) -> UserInfo:
|
|
||||||
headers = {
|
|
||||||
'Accept': 'application/vnd.github.v3+json',
|
|
||||||
'Authentication': f'token {GITHUB_TOKEN}'
|
|
||||||
}
|
|
||||||
for retry in range(1, 6):
|
|
||||||
debug(f'[{retry}/5]', f'{url = }', f'{params = }')
|
|
||||||
with requests.get(url, params=params, headers=headers) as req:
|
|
||||||
try:
|
|
||||||
req.raise_for_status()
|
|
||||||
except requests.exceptions.HTTPError as exc:
|
|
||||||
if exc.response.status_code == 403:
|
|
||||||
# 403 Client Error: rate limit exceeded for url: ...
|
|
||||||
now = int(datetime.utcnow().timestamp())
|
|
||||||
xrate_limit_reset = int(exc.response.headers['X-RateLimit-Reset'])
|
|
||||||
wait = xrate_limit_reset - now
|
|
||||||
if wait > 20:
|
|
||||||
raise FinishedForNow()
|
|
||||||
debug(' !', 'Waiting', wait, 'seconds before another try ...')
|
|
||||||
sleep(wait)
|
|
||||||
continue
|
|
||||||
return req.json()
|
|
||||||
assert ValueError('Rate limit exceeded')
|
|
||||||
|
|
||||||
|
|
||||||
def new_person(**kwargs: str) -> Person:
|
|
||||||
data = deepcopy(DEFAULT_PERSON)
|
|
||||||
data.update(**kwargs)
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def user(fullname: Optional[str] = '', github_username: Optional[str] = '') -> UserInfo:
|
|
||||||
if github_username:
|
|
||||||
url = f'{API_URL}/users/{github_username}'
|
|
||||||
return fetch(url)
|
|
||||||
|
|
||||||
url = f'{API_URL}/search/users'
|
|
||||||
for query in (f'fullname:{fullname}', f'user:{fullname}'):
|
|
||||||
params = {
|
|
||||||
'q': f'repo:{REPO}/{OWNER} {query}',
|
|
||||||
'per_page': 1,
|
|
||||||
}
|
|
||||||
user_info = fetch(url, params=params)
|
|
||||||
if user_info['items']:
|
|
||||||
user_url = user_info['items'][0]['url']
|
|
||||||
return fetch(user_url)
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_missing_users_details(people: People) -> None:
|
|
||||||
for name, details in people.items():
|
|
||||||
if details['github'] and details['twitter']:
|
|
||||||
continue
|
|
||||||
user_info = user(github_username=details['github'], fullname=name)
|
|
||||||
if not details['github']:
|
|
||||||
details['github'] = user_info['login']
|
|
||||||
if not details['twitter']:
|
|
||||||
details['twitter'] = user_info['twitter_username']
|
|
||||||
|
|
||||||
|
|
||||||
def save_awesome_people(people: People) -> None:
|
|
||||||
with DB_FILE.open(mode='w', encoding='utf-8') as fh:
|
|
||||||
json.dump(people, fh, indent=4, sort_keys=True)
|
|
||||||
|
|
||||||
|
|
||||||
def debug(*args: Any) -> None:
|
|
||||||
if os.getenv('DEBUG') == '1':
|
|
||||||
print(*args)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
ret = 1
|
|
||||||
try:
|
|
||||||
ret = main(*sys.argv[1:])
|
|
||||||
except TypeError:
|
|
||||||
ret = 2
|
|
||||||
print(f'''
|
|
||||||
Fetch contributors to a release.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python {sys.argv[0]} {sys.argv[0]} <RELEASE N-1> <RELEASE N>
|
|
||||||
Example:
|
|
||||||
python {sys.argv[0]} 2.4.0 2.5.0
|
|
||||||
|
|
||||||
Define the DEBUG=1 environment variable to enable verbose output.
|
|
||||||
''')
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
ret = 255
|
|
||||||
sys.exit(ret)
|
|
@ -1,41 +0,0 @@
|
|||||||
"""
|
|
||||||
Generate snippets to copy-paste.
|
|
||||||
"""
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from jinja2 import Template
|
|
||||||
|
|
||||||
from fetch import HERE, load_awesome_people
|
|
||||||
|
|
||||||
TPL_FILE = HERE / 'snippet.jinja2'
|
|
||||||
HTTPIE_TEAM = {'jakubroztocil', 'BoboTiG', 'claudiatd'}
|
|
||||||
|
|
||||||
|
|
||||||
def generate_snippets(release: str) -> str:
|
|
||||||
people = load_awesome_people()
|
|
||||||
contributors = {
|
|
||||||
name: details
|
|
||||||
for name, details in people.items()
|
|
||||||
if details['github'] not in HTTPIE_TEAM
|
|
||||||
and (release in details['committed'] or release in details['reported'])
|
|
||||||
}
|
|
||||||
|
|
||||||
template = Template(source=TPL_FILE.read_text(encoding='utf-8'))
|
|
||||||
output = template.render(contributors=contributors, release=release)
|
|
||||||
print(output)
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
ret = 1
|
|
||||||
try:
|
|
||||||
ret = generate_snippets(sys.argv[1])
|
|
||||||
except (IndexError, TypeError):
|
|
||||||
ret = 2
|
|
||||||
print(f'''
|
|
||||||
Generate snippets for contributors to a release.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python {sys.argv[0]} {sys.argv[0]} <RELEASE>
|
|
||||||
''')
|
|
||||||
sys.exit(ret)
|
|
@ -1,240 +0,0 @@
|
|||||||
{
|
|
||||||
"Almad": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "Almad",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": "almadcz"
|
|
||||||
},
|
|
||||||
"Anton Emelyanov": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "king-menin",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"D8ger": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "caofanCPU",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Dawid Ferenczy Rogo\u017ean": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "ferenczy",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": "DawidFerenczy"
|
|
||||||
},
|
|
||||||
"Elena Lape": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "elenalape",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": "elena_lape"
|
|
||||||
},
|
|
||||||
"F\u00fash\u0113ng": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "lienide",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Giampaolo Rodola": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "giampaolo",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Hugh Williams": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "hughpv",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Ilya Sukhanov": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "IlyaSukhanov",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Jakub Roztocil": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "jakubroztocil",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": "jakubroztocil"
|
|
||||||
},
|
|
||||||
"Jan Verbeek": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "blyxxyz",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Jannik Vieten": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "exploide",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Marcel St\u00f6r": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "marcelstoer",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": "frightanic"
|
|
||||||
},
|
|
||||||
"Mariano Ruiz": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "mrsarm",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": "mrsarm82"
|
|
||||||
},
|
|
||||||
"Micka\u00ebl Schoentgen": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "BoboTiG",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": "__tiger222__"
|
|
||||||
},
|
|
||||||
"Miro Hron\u010dok": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "hroncok",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": "hroncok"
|
|
||||||
},
|
|
||||||
"Mohamed Daahir": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "ducaale",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Pavel Alexeev aka Pahan-Hubbitus": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "Hubbitus",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Samuel Marks": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "SamuelMarks",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Sullivan SENECHAL": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "soullivaneuh",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Thomas Klinger": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "mosesontheweb",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"Yannic Schneider": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "cynay",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"a1346054": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "a1346054",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"bl-ue": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "FiReBlUe45",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"henryhu712": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "henryhu712",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"jungle-boogie": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "jungle-boogie",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"nixbytes": {
|
|
||||||
"committed": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"github": "nixbytes",
|
|
||||||
"reported": [],
|
|
||||||
"twitter": "linuxbyte3"
|
|
||||||
},
|
|
||||||
"qiulang": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "qiulang",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
},
|
|
||||||
"zwx00": {
|
|
||||||
"committed": [],
|
|
||||||
"github": "zwx00",
|
|
||||||
"reported": [
|
|
||||||
"2.5.0"
|
|
||||||
],
|
|
||||||
"twitter": null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
<!-- Blog post -->
|
|
||||||
|
|
||||||
## Community contributions
|
|
||||||
|
|
||||||
We’d like to thank these amazing people for their contributions to this release: {% for name, details in contributors.items() -%}
|
|
||||||
[{{ name }}](https://github.com/{{ details.github }}){{ '' if loop.last else ', ' }}
|
|
||||||
{%- endfor %}.
|
|
||||||
|
|
||||||
<!-- Twitter -->
|
|
||||||
|
|
||||||
We’d like to thank these amazing people for their contributions to HTTPie {{ release }}: {% for name, details in contributors.items() if details.twitter -%}
|
|
||||||
@{{ details.twitter }}{{ '' if loop.last else ', ' }}
|
|
||||||
{%- endfor %} 🥧
|
|
@ -19,16 +19,16 @@ Do not edit here, but in docs/installation/.
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if tool.links.setup %}
|
{% if tool.links.setup %}
|
||||||
To install [{{ tool.name }}]({{ tool.links.homepage }}), see [its installation]({{ tool.links.setup }}).
|
To install [{{ tool.name }}]({{ tool.links.homepage }}) follow [installation instructions]({{ tool.links.setup }}).
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install httpie
|
# Install
|
||||||
$ {{ tool.commands.install|join('\n$ ') }}
|
$ {{ tool.commands.install|join('\n$ ') }}
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upgrade httpie
|
# Upgrade
|
||||||
$ {{ tool.commands.upgrade|join('\n$ ') }}
|
$ {{ tool.commands.upgrade|join('\n$ ') }}
|
||||||
```
|
```
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -14,6 +14,7 @@ docs-structure:
|
|||||||
macOS:
|
macOS:
|
||||||
- brew-mac
|
- brew-mac
|
||||||
- port
|
- port
|
||||||
|
- snap-mac
|
||||||
- spack-mac
|
- spack-mac
|
||||||
Windows:
|
Windows:
|
||||||
- chocolatey
|
- chocolatey
|
||||||
@ -201,6 +202,19 @@ tools:
|
|||||||
upgrade:
|
upgrade:
|
||||||
- snap refresh httpie
|
- 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:
|
spack-linux:
|
||||||
title: Spack (Linux)
|
title: Spack (Linux)
|
||||||
name: Spack
|
name: Spack
|
||||||
|
@ -23,8 +23,7 @@ That is done quite easily by manually triggering the [release workflow](https://
|
|||||||
|
|
||||||
## Then, company-specific tasks
|
## Then, company-specific tasks
|
||||||
|
|
||||||
- Blank the `master_and_released_docs_differ_after` value in [config.json](https://github.com/httpie/httpie/blob/master/docs/config.json).
|
- Update the HTTPie version bundled into termible ([example](https://github.com/httpie/termible/pull/1)).
|
||||||
- Update the HTTPie version bundled into [Termible](https://termible.io/) ([example](https://github.com/httpie/termible/pull/1)).
|
|
||||||
|
|
||||||
## Finally, spread dowstream
|
## Finally, spread dowstream
|
||||||
|
|
||||||
|
@ -1,2 +1,6 @@
|
|||||||
$ErrorActionPreference = 'Stop';
|
$ErrorActionPreference = 'Stop';
|
||||||
py -m pip install $env:ChocolateyPackageName==$env:ChocolateyPackageVersion --disable-pip-version-check
|
$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
|
||||||
|
@ -3,6 +3,6 @@ HTTPie: command-line HTTP client for the API era.
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = '2.6.0'
|
__version__ = '2.6.0.dev0'
|
||||||
__author__ = 'Jakub Roztocil'
|
__author__ = 'Jakub Roztocil'
|
||||||
__licence__ = 'BSD'
|
__licence__ = 'BSD'
|
||||||
|
@ -75,6 +75,8 @@ class HTTPieArgumentParser(argparse.ArgumentParser):
|
|||||||
) -> argparse.Namespace:
|
) -> argparse.Namespace:
|
||||||
self.env = env
|
self.env = env
|
||||||
self.args, no_options = super().parse_known_args(args, namespace)
|
self.args, no_options = super().parse_known_args(args, namespace)
|
||||||
|
if self.args.prompt:
|
||||||
|
return self.args
|
||||||
if self.args.debug:
|
if self.args.debug:
|
||||||
self.args.traceback = True
|
self.args.traceback = True
|
||||||
self.has_stdin_data = (
|
self.has_stdin_data = (
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
CLI arguments definition.
|
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 textwrap import dedent, wrap
|
||||||
|
|
||||||
from .. import __doc__, __version__
|
from .. import __doc__, __version__
|
||||||
@ -73,6 +73,7 @@ positional.add_argument(
|
|||||||
positional.add_argument(
|
positional.add_argument(
|
||||||
dest='url',
|
dest='url',
|
||||||
metavar='URL',
|
metavar='URL',
|
||||||
|
nargs=OPTIONAL,
|
||||||
help='''
|
help='''
|
||||||
The scheme defaults to 'http://' if the URL does not include one.
|
The scheme defaults to 'http://' if the URL does not include one.
|
||||||
(You can override this with: --default-scheme=https)
|
(You can override this with: --default-scheme=https)
|
||||||
@ -497,14 +498,12 @@ output_options.add_argument(
|
|||||||
|
|
||||||
output_options.add_argument(
|
output_options.add_argument(
|
||||||
'--quiet', '-q',
|
'--quiet', '-q',
|
||||||
action='count',
|
action='store_true',
|
||||||
default=0,
|
default=False,
|
||||||
help='''
|
help='''
|
||||||
Do not print to stdout or stderr, except for errors and warnings when provided once.
|
Do not print to stdout or stderr.
|
||||||
Provide twice to suppress warnings as well.
|
|
||||||
stdout is still redirected if --output is specified.
|
stdout is still redirected if --output is specified.
|
||||||
Flag doesn't affect behaviour of download beyond not printing to terminal.
|
Flag doesn't affect behaviour of download beyond not printing to terminal.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -842,3 +841,12 @@ troubleshooting.add_argument(
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
troubleshooting.add_argument(
|
||||||
|
'--prompt',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='''
|
||||||
|
Start the shell!
|
||||||
|
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
@ -29,6 +29,10 @@ def main(args: List[Union[str, bytes]] = sys.argv, env=Environment()) -> ExitSta
|
|||||||
Return exit status code.
|
Return exit status code.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if '--prompt' in args:
|
||||||
|
from .prompt.cli import cli
|
||||||
|
return cli(sys.argv[2:])
|
||||||
|
|
||||||
program_name, *args = args
|
program_name, *args = args
|
||||||
env.program_name = os.path.basename(program_name)
|
env.program_name = os.path.basename(program_name)
|
||||||
args = decode_raw_args(args, env.stdin_encoding)
|
args = decode_raw_args(args, env.stdin_encoding)
|
||||||
@ -185,7 +189,7 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
|
|||||||
final_response = message
|
final_response = message
|
||||||
if args.check_status or downloader:
|
if args.check_status or downloader:
|
||||||
exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)
|
exit_status = http_status_to_exit_status(http_status=message.status_code, follow=args.follow)
|
||||||
if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet == 1):
|
if exit_status != ExitStatus.SUCCESS and (not env.stdout_isatty or args.quiet):
|
||||||
env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level='warning')
|
env.log_error(f'HTTP {message.raw.status} {message.raw.reason}', level='warning')
|
||||||
write_message(requests_message=message, env=env, args=args, with_headers=with_headers,
|
write_message(requests_message=message, env=env, args=args, with_headers=with_headers,
|
||||||
with_body=do_write_body)
|
with_body=do_write_body)
|
||||||
|
@ -20,7 +20,7 @@ class EnhancedJsonLexer(RegexLexer):
|
|||||||
tokens = {
|
tokens = {
|
||||||
'root': [
|
'root': [
|
||||||
# Eventual non-JSON data prefix followed by actual JSON body.
|
# Eventual non-JSON data prefix followed by actual JSON body.
|
||||||
# FIX: data prefix + number (integer or float) is not correctly handled.
|
# FIX: data prefix + number (integer or float) are not correctly handled.
|
||||||
(
|
(
|
||||||
fr'({PREFIX_REGEX})' + r'((?:[{\["]|true|false|null).+)',
|
fr'({PREFIX_REGEX})' + r'((?:[{\["]|true|false|null).+)',
|
||||||
bygroups(PREFIX_TOKEN, using(JsonLexer))
|
bygroups(PREFIX_TOKEN, using(JsonLexer))
|
||||||
|
1
httpie/prompt
Submodule
1
httpie/prompt
Submodule
Submodule httpie/prompt added at 8922a77156
9
setup.py
9
setup.py
@ -9,6 +9,7 @@ import httpie
|
|||||||
|
|
||||||
# Note: keep requirements here to ease distributions packaging
|
# Note: keep requirements here to ease distributions packaging
|
||||||
tests_require = [
|
tests_require = [
|
||||||
|
'pexpect',
|
||||||
'pytest',
|
'pytest',
|
||||||
'pytest-httpbin>=0.0.6',
|
'pytest-httpbin>=0.0.6',
|
||||||
'responses',
|
'responses',
|
||||||
@ -20,12 +21,12 @@ dev_require = [
|
|||||||
'flake8-deprecated',
|
'flake8-deprecated',
|
||||||
'flake8-mutable',
|
'flake8-mutable',
|
||||||
'flake8-tuple',
|
'flake8-tuple',
|
||||||
|
'jinja2',
|
||||||
'pyopenssl',
|
'pyopenssl',
|
||||||
'pytest-cov',
|
'pytest-cov',
|
||||||
'pyyaml',
|
'pyyaml',
|
||||||
'twine',
|
'twine',
|
||||||
'wheel',
|
'wheel',
|
||||||
'Jinja2'
|
|
||||||
]
|
]
|
||||||
install_requires = [
|
install_requires = [
|
||||||
'charset_normalizer>=2.0.0',
|
'charset_normalizer>=2.0.0',
|
||||||
@ -34,6 +35,11 @@ install_requires = [
|
|||||||
'Pygments>=2.5.2',
|
'Pygments>=2.5.2',
|
||||||
'requests-toolbelt>=0.9.1',
|
'requests-toolbelt>=0.9.1',
|
||||||
'setuptools',
|
'setuptools',
|
||||||
|
# Prompt
|
||||||
|
'click>=5.0',
|
||||||
|
'parsimonious>=0.6.2',
|
||||||
|
'prompt-toolkit>=2.0.0,<3.0.0',
|
||||||
|
'pyyaml>=3.0',
|
||||||
]
|
]
|
||||||
install_requires_win_only = [
|
install_requires_win_only = [
|
||||||
'colorama>=0.2.4',
|
'colorama>=0.2.4',
|
||||||
@ -79,6 +85,7 @@ setup(
|
|||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
'http = httpie.__main__:main',
|
'http = httpie.__main__:main',
|
||||||
'https = httpie.__main__:main',
|
'https = httpie.__main__:main',
|
||||||
|
'http-prompt=httpie.prompt.cli:cli',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
python_requires='>=3.6',
|
python_requires='>=3.6',
|
||||||
|
0
tests/prompt/__init__.py
Normal file
0
tests/prompt/__init__.py
Normal file
59
tests/prompt/base.py
Normal file
59
tests/prompt/base.py
Normal 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
|
161
tests/prompt/context/test_context.py
Normal file
161
tests/prompt/context/test_context.py
Normal 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
|
162
tests/prompt/context/test_transform.py
Normal file
162
tests/prompt/context/test_transform.py
Normal 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
319
tests/prompt/test_cli.py
Normal 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')
|
130
tests/prompt/test_completer.py
Normal file
130
tests/prompt/test_completer.py
Normal 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'])
|
70
tests/prompt/test_config.py
Normal file
70
tests/prompt/test_config.py
Normal 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!')
|
24
tests/prompt/test_contextio.py
Normal file
24
tests/prompt/test_contextio.py
Normal 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': '中文'
|
||||||
|
})
|
1631
tests/prompt/test_execution.py
Normal file
1631
tests/prompt/test_execution.py
Normal file
File diff suppressed because it is too large
Load Diff
32
tests/prompt/test_installation.py
Normal file
32
tests/prompt/test_installation.py
Normal 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
|
79
tests/prompt/test_interaction.py
Normal file
79
tests/prompt/test_interaction.py
Normal 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
793
tests/prompt/test_lexer.py
Normal 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')
|
||||||
|
])
|
131
tests/prompt/test_tree.py
Normal file
131
tests/prompt/test_tree.py
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from httpie.prompt.tree import Node
|
||||||
|
|
||||||
|
|
||||||
|
class TestNode(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Make a tree like this:
|
||||||
|
# root
|
||||||
|
# a h
|
||||||
|
# b d i n
|
||||||
|
# c f e g k o
|
||||||
|
# l m p
|
||||||
|
self.root = Node('root')
|
||||||
|
self.root.add_path('a', 'b', 'c')
|
||||||
|
self.root.add_path('a', 'b', 'f')
|
||||||
|
self.root.add_path('a', 'd', 'e')
|
||||||
|
self.root.add_path('a', 'd', 'g')
|
||||||
|
self.root.add_path('h', 'i', 'k', 'l')
|
||||||
|
self.root.add_path('h', 'i', 'k', 'm')
|
||||||
|
self.root.add_path('h', 'i', 'k', 'p')
|
||||||
|
self.root.add_path('h', 'n', 'o')
|
||||||
|
|
||||||
|
def test_illegal_name(self):
|
||||||
|
self.assertRaises(ValueError, Node, '.')
|
||||||
|
self.assertRaises(ValueError, Node, '..')
|
||||||
|
|
||||||
|
def test_str(self):
|
||||||
|
node = Node('my node')
|
||||||
|
self.assertEqual(str(node), 'my node')
|
||||||
|
|
||||||
|
def test_cmp_same_type(self):
|
||||||
|
a = Node('a', data={'type': 'dir'})
|
||||||
|
b = Node('b', data={'type': 'dir'})
|
||||||
|
self.assertTrue(a < b)
|
||||||
|
|
||||||
|
def test_cmp_different_type(self):
|
||||||
|
a = Node('a', data={'type': 'file'})
|
||||||
|
b = Node('b', data={'type': 'dir'})
|
||||||
|
self.assertTrue(b < a)
|
||||||
|
|
||||||
|
def test_eq(self):
|
||||||
|
a = Node('a', data={'type': 'file'})
|
||||||
|
b = Node('b', data={'type': 'dir'})
|
||||||
|
self.assertNotEqual(a, b)
|
||||||
|
|
||||||
|
a = Node('a', data={'type': 'file'})
|
||||||
|
b = Node('a', data={'type': 'file'})
|
||||||
|
self.assertEqual(a, b)
|
||||||
|
|
||||||
|
def test_add_path_and_find_child(self):
|
||||||
|
# Level 1 (root)
|
||||||
|
self.assertEqual(set(c.name for c in self.root.children), set('ah'))
|
||||||
|
|
||||||
|
# Level 2
|
||||||
|
node_a = self.root.find_child('a')
|
||||||
|
node_h = self.root.find_child('h')
|
||||||
|
self.assertEqual(set(c.name for c in node_a.children), set('bd'))
|
||||||
|
self.assertEqual(set(c.name for c in node_h.children), set('in'))
|
||||||
|
|
||||||
|
# Level 3
|
||||||
|
node_b = node_a.find_child('b')
|
||||||
|
node_i = node_h.find_child('i')
|
||||||
|
self.assertEqual(set(c.name for c in node_b.children), set('cf'))
|
||||||
|
self.assertEqual(set(c.name for c in node_i.children), set('k'))
|
||||||
|
|
||||||
|
# Level 4
|
||||||
|
node_c = node_b.find_child('c')
|
||||||
|
node_k = node_i.find_child('k')
|
||||||
|
self.assertEqual(set(c.name for c in node_c.children), set())
|
||||||
|
self.assertEqual(set(c.name for c in node_k.children), set('lmp'))
|
||||||
|
|
||||||
|
# Return None if child can't be found
|
||||||
|
self.assertFalse(node_c.find_child('x'))
|
||||||
|
|
||||||
|
def test_find_child_wildcard(self):
|
||||||
|
root = Node('root')
|
||||||
|
root.add_path('a')
|
||||||
|
root.add_path('{b}')
|
||||||
|
root.add_path('c')
|
||||||
|
|
||||||
|
self.assertEqual(root.find_child('a').name, 'a')
|
||||||
|
self.assertEqual(root.find_child('c').name, 'c')
|
||||||
|
self.assertEqual(root.find_child('x').name, '{b}')
|
||||||
|
self.assertFalse(root.find_child('x', wildcard=False))
|
||||||
|
|
||||||
|
def test_ls(self):
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('a')], list('bd'))
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('a', 'b')], list('cf'))
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('a', 'b', 'c')], [])
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('h', 'i', 'k')],
|
||||||
|
list('lmp'))
|
||||||
|
|
||||||
|
def test_ls_root(self):
|
||||||
|
self.assertEqual([n.name for n in self.root.ls()], list('ah'))
|
||||||
|
|
||||||
|
def test_ls_non_existing(self):
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('x')], [])
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('a', 'b', 'x')], [])
|
||||||
|
|
||||||
|
def test_ls_parent(self):
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('..')], list('ah'))
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('..', '..', '..')],
|
||||||
|
list('ah'))
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('..', '..', 'h')],
|
||||||
|
list('in'))
|
||||||
|
self.assertEqual(
|
||||||
|
[n.name for n in self.root.ls('..', '..', 'h', '..', 'a')],
|
||||||
|
list('bd'))
|
||||||
|
|
||||||
|
def test_ls_dot(self):
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('.')], list('ah'))
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('.', '.', '.')],
|
||||||
|
list('ah'))
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('.', 'a', 'b')],
|
||||||
|
list('cf'))
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('.', 'h', '.')],
|
||||||
|
list('in'))
|
||||||
|
self.assertEqual(
|
||||||
|
[n.name for n in self.root.ls('.', 'h', '.', '.', 'n')], ['o'])
|
||||||
|
|
||||||
|
def test_ls_sort_by_types(self):
|
||||||
|
self.root.add_path('q', 'r')
|
||||||
|
self.root.add_path('q', 's', node_type='file')
|
||||||
|
self.root.add_path('q', 't', node_type='file')
|
||||||
|
self.root.add_path('q', 'u')
|
||||||
|
self.root.add_path('q', 'v', node_type='file')
|
||||||
|
|
||||||
|
self.assertEqual([n.name for n in self.root.ls('q')],
|
||||||
|
list('rustv'))
|
92
tests/prompt/test_utils.py
Normal file
92
tests/prompt/test_utils.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
from httpie.prompt import utils
|
||||||
|
|
||||||
|
|
||||||
|
def test_colformat_zero_items():
|
||||||
|
assert list(utils.colformat([], terminal_width=80)) == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_colformat_one_item():
|
||||||
|
assert list(utils.colformat(['hello'], terminal_width=80)) == ['hello']
|
||||||
|
|
||||||
|
|
||||||
|
def test_colformat_single_line():
|
||||||
|
items = ['hello', 'world', 'foo', 'bar']
|
||||||
|
assert list(utils.colformat(items, terminal_width=80)) == [
|
||||||
|
'hello world foo bar'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_colformat_single_column():
|
||||||
|
items = ['chap1.txt', 'chap2.txt', 'chap3.txt', 'chap4.txt',
|
||||||
|
'chap5.txt', 'chap6.txt', 'chap7.txt', 'chap8.txt']
|
||||||
|
assert list(utils.colformat(items, terminal_width=10)) == [
|
||||||
|
'chap1.txt', 'chap2.txt', 'chap3.txt', 'chap4.txt',
|
||||||
|
'chap5.txt', 'chap6.txt', 'chap7.txt', 'chap8.txt'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_colformat_multi_columns_no_remainder():
|
||||||
|
items = ['chap1.txt', 'chap2.txt', 'chap3.txt', 'chap4.txt',
|
||||||
|
'chap5.txt', 'chap6.txt', 'chap7.txt', 'chap8.txt',
|
||||||
|
'chap9.txt', 'chap10.txt', 'chap11.txt', 'chap12.txt']
|
||||||
|
assert list(utils.colformat(items, terminal_width=50)) == [
|
||||||
|
'chap1.txt chap4.txt chap7.txt chap10.txt',
|
||||||
|
'chap2.txt chap5.txt chap8.txt chap11.txt',
|
||||||
|
'chap3.txt chap6.txt chap9.txt chap12.txt'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_colformat_multi_columns_remainder_1():
|
||||||
|
items = ['chap1.txt', 'chap2.txt', 'chap3.txt', 'chap4.txt',
|
||||||
|
'chap5.txt', 'chap6.txt', 'chap7.txt', 'chap8.txt',
|
||||||
|
'chap9.txt', 'chap10.txt', 'chap11.txt', 'chap12.txt',
|
||||||
|
'chap13.txt']
|
||||||
|
assert list(utils.colformat(items, terminal_width=50)) == [
|
||||||
|
'chap1.txt chap5.txt chap9.txt chap13.txt',
|
||||||
|
'chap2.txt chap6.txt chap10.txt',
|
||||||
|
'chap3.txt chap7.txt chap11.txt',
|
||||||
|
'chap4.txt chap8.txt chap12.txt'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_colformat_multi_columns_remainder_2():
|
||||||
|
items = ['chap1.txt', 'chap2.txt', 'chap3.txt', 'chap4.txt',
|
||||||
|
'chap5.txt', 'chap6.txt', 'chap7.txt', 'chap8.txt',
|
||||||
|
'chap9.txt', 'chap10.txt', 'chap11.txt', 'chap12.txt',
|
||||||
|
'chap13.txt', 'chap14.txt']
|
||||||
|
assert list(utils.colformat(items, terminal_width=50)) == [
|
||||||
|
'chap1.txt chap5.txt chap9.txt chap13.txt',
|
||||||
|
'chap2.txt chap6.txt chap10.txt chap14.txt',
|
||||||
|
'chap3.txt chap7.txt chap11.txt',
|
||||||
|
'chap4.txt chap8.txt chap12.txt'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_colformat_wider_than_terminal():
|
||||||
|
items = ['a very long long name', '1111 2222 3333 4444 5555']
|
||||||
|
assert list(utils.colformat(items, terminal_width=10)) == [
|
||||||
|
'a very long long name',
|
||||||
|
'1111 2222 3333 4444 5555'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_colformat_long_short_mixed():
|
||||||
|
items = ['a', '1122334455667788', 'hello world', 'foo bar',
|
||||||
|
'b', '8877665544332211', 'abcd', 'yeah']
|
||||||
|
assert list(utils.colformat(items, terminal_width=50)) == [
|
||||||
|
'a foo bar abcd',
|
||||||
|
'1122334455667788 b yeah',
|
||||||
|
'hello world 8877665544332211'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_colformat_github_top_endpoints():
|
||||||
|
items = ['emojis', 'events', 'feeds', 'gists', 'gitignore', 'issues',
|
||||||
|
'legacy', 'markdown', 'meta', 'networks', 'notifications',
|
||||||
|
'orgs', 'rate_limit', 'repos', 'repositories', 'search',
|
||||||
|
'teams', 'user', 'users']
|
||||||
|
assert list(utils.colformat(items, terminal_width=136)) == [
|
||||||
|
'emojis gists legacy networks rate_limit'' search users', # noqa
|
||||||
|
'events gitignore markdown notifications repos teams', # noqa
|
||||||
|
'feeds issues meta orgs repositories user' # noqa
|
||||||
|
]
|
59
tests/prompt/test_xdg.py
Normal file
59
tests/prompt/test_xdg.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import os
|
||||||
|
import stat
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .base import TempAppDirTestCase
|
||||||
|
from httpie.prompt import xdg
|
||||||
|
|
||||||
|
|
||||||
|
class TestXDG(TempAppDirTestCase):
|
||||||
|
|
||||||
|
def test_get_app_data_home(self):
|
||||||
|
path = xdg.get_data_dir()
|
||||||
|
expected_path = os.path.join(os.environ[self.homes['data']],
|
||||||
|
'http-prompt')
|
||||||
|
self.assertEqual(path, expected_path)
|
||||||
|
self.assertTrue(os.path.exists(path))
|
||||||
|
|
||||||
|
if sys.platform != 'win32':
|
||||||
|
# Make sure permission for the directory is 700
|
||||||
|
mask = stat.S_IMODE(os.stat(path).st_mode)
|
||||||
|
self.assertTrue(mask & stat.S_IRWXU)
|
||||||
|
self.assertFalse(mask & stat.S_IRWXG)
|
||||||
|
self.assertFalse(mask & stat.S_IRWXO)
|
||||||
|
|
||||||
|
def test_get_app_config_home(self):
|
||||||
|
path = xdg.get_config_dir()
|
||||||
|
expected_path = os.path.join(os.environ[self.homes['config']],
|
||||||
|
'http-prompt')
|
||||||
|
self.assertEqual(path, expected_path)
|
||||||
|
self.assertTrue(os.path.exists(path))
|
||||||
|
|
||||||
|
if sys.platform != 'win32':
|
||||||
|
# Make sure permission for the directory is 700
|
||||||
|
mask = stat.S_IMODE(os.stat(path).st_mode)
|
||||||
|
self.assertTrue(mask & stat.S_IRWXU)
|
||||||
|
self.assertFalse(mask & stat.S_IRWXG)
|
||||||
|
self.assertFalse(mask & stat.S_IRWXO)
|
||||||
|
|
||||||
|
def test_get_resource_data_dir(self):
|
||||||
|
path = xdg.get_data_dir('something')
|
||||||
|
expected_path = os.path.join(
|
||||||
|
os.environ[self.homes['data']], 'http-prompt', 'something')
|
||||||
|
self.assertEqual(path, expected_path)
|
||||||
|
self.assertTrue(os.path.exists(path))
|
||||||
|
|
||||||
|
# Make sure we can write a file to the directory
|
||||||
|
with open(os.path.join(path, 'test'), 'wb') as f:
|
||||||
|
f.write(b'hello')
|
||||||
|
|
||||||
|
def test_get_resource_config_dir(self):
|
||||||
|
path = xdg.get_config_dir('something')
|
||||||
|
expected_path = os.path.join(
|
||||||
|
os.environ[self.homes['config']], 'http-prompt', 'something')
|
||||||
|
self.assertEqual(path, expected_path)
|
||||||
|
self.assertTrue(os.path.exists(path))
|
||||||
|
|
||||||
|
# Make sure we can write a file to the directory
|
||||||
|
with open(os.path.join(path, 'test'), 'wb') as f:
|
||||||
|
f.write(b'hello')
|
22
tests/prompt/utils.py
Normal file
22
tests/prompt/utils.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def get_http_prompt_path():
|
||||||
|
"""Get the path to http-prompt executable."""
|
||||||
|
python_dir = os.path.dirname(sys.executable)
|
||||||
|
bin_name = 'http-prompt'
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
bin_name += '.exe'
|
||||||
|
|
||||||
|
paths = [
|
||||||
|
os.path.join(python_dir, bin_name),
|
||||||
|
os.path.join(python_dir, 'Scripts', bin_name), # Windows
|
||||||
|
'/usr/bin/http-prompt' # Homebrew installation
|
||||||
|
]
|
||||||
|
for path in paths:
|
||||||
|
if os.path.exists(path):
|
||||||
|
return path
|
||||||
|
|
||||||
|
raise OSError("could not locate http-prompt executable, "
|
||||||
|
"Python directory: %s" % python_dir)
|
@ -39,16 +39,15 @@ def test_output_option(tmp_path, httpbin, stdout_isatty):
|
|||||||
|
|
||||||
|
|
||||||
class TestQuietFlag:
|
class TestQuietFlag:
|
||||||
QUIET_SCENARIOS = [('--quiet',), ('-q',), ('--quiet', '--quiet'), ('-qq',)]
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('quiet_flags', QUIET_SCENARIOS)
|
@pytest.mark.parametrize('argument_name', ['--quiet', '-q'])
|
||||||
def test_quiet(self, httpbin, quiet_flags):
|
def test_quiet(self, httpbin, argument_name):
|
||||||
env = MockEnvironment(
|
env = MockEnvironment(
|
||||||
stdin_isatty=True,
|
stdin_isatty=True,
|
||||||
stdout_isatty=True,
|
stdout_isatty=True,
|
||||||
devnull=io.BytesIO()
|
devnull=io.BytesIO()
|
||||||
)
|
)
|
||||||
r = http(*quiet_flags, 'GET', httpbin.url + '/get', env=env)
|
r = http(argument_name, 'GET', httpbin.url + '/get', env=env)
|
||||||
assert env.stdout is env.devnull
|
assert env.stdout is env.devnull
|
||||||
assert env.stderr is env.devnull
|
assert env.stderr is env.devnull
|
||||||
assert HTTP_OK in r.devnull
|
assert HTTP_OK in r.devnull
|
||||||
@ -70,25 +69,9 @@ class TestQuietFlag:
|
|||||||
)
|
)
|
||||||
assert 'http: warning: HTTP 500' in r.stderr
|
assert 'http: warning: HTTP 500' in r.stderr
|
||||||
|
|
||||||
def test_quiet_quiet_with_check_status_non_zero(self, httpbin):
|
|
||||||
r = http(
|
|
||||||
'--quiet', '--quiet', '--check-status', httpbin + '/status/500',
|
|
||||||
tolerate_error_exit_status=True,
|
|
||||||
)
|
|
||||||
assert not r.stderr
|
|
||||||
|
|
||||||
def test_quiet_quiet_with_check_status_non_zero_pipe(self, httpbin):
|
|
||||||
r = http(
|
|
||||||
'--quiet', '--quiet', '--check-status', httpbin + '/status/500',
|
|
||||||
tolerate_error_exit_status=True,
|
|
||||||
env=MockEnvironment(stdout_isatty=False)
|
|
||||||
)
|
|
||||||
assert 'http: warning: HTTP 500' in r.stderr
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('quiet_flags', QUIET_SCENARIOS)
|
|
||||||
@mock.patch('httpie.cli.argtypes.AuthCredentials._getpass',
|
@mock.patch('httpie.cli.argtypes.AuthCredentials._getpass',
|
||||||
new=lambda self, prompt: 'password')
|
new=lambda self, prompt: 'password')
|
||||||
def test_quiet_with_password_prompt(self, httpbin, quiet_flags):
|
def test_quiet_with_password_prompt(self, httpbin):
|
||||||
"""
|
"""
|
||||||
Tests whether httpie still prompts for a password when request
|
Tests whether httpie still prompts for a password when request
|
||||||
requires authentication and only username is provided
|
requires authentication and only username is provided
|
||||||
@ -100,7 +83,7 @@ class TestQuietFlag:
|
|||||||
devnull=io.BytesIO()
|
devnull=io.BytesIO()
|
||||||
)
|
)
|
||||||
r = http(
|
r = http(
|
||||||
*quiet_flags, '--auth', 'user', 'GET',
|
'--quiet', '--auth', 'user', 'GET',
|
||||||
httpbin.url + '/basic-auth/user/password',
|
httpbin.url + '/basic-auth/user/password',
|
||||||
env=env
|
env=env
|
||||||
)
|
)
|
||||||
@ -110,19 +93,17 @@ class TestQuietFlag:
|
|||||||
assert r == ''
|
assert r == ''
|
||||||
assert r.stderr == ''
|
assert r.stderr == ''
|
||||||
|
|
||||||
@pytest.mark.parametrize('quiet_flags', QUIET_SCENARIOS)
|
@pytest.mark.parametrize('argument_name', ['-h', '-b', '-v', '-p=hH'])
|
||||||
@pytest.mark.parametrize('output_options', ['-h', '-b', '-v', '-p=hH'])
|
def test_quiet_with_explicit_output_options(self, httpbin, argument_name):
|
||||||
def test_quiet_with_explicit_output_options(self, httpbin, quiet_flags, output_options):
|
|
||||||
env = MockEnvironment(stdin_isatty=True, stdout_isatty=True)
|
env = MockEnvironment(stdin_isatty=True, stdout_isatty=True)
|
||||||
r = http(*quiet_flags, output_options, httpbin.url + '/get', env=env)
|
r = http('--quiet', argument_name, httpbin.url + '/get', env=env)
|
||||||
assert env.stdout is env.devnull
|
assert env.stdout is env.devnull
|
||||||
assert env.stderr is env.devnull
|
assert env.stderr is env.devnull
|
||||||
assert r == ''
|
assert r == ''
|
||||||
assert r.stderr == ''
|
assert r.stderr == ''
|
||||||
|
|
||||||
@pytest.mark.parametrize('quiet_flags', QUIET_SCENARIOS)
|
|
||||||
@pytest.mark.parametrize('with_download', [True, False])
|
@pytest.mark.parametrize('with_download', [True, False])
|
||||||
def test_quiet_with_output_redirection(self, tmp_path, httpbin, quiet_flags, with_download):
|
def test_quiet_with_output_redirection(self, tmp_path, httpbin, with_download):
|
||||||
url = httpbin + '/robots.txt'
|
url = httpbin + '/robots.txt'
|
||||||
output_path = Path('output.txt')
|
output_path = Path('output.txt')
|
||||||
env = MockEnvironment()
|
env = MockEnvironment()
|
||||||
@ -133,7 +114,7 @@ class TestQuietFlag:
|
|||||||
try:
|
try:
|
||||||
assert os.listdir('.') == []
|
assert os.listdir('.') == []
|
||||||
r = http(
|
r = http(
|
||||||
*quiet_flags,
|
'--quiet',
|
||||||
'--output', str(output_path),
|
'--output', str(output_path),
|
||||||
*extra_args,
|
*extra_args,
|
||||||
url,
|
url,
|
||||||
@ -161,7 +142,7 @@ class TestVerboseFlag:
|
|||||||
|
|
||||||
def test_verbose_raw(self, httpbin):
|
def test_verbose_raw(self, httpbin):
|
||||||
r = http('--verbose', '--raw', 'foo bar',
|
r = http('--verbose', '--raw', 'foo bar',
|
||||||
'POST', httpbin.url + '/post')
|
'POST', httpbin.url + '/post',)
|
||||||
assert HTTP_OK in r
|
assert HTTP_OK in r
|
||||||
assert 'foo bar' in r
|
assert 'foo bar' in r
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user