Full Code of httpie/http-prompt for AI

master 6602b8151685 cached
54 files
221.5 KB
54.5k tokens
576 symbols
1 requests
Download .txt
Showing preview only (235K chars total). Download the full file or copy to clipboard to get everything.
Repository: httpie/http-prompt
Branch: master
Commit: 6602b8151685
Files: 54
Total size: 221.5 KB

Directory structure:
gitextract_2twf_0e5/

├── .coveragerc
├── .github/
│   └── workflows/
│       └── build.yml
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── docs/
│   ├── LICENSE
│   ├── Makefile
│   ├── README.md
│   ├── _templates/
│   │   └── layout.html
│   ├── conf.py
│   ├── contributor-guide.rst
│   ├── index.rst
│   ├── make.bat
│   └── user-guide.rst
├── http_prompt/
│   ├── __init__.py
│   ├── cli.py
│   ├── completer.py
│   ├── completion.py
│   ├── config.py
│   ├── context/
│   │   ├── __init__.py
│   │   └── transform.py
│   ├── contextio.py
│   ├── defaultconfig.py
│   ├── execution.py
│   ├── lexer.py
│   ├── options.py
│   ├── output.py
│   ├── tree.py
│   ├── utils.py
│   └── xdg.py
├── requirements-test.txt
├── requirements.txt
├── setup.cfg
├── setup.py
├── snap/
│   └── snapcraft.yaml
├── tests/
│   ├── __init__.py
│   ├── base.py
│   ├── context/
│   │   ├── test_context.py
│   │   └── test_transform.py
│   ├── test_cli.py
│   ├── test_completer.py
│   ├── test_config.py
│   ├── test_contextio.py
│   ├── test_execution.py
│   ├── test_installation.py
│   ├── test_interaction.py
│   ├── test_lexer.py
│   ├── test_tree.py
│   ├── test_utils.py
│   ├── test_xdg.py
│   └── utils.py
└── tox.ini

================================================
FILE CONTENTS
================================================

================================================
FILE: .coveragerc
================================================
[report]
show_missing = True
exclude_lines =
    nocover


================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on: [push, pull_request]
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macOS-latest, windows-latest]
        python-version: [3.6, 3.7, 3.8, 3.9]
    steps:
      - uses: actions/checkout@v1
      - uses: actions/setup-python@v1
        with:
          python-version: ${{ matrix.python-version }}
      - run: python -m pip install -U pip setuptools wheel
      - run: python -m pip install -r requirements-test.txt
      - run: python -m pip install -e .
      - run: python -m pytest


================================================
FILE: .gitignore
================================================
*.egg-info
*.pyc
.cache
.coverage
.DS_Store
.python-version
.tox
_build
build
dist
data.json
venv*


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2016-2021 Chang-Hung Liang

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: MANIFEST.in
================================================
include README.rst LICENSE requirements.txt requirements-test.txt


================================================
FILE: Makefile
================================================
.PHONY: build

install:
	python -m pip install -e .
	python -m pip install -r requirements-test.txt

clean:
	rm -rf dist/ build/

test:
	python -m pytest

build:
	python setup.py sdist bdist_wheel

check:
	twine check dist/*

upload:
	twine upload --repository=http-prompt dist/*

release: test clean build check upload


================================================
FILE: README.rst
================================================
HTTP Prompt
===========

|PyPI| |Docs| |Build| |Coverage| |Discord|

HTTP Prompt is an interactive command-line HTTP client featuring autocomplete
and syntax highlighting, built on HTTPie_ and prompt_toolkit_.

|Asciinema|


Links
-----

* Home: https://http-prompt.com
* Documentation: https://docs.http-prompt.com
* Code: https://github.com/httpie/http-prompt
* Chat: https://httpie.io/chat


.. |PyPI| image:: https://img.shields.io/pypi/v/http-prompt.svg
    :target: https://pypi.python.org/pypi/http-prompt

.. |Docs| image:: https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat
    :target: http://docs.http-prompt.com/en/latest/?badge=latest

.. |Build| image:: https://github.com/httpie/http-prompt/workflows/Build/badge.svg
    :target: https://github.com/httpie/http-prompt/actions

.. |Coverage| image:: https://coveralls.io/repos/github/eliangcs/http-prompt/badge.svg?branch=master
    :target: https://coveralls.io/github/eliangcs/http-prompt?branch=master

.. |Discord| image:: https://img.shields.io/badge/chat-on%20Discord-brightgreen?style=flat-square
    :target: https://httpie.io/chat

.. |Asciinema| image:: https://asciinema.org/a/96613.png
    :target: https://asciinema.org/a/96613?theme=monokai&size=medium&autoplay=1&speed=1.5

.. _HTTPie: https://httpie.io
.. _prompt_toolkit: https://github.com/jonathanslenders/python-prompt-toolkit


================================================
FILE: docs/LICENSE
================================================
Refer to LICENSE in the main repo:

https://github.com/httpie/http-prompt


================================================
FILE: docs/Makefile
================================================
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line.
SPHINXOPTS    =
SPHINXBUILD   = sphinx-build
SPHINXPROJ    = HTTPPrompt
SOURCEDIR     = .
BUILDDIR      = _build

# Put it first so that "make" without argument is like "make help".
help:
	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

================================================
FILE: docs/README.md
================================================
# HTTP Prompt Documentation

This repo contains the documentation for HTTP Prompt, published on
http://docs.http-prompt.com. The source code of HTTP Prompt can be found in the
main repo: https://github.com/httpie/http-prompt

## How to Build

```
pip install sphinx
make html
open _build/html/index.html
```


================================================
FILE: docs/_templates/layout.html
================================================
{%- extends "!layout.html" %}


================================================
FILE: docs/conf.py
================================================
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# HTTP Prompt documentation build configuration file, created by
# sphinx-quickstart on Wed Dec 21 20:28:44 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
from collections import OrderedDict

from http_prompt import __version__

# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.autodoc']

# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = 'HTTP Prompt'
copyright = '2016-17, Chang-Hung Liang'
author = 'Chang-Hung Liang'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = __version__
# The full version, including alpha/beta/rc tags.
release = version

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False


# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']


# -- Options for HTMLHelp output ------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = 'HTTPPromptdoc'


# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
    # The paper size ('letterpaper' or 'a4paper').
    #
    # 'papersize': 'letterpaper',

    # The font size ('10pt', '11pt' or '12pt').
    #
    # 'pointsize': '10pt',

    # Additional stuff for the LaTeX preamble.
    #
    # 'preamble': '',

    # Latex figure (float) alignment
    #
    # 'figure_align': 'htbp',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
    (master_doc, 'HTTPPrompt.tex', 'HTTP Prompt Documentation',
     'Chang-Hung Liang', 'manual'),
]


# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, 'httpprompt', 'HTTP Prompt Documentation',
     [author], 1)
]


# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
    (master_doc, 'HTTPPrompt', 'HTTP Prompt Documentation',
     author, 'HTTPPrompt', 'One line description of project.',
     'Miscellaneous'),
]


html_sidebars = {
    '**': [
        'localtoc.html',
        'navigation.html'
    ]
}


html_theme_options = {
    'extra_nav_links': OrderedDict([
        ('Home', 'http://http-prompt.com'),
        ('Discord', 'https://httpie.io/chat'),
        ('Code on GitHub', 'https://github.com/httpie/http-prompt'),
    ])
}


================================================
FILE: docs/contributor-guide.rst
================================================
.. _contributor-guide:

Contributor Guide
=================

This document is for developers who want to contribute code to this project.
Any contributions are welcome and greatly appreciated!

This project follows the common conventions of a Python/GitHub project. So if
you're already an experienced Python/GitHub user, it should be straightforward
for you to set up your development environment and send patches. Generally, the
steps include:

1. Fork and clone the repo
2. Create a virtualenv for this project
3. Install dependent packages with ``pip install -e .``
4. Install test dependent packages with ``pip install -r requirements-test.txt``
5. Make your changes to the code
6. Run tests with ``pytest`` and ``tox``
7. Commit and push your changes
8. Send a pull request
9. Wait to be reviewed and get merged!

If you're not familiar with any of the above steps, read the following
instructions.


Forking
-------

Fork_ is like copying someone else's project to your account, so you can start
your own independent development without interfering with the original one.

To fork HTTP Prompt, just click the **Fork** button on HTTP Prompt's GitHub
project page. Then you clone your fork to your local computer::

    $ cd ~/Projects
    $ git clone git@github.com:{YOUR_USERNAME}/http-prompt.git

Read `Forking Projects`_ on GitHub to learn more.


Working with virtualenv
-----------------------

*virtualenv* is the de facto standard tool when developing a Python project.
Instead of polluting your system-wide Python installation with different Python
projects, virtualenv creates an isolated Python environment exclusively for a
Python project.

There are several tools you can use for managing virtualenvs. In this guide,
we'll show you how to use pyenv_ and pyenv-virtualenv_, which is one of the
most popular virtualenv management tools.

Make sure you have installed pyenv_ and pyenv-virtualenv_ first.

HTTP Prompt should work on Python 3.6 and newer. You can use any
of these Python versions as your development environment, but using the latest
version (3.6.x) is probably the best. You can install the latest Python with
pyenv::

    $ pyenv install 3.6.0

This will install Python 3.6.0 in ``~/.pyenv/versions/3.6.0`` directory. To
create a virtualenv for HTTP Prompt, do::

    $ pyenv virtualenv 3.6.0 http-prompt

The command means: create a virtualenv named "http-prompt" based on Python
3.6.0. The virtualenv can be found at ``~/.pyenv/versions/3.6.0/envs/http-prompt``.

To activate the virtualenv, do::

    $ pyenv activate http-prompt

This will switch your Python environment from the system-wide Python to the
virtualenv's (named "http-prompt") Python.

To go back to the system-wide Python, you have to deactivate the virtualenv::

    $ pyenv deactivate

Refer to pyenv_ and pyenv-virtualenv_ if anything else is unclear.


Installing Dependent Packages
-----------------------------

The dependent packages should be installed on a virtualenv, so make sure you
activate your virtualenv first. If not, do::

    $ pyenv activate http-prompt

It is also recommended to use the latest version of pip. You can upgrade it
with::

    $ pip install -U pip

Install HTTP Prompt with its dependent packages::

    $ cd ~/Projects/http-prompt
    $ pip install -e .

``pip install -e .`` means install the ``http-prompt`` package in editable mode
(or developer mode). This allows you to edit code directly in
``~/Projects/http-prompt`` without reinstalling the package. Without the ``-e``
option, the package will be installed to Python's ``site-packages`` directory,
which is not convenient for developing.


Installing Test Dependent Packages
----------------------------------

Test requirements are placed in a separate file named ``requirements-test.txt``.
To install them, do::

    $ cd ~/Projects/http-prompt
    $ pip install -r requirements-test.txt


Making Your Changes
-------------------

Code Style
~~~~~~~~~~

Always lint your code with Flake8_. You can set it up in your code editor or
simply use ``flake8`` in the command line.

`The Hitchhiker’s Guide to Python`_ provides the best Python coding practices.
We recommend anyone who wants to write good Python code to read it.

Adding Features
~~~~~~~~~~~~~~~

Before you add a new feature, make sure you create an issue making a proposal
first, because you don't want to waste your time on something that the
community don't agree upon.

Python Compatibility
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

HTTP Prompt is compatible with Python 3.6+.

Documentation
~~~~~~~~~~~~~

Documentation is written in Sphinx_. To build documentation, you need to
install Sphinx_ first::

    $ pip install sphinx

To build and view documentation in HTML, do::

    $ cd ~/Projects/http-prompt/docs
    $ make html
    $ open _build/html/index.html


Running Tests
-------------

Single Python Version
~~~~~~~~~~~~~~~~~~~~~

Make sure your virtualenv is activated. To run tests, do::

    $ cd ~/Projects/http-prompt
    $ pytest

``pytest`` runs the tests with your virtualenv's Python version. This is good
for fast testing. To test the code against multiple Python versions, you use
Tox_.

Multiple Python Versions
~~~~~~~~~~~~~~~~~~~~~~~~

All the commands in this section should **NOT** be run in a virtualenv.
Deactivate it first if you're in a virtualenv::

    $ pyenv deactivate

Make sure you have installed all the Python versions we're targeting. If not,
do::

    $ pyenv install 3.6.0
    $ pyenv install 3.7.0
    $ pyenv install 3.8.0

To use Tox_ with pyenv_, you have to instruct pyenv to use multiple Python
versions for the project::

    $ cd ~/Projects/http-prompt
    $ pyenv local 3.6.0 3.7.0 3.8.0

This will generate a ``.python-version`` in the project directory::

    $ cat ~/Projects/http-prompt/.python-version
    3.6.0
    3.7.0
    3.8.0

This tells pyenv_ to choose a Python version based on the above order. In this
case, 3.6.0 is the first choice, so any Python executables (such as ``python``
and ``pip``) will be automatically mapped to the ones in
``~/.pyenv/versions/3.8.0/bin``.

We want to run ``tox`` using on Python 3.8.0. Make sure you have installed
Tox_::

    $ pip install tox

To run tests, execute ``tox``::

    $ cd ~/Projects/http-prompt
    $ tox

Tox_ will install the test Python environments in the ``.tox/`` directory in
the project directory, and run the test code against all the Python versions
listed above.


Code Review
-----------

Once you made changes and all the tests pass, push your modified code to your
GitHub account. Submit a pull request (PR) on GitHub for the maintainers to
review. If the patch is good, The maintainers will merge it to the master
branch and ship the new code in the next release. If the patch needs
improvements, we'll give you feedback so you can modify accordingly and
resubmit it to the PR.


.. _Flake8: http://flake8.pycqa.org/en/latest/index.html
.. _Fork: https://en.wikipedia.org/wiki/Fork_(software_development)
.. _Forking Projects: https://guides.github.com/activities/forking/
.. _pyenv-virtualenv: https://github.com/yyuu/pyenv-virtualenv
.. _pyenv: https://github.com/yyuu/pyenv
.. _Sphinx: http://www.sphinx-doc.org/
.. _The Hitchhiker’s Guide to Python: http://docs.python-guide.org/en/latest/
.. _Tox: https://tox.readthedocs.io/en/latest/


================================================
FILE: docs/index.rst
================================================
HTTP Prompt Documentation
=========================

HTTP Prompt is an interactive command-line HTTP client featuring autocomplete
and syntax highlighting, built on HTTPie_ and prompt_toolkit_.

See it in action:

|Asciinema|


Contents
--------

.. toctree::
   :maxdepth: 3

   user-guide
   contributor-guide


Roadmap
-------

* Support for advanced HTTPie syntax, e.g, ``field=@file.json``
* Support for cURL command and raw format preview
* Improve autocomplete
* Python syntax evaluation
* HTTP/2 support


User Support
------------

We'd love to hear more from our users! Please use the following channels for
bug reports, feature requests, and questions:

* `GitHub issues`_
* `Gitter chat room`_


Contributing
------------

Are you a developer and interested in contributing to HTTP Prompt? See
:ref:`Contributor Guide <contributor-guide>`.


Thanks
------

* HTTPie_: for designing such a user-friendly HTTP CLI
* prompt_toolkit_: for simplifying the work of building an interactive CLI
* Parsimonious_: for the PEG parser used by this project
* pgcli_: for the inspiration of this project
* Contributors_: for improving this project


.. |Asciinema| image:: https://asciinema.org/a/96613.png
    :target: https://asciinema.org/a/96613?theme=monokai&size=medium&autoplay=1&speed=1.5

.. _Contributors: https://github.com/eliangcs/http-prompt/graphs/contributors
.. _GitHub issues: https://github.com/httpie/http-prompt/issues
.. _Discord: https://htpie.io/chat
.. _HTTPie: https://httpie.io
.. _Parsimonious: https://github.com/erikrose/parsimonious
.. _pgcli: http://pgcli.com
.. _prompt_toolkit: https://github.com/jonathanslenders/python-prompt-toolkit


================================================
FILE: docs/make.bat
================================================
@ECHO OFF

pushd %~dp0

REM Command file for Sphinx documentation

if "%SPHINXBUILD%" == "" (
	set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=.
set BUILDDIR=_build
set SPHINXPROJ=HTTPPrompt

if "%1" == "" goto help

%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
	echo.
	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
	echo.installed, then set the SPHINXBUILD environment variable to point
	echo.to the full path of the 'sphinx-build' executable. Alternatively you
	echo.may add the Sphinx directory to PATH.
	echo.
	echo.If you don't have Sphinx installed, grab it from
	echo.http://sphinx-doc.org/
	exit /b 1
)

%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
goto end

:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%

:end
popd


================================================
FILE: docs/user-guide.rst
================================================
.. _user-guide:

User Guide
==========

Installation
------------

Just install it like a regular Python package::

    $ pip install http-prompt

You'll probably see some permission errors if you're trying to install it on
the system-wide Python. It isn't recommended. But if that's what you want to
do, you need to ``sudo``::

    $ sudo pip install http-prompt

Another alternative is to use ``--user`` option to install the package into
your user directory::

    $ pip install --user http-prompt

To upgrade HTTP Prompt, do::

    $ pip install -U http-prompt

It's also possible to install it using Homebrew::

    $ brew install http-prompt


Quickstart
----------

To start a session, you use the ``http-prompt`` executable:

.. code-block:: bash

    # Start with the last session or http://localhost:8000
    $ http-prompt

    # Start with the given URL
    $ http-prompt http://httpbin.org

    # Start with some initial options
    $ http-prompt localhost:8000/api --auth user:pass username=somebody

Once you're in a session, you can use the following commands.

To change URL address, use ``cd``:

.. code-block:: bash

    # Relative URL path
    > cd api/v1

    # Absolute URL
    > cd http://localhost/api

To add headers, querystring, or body parameters, use the syntax as in HTTPie_.
The following are all valid:

.. code-block:: bash

    # Header
    > Content-Type:application/json

    # Querystring parameter
    > page==2

    # Body parameters
    > username=foo
    > full_name='foo bar'

    # Body parameters in raw JSON (new in v0.9.0)
    > number:=1234
    > is_ok:=true
    > names:=["foo","bar"]
    > user:='{"username": "foo", "password": "bar"}'

    # Write them in one line
    > Content-Type:application/json page==2 username=foo

You can also add HTTPie_ options like this:

.. code-block:: bash

    > --form --auth user:pass
    > --verify=no

    # HTTPie options and request parameters in one line
    > --form --auth user:pass username=foo Content-Type:application/json

To preview how HTTP Prompt is going to call HTTPie_, do:

.. code-block:: bash

    > httpie post
    http --auth user:pass --form POST http://localhost/api apikey==abc username=john

You can temporarily override the request parameters by supplying options and
parameters in ``httpie`` command. The overrides won't affect the later
requests.

.. code-block:: bash

    # No parameters initially
    > httpie
    http http://localhost

    # Override parameters temporarily
    > httpie /api/something page==2 --json
    http --json http://localhost/api/something page==2

    # Current state is not affected by the above overrides
    > httpie
    http http://localhost

Since v0.6.0, apart from ``httpie`` command, you can also use ``env`` to print
the current session:

.. code-block:: bash

    > env
    --verify=no
    cd http://localhost
    page==10
    limit==20

To actually send an HTTP request, enter one of the HTTP methods:

.. code-block:: bash

    > get
    > post
    > put
    > patch
    > delete
    > head
    > options (new in v0.8.0)

The above HTTP methods also support temporary overriding:

.. code-block:: bash

    # No parameters initially
    > httpie
    http http://localhost

    # Send a request with some overrided parameters
    > post /api/v1 --form name=jane

    # Current state remains intact
    > httpie
    http http://localhost

To remove an existing header, a querystring parameter, a body parameter, or an
HTTPie_ option:

.. code-block:: bash

    # Remove a header
    > rm -h Content-Type

    # Remove a querystring parameter
    > rm -q apikey

    # Remove a body parameter
    > rm -b username

    # Remove an HTTPie option
    > rm -o --auth

To reset the session, i.e., clear all parameters and options:

.. code-block:: bash

    > rm *

To exit a session, simply enter:

.. code-block:: bash

    > exit


Output Redirection
------------------

*New in v0.6.0.*

You can redirect the output of a command to a file by using the syntax:

.. code-block:: bash

    # Write output to a file
    > COMMAND > /path/to/file

    # Append output to a file
    > COMMAND >> /path/to/file

where ``COMMAND`` can be one of the following:

* ``env``
* ``httpie``
* HTTP actions: ``get``, ``post``, ``put``, ``patch``, ``delete``, ``head``,
  ``options``


Saving and Loading Sessions
~~~~~~~~~~~~~~~~~~~~~~~~~~~

One of the use cases of output redirection is to save and load sessions, which
is especially useful for team collaboration, where you want to share your
sessions with your team members.

To save your current session, you redirect the output of ``env`` to a file:

.. code-block:: bash

    > env > /path/to/file

To load a saved session, you can use ``source`` or ``exec``. Their only
difference is that ``exec`` wipes out the current session before loading.
Usage:

.. code-block:: bash

    # Update the current session
    > source /path/to/file

    # Wipe out the current session and load from a file
    > exec /path/to/file

*New in v0.11.0.*

Load a saved session from the command line directly with the ``--env`` option.
This allows you for example to define aliases and easily start HTTP Prompt with
a full configuration already loaded for each of your projects.

.. code-block:: bash

    # Define alias for project1
    $ alias http_project1='http-prompt --env /path/to/project1/env/file'

    # Launch HTTP Prompt for project1
    $ http_project1

Any extra argument in the command line is still used and overwrites the value
from the session file if already present

.. code-block:: bash

    # Use saved session but overwrite the URL and add a parameter
    $ http-prompt --env /path/to/file localhost:8080 page==2


Saving HTTP Responses
~~~~~~~~~~~~~~~~~~~~~

Printing HTTP responses to the console is good for small text responses. For
larger text or binary data, you may want to save the response to a file. Usage:

.. code-block:: bash

    # Save http://httpbin.org/image/png to a file
    > cd http://httpbin.org/image/png
    > get > pig.png

    # Or use this one-liner
    > get http://httpbin.org/image/png > pig.png


Pipeline
--------

*New in v0.7.0.*

HTTP Prompt supports simplified pipeline syntax, where you can pipe the output
to a shell command:

.. code-block:: bash

    # Replace 'localhost' to '127.0.0.1'
    > httpie POST http://localhost | sed 's/localhost/127.0.0.1/'
    http http://127.0.0.1

    # Only print the line that contains 'User-Agent' using grep
    > get http://httpbin.org/get | grep 'User-Agent'
        "User-Agent": "HTTPie/0.9.6"

On macOS, you can even copy the result to the clipboard using ``pbcopy``:

.. code-block:: bash

    # Copy the HTTPie command to the clipboard (macOS only)
    > httpie | pbcopy

Another cool trick is to use jq_ to parse JSON data:

.. code-block:: bash

    > get http://httpbin.org/get | jq '.headers."User-Agent"'
    "HTTPie/0.9.6"

**Note**: Syntax with multiple pipes is not supported currently.


Shell Substitution
------------------

*New in v0.7.0.*

Shell substitution happens when you put a shell command between two backticks
like ```...```. This syntax allows you compute a value from the shell
environment and assign the value to a parameter::

    # Set date to current time
    > date==`date -u +"%Y-%m-%d %H:%M:%S"`
    > httpie
    http http://localhost:8000 'date==2016-10-08 09:45:00'

    # Get password from a file. Suppose the file has a content of
    # "secret_api_key".
    > password==`cat ./apikey.txt`
    > httpie
    http http://localhost:8000 password==secret_api_key


Configuration
-------------

*New in v0.4.0.*

When launched for the first time, HTTP Prompt creates a user config file at
``$XDG_CONFIG_HOME/http-prompt/config.py`` (or ``%LOCALAPPDATA%/http-prompt/config.py``
on Windows). By default, it's ``~/.config/http-prompt/config.py`` (or
``~/AppData/Local/http-prompt/config.py``).

``config.py`` is a Python module with all the available options you can
customize. Don't worry. You don't need to know Python to edit it. Just open it
up with a text editor and follow the guidance inside.


Persistent Context
------------------

*New in v0.4.0.*

HTTP Prompt keeps a data structure called *context* to represent your current
session. Every time you enter a command modifying your context, HTTP Prompt
saves the context to your filesystem, enabling you to resume your previous
session when you restart ``http-prompt``.

The last saved context is located at ``$XDG_DATA_HOME/http-prompt/context.hp``
(or ``%LOCALAPPDATA%/http-prompt/context.hp`` on Windows). By default, it's
``~/.local/share/http-prompt/context.hp`` (or ``~/AppData/Local/http-prompt/context.hp``).

As context data may contain sensitive data like API keys, you should keep the
user data directory private. By default, HTTP Prompt sets the modes of
``$XDG_DATA_HOME/http-prompt`` to ``rwx------`` (i.e., ``700``) so that the
only person who can read it is the owner (you).

**Note for users of older versions**: Since 0.6.0, HTTP Prompt only stores the
last context instead of grouping multiple contexts by hostnames and ports like
it did previously. We changed the behavior because the feature can be simply
replaced by ``env``, ``exec`` and ``source`` commands. See the discussion in
`issue #70 <https://github.com/httpie/http-prompt/issues/70>`_ for detail.


``ls``, ``cd``, and OpenAPI/Swagger Specification
-------------------------------------------------

*New in v0.10.0.*

OpenAPI_ (formerly known as Swagger_) is a specification that describes an
HTTP/REST API. The ``http-prompt`` has a ``--spec`` option for you to provide
an OpenAPI specification in JSON format. The specification enables HTTP Prompt
to do some cool things like autocomplete API endpoint paths and parameters
for you.

See it in action:

|ls-demo|

To use this feature, specify an OpenAPI/Swagger specification file with
``--spec`` command line option::

    # Specify a spec on local filesystem
    $ http-prompt http://localhost:8000 --spec /path/to/spec.json

    # Specify a spec on the internet (https://apis.guru has lots of them)
    $ http-prompt https://api.github.com --spec https://api.apis.guru/v2/specs/github.com/v3/swagger.json

Then you can use ``ls`` and ``cd`` commands to navigate API endpoints with
autocomplete!


.. |ls-demo| image:: https://asciinema.org/a/107732.png
    :target: https://asciinema.org/a/107732

.. _HTTPie: https://httpie.org
.. _jq: https://stedolan.github.io/jq/
.. _OpenAPI: https://openapis.org
.. _Swagger: http://swagger.io/


================================================
FILE: http_prompt/__init__.py
================================================
__version__ = '2.1.0'


================================================
FILE: http_prompt/cli.py
================================================
import json
from http.cookies import SimpleCookie
from urllib.request import pathname2url, urlopen

import yaml
import os
import re
import sys

import click

from httpie.plugins import FormatterPlugin  # noqa, avoid cyclic import
from httpie.output.formatters.colors import Solarized256Style
from prompt_toolkit import prompt
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.history import FileHistory
from prompt_toolkit.lexers import PygmentsLexer
from prompt_toolkit.styles.pygments import style_from_pygments_cls
from pygments.styles import get_style_by_name
from pygments.util import ClassNotFound

from . import __version__
from . import config
from .completer import HttpPromptCompleter
from .context import Context
from .contextio import load_context, save_context
from .execution import execute
from .lexer import HttpPromptLexer
from .utils import smart_quote
from .xdg import get_data_dir


def fix_incomplete_url(url):
    if url.startswith(('s://', '://')):
        url = 'http' + url
    elif url.startswith('//'):
        url = 'http:' + url
    elif not url.startswith(('http://', 'https://')):
        url = 'http://' + url
    return url


def update_cookies(base_value, cookies):
    cookie = SimpleCookie(base_value)
    for k, v in cookies.items():
        cookie[k] = v
    return str(cookie.output(header='', sep=';').lstrip())


class ExecutionListener(object):

    def __init__(self, cfg):
        self.cfg = cfg

    def context_changed(self, context):
        # Dump the current context to HTTP Prompt format
        save_context(context)

    def response_returned(self, context, response):
        if not response.cookies:
            return

        cookie_pref = self.cfg.get('set_cookies') or 'auto'
        if cookie_pref == 'auto' or (
                cookie_pref == 'ask' and
                click.confirm('Cookies incoming! Do you want to set them?')):
            existing_cookie = context.headers.get('Cookie')
            new_cookie = update_cookies(existing_cookie, response.cookies)
            context.headers['Cookie'] = new_cookie
            click.secho('Cookies set: %s' % new_cookie)


def normalize_url(ctx, param, value):
    if value:
        if not re.search(r'^\w+://', value):
            value = 'file:' + pathname2url(os.path.abspath(value))
        return value
    return None


@click.command(context_settings={'ignore_unknown_options': True})
@click.option('--spec', help='OpenAPI/Swagger specification file.',
              callback=normalize_url)
@click.option('--env', help='Environment file to preload.',
              type=click.Path(exists=True))
@click.argument('url', default='')
@click.argument('http_options', nargs=-1, type=click.UNPROCESSED)
@click.version_option(message='%(version)s')
def cli(spec, env, url, http_options):
    click.echo('Version: %s' % __version__)

    copied, config_path = config.initialize()
    if copied:
        click.echo('Config file not found. Initialized a new one: %s' %
                   config_path)

    cfg = config.load()

    # Override pager/less options
    os.environ['PAGER'] = cfg['pager']
    os.environ['LESS'] = '-RXF'

    if spec:
        f = urlopen(spec)
        try:
            content = f.read().decode()
            try:
                spec = json.loads(content)
            except json.JSONDecodeError:
                try:
                    spec = yaml.safe_load(content)
                except yaml.YAMLError:
                    click.secho("Warning: Specification file '%s' is neither valid JSON nor YAML" %
                                spec, err=True, fg='red')
                    spec = None
        finally:
            f.close()

    if url:
        url = fix_incomplete_url(url)
    context = Context(url, spec=spec)

    output_style = cfg.get('output_style')
    if output_style:
        context.options['--style'] = output_style

    # For prompt-toolkit
    history = FileHistory(os.path.join(get_data_dir(), 'history'))
    lexer = PygmentsLexer(HttpPromptLexer)
    completer = HttpPromptCompleter(context)
    try:
        style_class = get_style_by_name(cfg['command_style'])
    except ClassNotFound:
        style_class = Solarized256Style
    style = style_from_pygments_cls(style_class)

    listener = ExecutionListener(cfg)

    if len(sys.argv) == 1:
        # load previous context if nothing defined
        load_context(context)
    else:
        if env:
            load_context(context, env)
            if url:
                # Overwrite the env url if not default
                context.url = url

        if http_options:
            # Execute HTTPie options from CLI (can overwrite env file values)
            http_options = [smart_quote(a) for a in http_options]
            execute(' '.join(http_options), context, listener=listener)

    while True:
        try:
            text = prompt('%s> ' % context.url, completer=completer,
                          lexer=lexer, style=style, history=history,
                          auto_suggest=AutoSuggestFromHistory(),
                          vi_mode=cfg['vi'])
        except KeyboardInterrupt:
            continue  # Control-C pressed
        except EOFError:
            break  # Control-D pressed
        else:
            execute(text, context, listener=listener, style=style_class)
            if context.should_exit:
                break

    click.echo('Goodbye!')


================================================
FILE: http_prompt/completer.py
================================================
# -*- coding: utf-8 -*-
import re

from collections import OrderedDict

from itertools import chain
from urllib.parse import urlparse

from prompt_toolkit.completion import Completer, Completion

from .completion import (ROOT_COMMANDS, ACTIONS, OPTION_NAMES, HEADER_NAMES,
                         HEADER_VALUES)


RULES = [
    # (regex pattern, a method name in CompletionGenerator)
    (r'((?:[^\s\'"\\=:]|(?:\\.))+):((?:[^\s\'"\\]|(?:\\.))*)$',
     'header_values'),

    (r'(get|head|post|put|patch|delete|connect)\s+', 'concat_mutations'),
    (r'(httpie|curl)\s+', 'preview'),
    (r'rm\s+\-b\s+', 'existing_body_params'),
    (r'rm\s+\-h\s+', 'existing_header_names'),
    (r'rm\s+\-o\s+', 'existing_option_names'),
    (r'rm\s+\-q\s+', 'existing_querystring_params'),

    # The last two captures are full URL path and the last part of the URL
    # path. For example:
    # '/foo/bar' => ('/foo/bar', 'bar')
    # '/foo/bar/' => ('/foo/bar/', '')
    # 'foo/bar' => ('foo/bar', 'bar')
    (r'(ls|cd)\s+(/?(?:[^/]+/)*([^/]*)/?)$', 'urlpaths'),
    (r'^\s*[^\s]*$', 'root_commands')
]


def compile_rules(rules):
    compiled_rules = []
    for pattern, meta_dict in rules:
        regex = re.compile(pattern)
        compiled_rules.append((regex, meta_dict))
    return compiled_rules


RULES = compile_rules(RULES)


def fuzzyfinder(text, collection):
    """https://github.com/amjith/fuzzyfinder"""
    suggestions = []
    if not isinstance(text, str):
        text = str(text)
    pat = '.*?'.join(map(re.escape, text))
    regex = re.compile(pat, flags=re.IGNORECASE)
    for item in collection:
        r = regex.search(item)
        if r:
            suggestions.append((len(r.group()), r.start(), item))

    return (z for _, _, z in sorted(suggestions))


def match_completions(cur_word, word_dict):
    words = word_dict.keys()
    suggestions = fuzzyfinder(cur_word, words)
    for word in suggestions:
        desc = word_dict.get(word, '')
        yield Completion(word, -len(cur_word), display_meta=desc)


class CompletionGenerator(object):

    def root_commands(self, context, match):
        return chain(
            self._generic_generate(ROOT_COMMANDS.keys(), {}, ROOT_COMMANDS),
            self.actions(context, match),
            self.concat_mutations(context, match)
        )

    def header_values(self, context, match):
        header_name = match.group(1)
        header_values = HEADER_VALUES.get(header_name)
        if header_values:
            for value in header_values:
                yield value, header_name

    def preview(self, context, match):
        return chain(
            self.actions(context, match),
            self.concat_mutations(context, match)
        )

    def actions(self, context, match):
        return self._generic_generate(ACTIONS.keys(), {}, ACTIONS)

    def concat_mutations(self, context, match):
        return chain(
            self._generic_generate(context.body_params.keys(),
                                   context.body_params, 'Body parameter'),
            self._generic_generate(context.querystring_params.keys(),
                                   context.querystring_params,
                                   'Querystring parameter'),
            self._generic_generate(HEADER_NAMES.keys(),
                                   context.headers, HEADER_NAMES),
            self._generic_generate(OPTION_NAMES.keys(),
                                   context.options, OPTION_NAMES)
        )

    def existing_body_params(self, context, match):
        params = context.body_params.copy()
        params.update(context.body_json_params)
        return self._generic_generate(params.keys(), params, 'Body parameter')

    def existing_querystring_params(self, context, match):
        return self._generic_generate(
            context.querystring_params.keys(),
            context.querystring_params, 'Querystring parameter')

    def existing_header_names(self, context, match):
        return self._generic_generate(context.headers.keys(),
                                      context.headers, HEADER_NAMES)

    def existing_option_names(self, context, match):
        return self._generic_generate(context.options.keys(),
                                      context.options, OPTION_NAMES)

    def urlpaths(self, context, match):
        path = urlparse(context.url).path.split('/')
        overrided_path = match.group(2)
        if overrided_path:
            if overrided_path.startswith('/'):
                # Absolute path
                path = []
            path += overrided_path.split('/')[:-1]
        names = [
            node.name for node in context.root.ls(*path)
            if node.data.get('type') == 'dir'
        ]
        return self._generic_generate(names, {}, 'Endpoint')

    def _generic_generate(self, names, values, descs):
        for name in sorted(names):
            if isinstance(descs, str):
                desc = descs
            else:
                desc = descs.get(name, '')
            if name in values:
                value = values[name]
                if value is None:
                    desc += ' (on)'
                else:
                    value = str(value)
                    if len(value) > 16:
                        value = value[:13] + '...'
                    desc += ' (=%s)' % value
            yield name, desc


class HttpPromptCompleter(Completer):

    def __init__(self, context):
        self.context = context
        self.comp_gen = CompletionGenerator()

    def get_completions(self, document, complete_event):
        cur_text = document.text_before_cursor
        cur_word = None
        word_dict = None

        for regex, method_name in RULES:
            match = regex.search(cur_text)
            if match:
                gen_completions = getattr(self.comp_gen, method_name)
                completions = gen_completions(self.context, match)
                word_dict = OrderedDict(completions)

                groups = match.groups()
                if len(groups) > 1:
                    cur_word = groups[-1]
                else:
                    cur_word = document.get_word_before_cursor(WORD=True)

                break

        if word_dict:
            for comp in match_completions(cur_word, word_dict):
                yield comp


================================================
FILE: http_prompt/completion.py
================================================
"""Meta data for autocomplete."""

from collections import OrderedDict

from . import options as opt


ROOT_COMMANDS = OrderedDict([
    ('cd', 'Change URL/path'),
    ('clear', 'Clear console screen'),
    ('curl', 'Preview curl command'),
    ('env', 'Print environment'),
    ('exec', 'Clear and load environment from a file'),
    ('exit', 'Exit HTTP Prompt'),
    ('help', 'List commands, actions, and HTTPie options'),
    ('httpie', 'Preview HTTPie command'),
    ('rm *', 'Remove all options and parameters'),
    ('rm -b', 'Remove body parameter'),
    ('rm -b *', 'Remove all body parameters'),
    ('rm -h', 'Remove header'),
    ('rm -h *', 'Remove all headers'),
    ('rm -o', 'Remove HTTPie option'),
    ('rm -o *', 'Remove all HTTPie options'),
    ('rm -q', 'Remove querystring parameter'),
    ('rm -q *', 'Remove all querystring parameters'),
    ('source', 'Load environment from a file'),
])

ACTIONS = OrderedDict([
    ('connect', 'CONNECT request'),
    ('delete', 'DELETE request'),
    ('get', 'GET request'),
    ('head', 'HEAD request'),
    ('options', 'OPTIONS request'),
    ('patch', 'GET request'),
    ('post', 'POST request'),
    ('put', 'PUT request'),
])

# http://www.iana.org/assignments/message-headers/message-headers.xhtml
# https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
HEADER_NAMES = OrderedDict([
    ('Accept', 'Acceptable response media type'),
    ('Accept-Charset', 'Acceptable response charsets'),
    ('Accept-Encoding', 'Acceptable response content codings'),
    ('Accept-Language', 'Preferred natural languages in response'),
    ('ALPN', 'Application-layer protocol negotiation to use'),
    ('Alt-Used', 'Alternative host in use'),
    ('Authorization', 'Authentication information'),
    ('Cache-Control', 'Directives for caches'),
    ('Connection', 'Connection options'),
    ('Content-Encoding', 'Content codings'),
    ('Content-Language', 'Natural languages for content'),
    ('Content-Length', 'Anticipated size for payload body'),
    ('Content-Location', 'Where content was obtained'),
    ('Content-MD5', 'Base64-encoded MD5 sum of content'),
    ('Content-Type', 'Content media type'),
    ('Cookie', 'Stored cookies'),
    ('Date', 'Datetime when message was originated'),
    ('Depth', 'Applied only to resource or its members'),
    ('DNT', 'Do not track user'),
    ('Expect', 'Expected behaviors supported by server'),
    ('Forwarded', 'Proxies involved'),
    ('From', 'Sender email address'),
    ('Host', 'Target URI'),
    ('HTTP2-Settings', 'HTTP/2 connection parameters'),
    ('If', 'Request condition on state tokens and ETags'),
    ('If-Match', 'Request condition on target resource'),
    ('If-Modified-Since', 'Request condition on modification date'),
    ('If-None-Match', 'Request condition on target resource'),
    ('If-Range', 'Request condition on Range'),
    ('If-Schedule-Tag-Match', 'Request condition on Schedule-Tag'),
    ('If-Unmodified-Since', 'Request condition on modification date'),
    ('Max-Forwards', 'Max number of times forwarded by proxies'),
    ('MIME-Version', 'Version of MIME protocol'),
    ('Origin', 'Origin(s) issuing the request'),
    ('Pragma', 'Implementation-specific directives'),
    ('Prefer', 'Preferred server behaviors'),
    ('Proxy-Authorization', 'Proxy authorization credentials'),
    ('Proxy-Connection', 'Proxy connection options'),
    ('Range', 'Request transfer of only part of data'),
    ('Referer', 'Previous web page'),
    ('TE', 'Transfer codings willing to accept'),
    ('Transfer-Encoding', 'Transfer codings applied to payload body'),
    ('Upgrade', 'Invite server to upgrade to another protocol'),
    ('User-Agent', 'User agent string'),
    ('Via', 'Intermediate proxies'),
    ('Warning', 'Possible incorrectness with payload body'),
    ('WWW-Authenticate', 'Authentication scheme'),
    ('X-Csrf-Token', 'Prevent cross-site request forgery'),
    ('X-CSRFToken', 'Prevent cross-site request forgery'),
    ('X-Forwarded-For', 'Originating client IP address'),
    ('X-Forwarded-Host', 'Original host requested by client'),
    ('X-Forwarded-Proto', 'Originating protocol'),
    ('X-Http-Method-Override', 'Request method override'),
    ('X-Requested-With', 'Used to identify Ajax requests'),
    ('X-XSRF-TOKEN', 'Prevent cross-site request forgery'),
])

CONTENT_TYPES = [
    'application/json',
    'application/x-www-form-urlencoded',
    'multipart/form-data',
    'text/html',
]

# TODO: Include more common header values
HEADER_VALUES = {
    'Accept': CONTENT_TYPES,
    'Content-Type': CONTENT_TYPES,
}

OPTION_NAMES = sorted(opt.FLAG_OPTIONS + opt.VALUE_OPTIONS)
OPTION_NAMES = OrderedDict(OPTION_NAMES)


================================================
FILE: http_prompt/config.py
================================================
"""Functions that deal with the user configuration."""

import os
import shutil

from . import defaultconfig
from . import xdg


def get_user_config_path():
    """Get the path to the user config file."""
    return os.path.join(xdg.get_config_dir(), 'config.py')


def initialize():
    """Initialize a default config file if it doesn't exist yet.

    Returns:
        tuple: A tuple of (copied, dst_path). `copied` is a bool indicating if
            this function created the default config file. `dst_path` is the
            path of the user config file.
    """
    dst_path = get_user_config_path()
    copied = False
    if not os.path.exists(dst_path):
        src_path = os.path.join(os.path.dirname(__file__), 'defaultconfig.py')
        shutil.copyfile(src_path, dst_path)
        copied = True
    return copied, dst_path


def _module_to_dict(module):
    attrs = {}
    attr_names = filter(lambda n: not n.startswith('_'), dir(module))
    for name in attr_names:
        value = getattr(module, name)
        attrs[name] = value
    return attrs


def load_default():
    """Return default config as a dict."""
    return _module_to_dict(defaultconfig)


def load_user():
    """Read user config file and return it as a dict."""
    config_path = get_user_config_path()
    config = {}

    # TODO: This may be overkill and too slow just for reading a config file
    with open(config_path) as f:
        code = compile(f.read(), config_path, 'exec')
    exec(code, config)

    keys = list(config.keys())
    for k in keys:
        if k.startswith('_'):
            del config[k]

    return config


def load():
    """Read default and user config files and return them as a dict."""
    config = load_default()
    config.update(load_user())
    return config


================================================
FILE: http_prompt/context/__init__.py
================================================
from http_prompt.tree import Node


class Context(object):

    def __init__(self, url=None, spec=None):
        self.url = url
        self.headers = {}
        self.querystring_params = {}
        self.body_params = {}
        self.body_json_params = {}
        self.options = {}
        self.should_exit = False

        # Create a tree for supporting API spec and ls command
        self.root = Node('root')
        if spec:
            if not self.url:
                schemes = spec.get('schemes')
                scheme = schemes[0] if schemes else 'https'
                self.url = (scheme + '://' +
                            spec.get('host', 'http://localhost:8000') +
                            spec.get('basePath', ''))

            base_path_tokens = list(filter(lambda s: s,
                                    spec.get('basePath', '').split('/')))
            paths = spec.get('paths')
            if paths:
                for path in paths:
                    path_tokens = (base_path_tokens +
                                   list(filter(lambda s: s, path.split('/'))))
                    if path == '/':  # Path is a trailing slash
                        path_tokens.insert(len(base_path_tokens), '/')
                    elif path[-1] == '/':  # Path ends with a trailing slash
                        path_tokens[-1] = path_tokens[-1] + '/'
                    self.root.add_path(*path_tokens)
                    endpoint = dict(paths[path])
                    # path parameters (apply to all paths if not overriden)
                    # exclude $ref as we have no system to handle that now
                    global_parameters = list(endpoint.pop('parameters', []))
                    # not used
                    endpoint.pop('servers', None)
                    endpoint.pop('$ref', None)
                    endpoint.pop('summary', None)
                    endpoint.pop('description', None)
                    for method, info in endpoint.items():
                        params = info.get('parameters', [])
                        params = list(global_parameters + params)
                        if params:
                            def parameter_key(i): return (
                                i.get('$ref', None),
                                i.get('name', None),
                                i.get('in', None)
                            )
                            # parameter is overriden based on $ref/in/name value
                            # last value (local definition) takes precedence
                            params_map = {parameter_key(p): p for p in params}
                            params = params_map.values()
                            for param in params:
                                if param.get('$ref'):
                                    for section in param.get('$ref').split('/'):
                                        param = param.get(
                                            section) if not section == '#' else spec

                                if param.get('in') != 'path':
                                    # Note that for completion mechanism, only
                                    # name/node_type is used
                                    # Parameters from methods/location
                                    # are merged
                                    full_path = path_tokens + [param['name']]
                                    self.root.add_path(*full_path,
                                                       node_type='file')
        elif not self.url:
            self.url = 'http://localhost:8000'

    def __eq__(self, other):
        return (self.url == other.url and
                self.headers == other.headers and
                self.options == other.options and
                self.querystring_params == other.querystring_params and
                self.body_params == other.body_params and
                self.body_json_params == other.body_json_params and
                self.should_exit == other.should_exit)

    def copy(self):
        context = Context(self.url)
        context.headers = self.headers.copy()
        context.querystring_params = self.querystring_params.copy()
        context.body_params = self.body_params.copy()
        context.body_json_params = self.body_json_params.copy()
        context.options = self.options.copy()
        context.should_exit = self.should_exit
        return context

    def update(self, context):
        if context.url:
            self.url = context.url

        self.headers.update(context.headers)
        self.querystring_params.update(context.querystring_params)
        self.body_params.update(context.body_params)
        self.body_json_params.update(context.body_json_params)
        self.options.update(context.options)
        self.should_exit = self.should_exit


================================================
FILE: http_prompt/context/transform.py
================================================
"""Functions that transform a Context object to a different representation."""

import json

from http_prompt.utils import smart_quote


def _noop(s):
    return s


def _extract_httpie_options(context, quote=False, join_key_value=False,
                            excluded_keys=None):
    if quote:
        quote_func = smart_quote
    else:
        quote_func = _noop

    if join_key_value:
        def form_new_opts(k, v): return [k + '=' + v]
    else:
        def form_new_opts(k, v): return [k, v]

    excluded_keys = excluded_keys or []

    opts = []
    for k, v in sorted(context.options.items()):
        if k not in excluded_keys:
            if v is not None:
                v = quote_func(v)
                new_opts = form_new_opts(k, v)
            else:
                new_opts = [k]
            opts += new_opts
    return opts


def _extract_httpie_request_items(context, quote=False):
    if quote:
        quote_func = smart_quote
    else:
        quote_func = _noop

    items = []
    operators_and_items = [
        # (separator, dict_of_request_items)
        ('==', context.querystring_params),
        (':=', context.body_json_params),
        ('=', context.body_params),
        (':', context.headers)
    ]
    for sep, item_dict in operators_and_items:
        for k, value in sorted(item_dict.items()):
            if sep == ':=':
                json_str = json.dumps(value,
                                      sort_keys=True)
                item = '%s:=%s' % (k, quote_func(json_str))
                items.append(item)
            elif isinstance(value, (list, tuple)):
                for v in value:
                    item = quote_func('%s%s%s' % (k, sep, v))
                    items.append(item)
            else:
                item = quote_func('%s%s%s' % (k, sep, value))
                items.append(item)
    return items


def extract_args_for_httpie_main(context, method=None):
    """Transform a Context object to a list of arguments that can be passed to
    HTTPie main function.
    """
    args = _extract_httpie_options(context)

    if method:
        args.append(method.upper())

    args.append(context.url)
    args += _extract_httpie_request_items(context)
    return args


def format_to_curl(context, method=None):
    """Format a Context object to a cURL command."""
    raise NotImplementedError('curl format is not supported yet')


def format_to_raw(context, method=None):
    """Format a Context object to HTTP raw text."""
    raise NotImplementedError('raw format is not supported yet')


def format_to_httpie(context, method=None):
    """Format a Context object to an HTTPie command."""
    cmd = ['http'] + _extract_httpie_options(context, quote=True,
                                             join_key_value=True)
    if method:
        cmd.append(method.upper())
    cmd.append(context.url)
    cmd += _extract_httpie_request_items(context, quote=True)
    return ' '.join(cmd) + '\n'


def format_to_http_prompt(context, excluded_options=None):
    """Format a Context object to HTTP Prompt commands."""
    cmds = _extract_httpie_options(context, quote=True, join_key_value=True,
                                   excluded_keys=excluded_options)
    cmds.append('cd ' + smart_quote(context.url))
    cmds += _extract_httpie_request_items(context, quote=True)
    return '\n'.join(cmds) + '\n'


================================================
FILE: http_prompt/contextio.py
================================================
"""Serialization and deserialization of a Context object."""

import io
import os

from . import xdg
from .context.transform import format_to_http_prompt
from .execution import execute


# Don't save these HTTPie options to avoid collision with user config file
EXCLUDED_OPTIONS = ['--style']

# Filename the current environment context will be saved to
CONTEXT_FILENAME = 'context.hp'


def _get_context_filepath():
    dir_path = xdg.get_data_dir()
    return os.path.join(dir_path, CONTEXT_FILENAME)


def load_context(context, file_path=None):
    """Load a Context object in place from user data directory."""
    if not file_path:
        file_path = _get_context_filepath()
    if os.path.exists(file_path):
        with open(file_path, encoding='utf-8') as f:
            for line in f:
                execute(line, context)


def save_context(context):
    """Save a Context object to user data directory."""
    file_path = _get_context_filepath()
    content = format_to_http_prompt(context, excluded_options=EXCLUDED_OPTIONS)
    with open(file_path, 'w', encoding='utf-8') as f:
        f.write(content)


================================================
FILE: http_prompt/defaultconfig.py
================================================
# Highlighting style for prompt commands. Available values:
# algol, algol_nu, autumn, borland, bw, colorful, default, emacs, friendly,
# fruity, igor, lovelace, manni, monokai, murphy, native, paraiso-dark,
# paraiso-light, pastie, perldoc, rrt, solarized, tango, trac, vim, vs, xcode.
# Preview themes at http://http-prompt.com/themes
command_style = 'solarized'

# Highlighting style for HTTPie's output. Available values are the same as
# command_style. Set this to None to use HTTPie's default style, which you
# can refer to https://httpie.org/doc#config-file-location
output_style = None

# The tool used to paginate output. Available values: 'less' and 'more'.
# Note that 'more' does not support ANSI colors.
pager = 'less'

# What to do when a response has a 'Set-Cookie' header? Available values:
# 'auto': set the cookie automatically and silently
# 'ask': ask the user if they want to set the cookie
# 'off': do nothing with the 'Set-Cookie' header
set_cookies = 'auto'

# Enable Vi editor mode? Available values: True / False.
# When Vi mode is enabled, you use Vi-like keybindings to edit your commands.
# When it is disabled, you use Emacs keybindings.
vi = False


================================================
FILE: http_prompt/execution.py
================================================
import io
import json
import re
import os
import sys
from urllib.parse import urlparse, urljoin

import click

from subprocess import CalledProcessError, Popen, PIPE

from httpie.context import Environment
from httpie.core import main as httpie_main
from parsimonious.exceptions import ParseError, VisitationError
from parsimonious.grammar import Grammar
from parsimonious.nodes import NodeVisitor
from parsimonious.nodes import Node
from pygments.token import String, Name

from .completion import ROOT_COMMANDS, ACTIONS, OPTION_NAMES, HEADER_NAMES
from .context import Context
from .context.transform import (
    extract_args_for_httpie_main,
    format_to_curl,
    format_to_httpie,
    format_to_http_prompt)
from .output import Printer, TextWriter
from .utils import unescape, unquote, colformat


HTTPIE_PROGRAM_NAME = 'http'


grammar = r"""
    command = mutation / immutation

    mutation = concat_mut+ / nonconcat_mut
    immutation = preview / action / ls / env / help / exit / exec / source / clear / _

    concat_mut = option_mut / full_quoted_mut / value_quoted_mut / unquoted_mut
    nonconcat_mut = cd / rm

    preview = _ tool _ (method _)? (urlpath _)? concat_mut* redir_out? _
    action = _ method _ (urlpath _)? concat_mut* redir_out? _
    urlpath = (~r"https?://" unquoted_string) /
              (!concat_mut !redir_out string)

    clear = _ "clear" _
    help = _ "help" _
    exit = _ "exit" _
    ls = _ "ls" _ (urlpath _)? (redir_out)?
    env  = _ "env" _ (redir_out)?
    source = _ "source" _ filepath _
    exec = _ "exec" _ filepath _

    redir_out = redir_append / redir_write / pipe
    redir_append = _ ">>" _ filepath _
    redir_write = _ ">" _ filepath _
    pipe = _ "|" _ (shell_subs / shell_code) _

    unquoted_mut = _ unquoted_mutkey mutop unquoted_mutval _
    full_quoted_mut = full_squoted_mut / full_dquoted_mut
    value_quoted_mut = value_squoted_mut / value_dquoted_mut
    full_squoted_mut = _ "'" squoted_mutkey mutop squoted_mutval "'" _
    full_dquoted_mut = _ '"' dquoted_mutkey mutop dquoted_mutval '"' _
    value_squoted_mut = _ unquoted_mutkey mutop "'" squoted_mutval "'" _
    value_dquoted_mut = _ unquoted_mutkey mutop '"' dquoted_mutval '"' _
    mutop = ":=" / ":" / "==" / "="
    unquoted_mutkey = unquoted_mutkey_item+
    unquoted_mutval = unquoted_stringitem*
    unquoted_mutkey_item = shell_subs / unquoted_mutkey_char / escapeseq
    unquoted_mutkey_char = ~r"[^\s'\"\\=:>]"
    squoted_mutkey = squoted_mutkey_item+
    squoted_mutval = squoted_stringitem*
    squoted_mutkey_item = shell_subs / squoted_mutkey_char / escapeseq
    squoted_mutkey_char = ~r"[^\r\n'\\=:]"
    dquoted_mutkey = dquoted_mutkey_item+
    dquoted_mutval = dquoted_stringitem*
    dquoted_mutkey_item = shell_subs / dquoted_mutkey_char / escapeseq
    dquoted_mutkey_char = ~r'[^\r\n"\\=:]'

    option_mut = flag_option_mut / value_option_mut
    flag_option_mut = _ flag_optname _
    flag_optname = "--json" / "-j" / "--form" / "-f" / "--verbose" / "-v" /
                   "--headers" / "-h" / "--body" / "-b" / "--stream" / "-S" /
                   "--download" / "-d" / "--continue" / "-c" / "--follow" /
                   "--check-status" / "--ignore-stdin" / "--help" /
                   "--version" / "--traceback" / "--debug"
    value_option_mut = _ value_optname ~r"(\s+|=)" string _
    value_optname = "--pretty" / "--style" / "-s" / "--print" / "-p" /
                    "--output" / "-o" / "--session-read-only" / "--session" /
                    "--auth-type" / "--auth" / "-a" / "--proxy" / "--verify" /
                    "--cert" / "--cert-key" / "--timeout" / "--raw"

    cd = _ "cd" _ string? _
    rm = (_ "rm" _ "*" _) / (_ "rm" _ ~r"\-(h|q|b|o)" _ mutkey _)
    tool = "httpie" / "curl"
    method = ~r"get"i / ~r"head"i / ~r"post"i / ~r"put"i / ~r"delete"i /
             ~r"patch"i / ~r"options"i / ~r"connect"i
    mutkey = unquoted_mutkey / ("'" squoted_mutkey "'") /
             ('"' dquoted_mutkey '"') / flag_optname / value_optname

    string = quoted_string / unquoted_string
    quoted_string = ('"' dquoted_stringitem* '"') /
                    ("'" squoted_stringitem* "'")
    unquoted_string = unquoted_stringitem+
    dquoted_stringitem = shell_subs / dquoted_stringchar / escapeseq
    squoted_stringitem = shell_subs / squoted_stringchar / escapeseq
    unquoted_stringitem = shell_subs / unquoted_stringchar / escapeseq
    dquoted_stringchar = ~r'[^\r\n"\\]'
    squoted_stringchar = ~r"[^\r\n'\\]"
    unquoted_stringchar = ~r"[^\s'\\]"
    escapeseq = ~r"\\."
    _ = ~r"\s*"

    shell_subs = "`" shell_code "`"
    shell_code = ~r"[^`]*"
"""

if sys.platform == 'win32':
    # XXX: Windows use backslashes as separators in its filesystem path, so we
    # have to avoid using backslashes to escape chars here.
    grammar += r"""
        filepath = quoted_filepath / unquoted_filepath
        quoted_filepath = ('"' dquoted_filepath_char+ '"') /
                          ("'" squoted_filepath_char+ "'")
        dquoted_filepath_char = ~r'[^\r\n"]'
        squoted_filepath_char = ~r"[^\r\n']"
        unquoted_filepath = unquoted_filepath_char+
        unquoted_filepath_char = ~r"[^\s\"]"
    """
else:
    grammar += r"""
        filepath = string
    """

grammar = Grammar(grammar)


if Environment.colors == 256:
    from pygments.formatters.terminal256 import (
        Terminal256Formatter as TerminalFormatter)
else:
    from pygments.formatters.terminal import TerminalFormatter


def urljoin2(base, path, **kwargs):
    if not base.endswith('/'):
        base += '/'
    url = urljoin(base, path, **kwargs)
    if url.endswith('/') and not path.endswith('/'):
        url = url[:-1]
    return url


def generate_help_text():
    """Return a formatted string listing commands, HTTPie options, and HTTP
    actions.
    """
    def generate_cmds_with_explanations(summary, cmds):
        text = '{0}:\n'.format(summary)
        for cmd, explanation in cmds:
            text += '\t{0:<10}\t{1:<20}\n'.format(cmd, explanation)
        return text + '\n'

    text = generate_cmds_with_explanations('Commands', ROOT_COMMANDS.items())
    text += generate_cmds_with_explanations('Options', OPTION_NAMES.items())
    text += generate_cmds_with_explanations('Actions', ACTIONS.items())
    text += generate_cmds_with_explanations('Headers', HEADER_NAMES.items())
    return text


if sys.platform == 'win32':  # nocover
    def normalize_filepath(path):
        return unquote(path)
else:
    def normalize_filepath(path):
        return unescape(unquote(path))


class DummyExecutionListener(object):

    def context_changed(self, context):
        pass

    def response_returned(self, context, response):
        pass


class ExecutionVisitor(NodeVisitor):

    unwrapped_exceptions = (CalledProcessError,)

    def __init__(self, context, listener=None, style=None):
        super(ExecutionVisitor, self).__init__()
        self.context = context

        self.context_override = Context(context.url)
        self.method = None
        self.tool = None
        self._output = Printer()

        # If there's a pipe, as in "httpie post | sed s/POST/GET/", this
        # variable points to the "sed" Popen object. The variable is necessary
        # because the we need to redirect Popen.stdout to Printer, which does
        # output pagination.
        self.pipe_proc = None

        self.listener = listener or DummyExecutionListener()

        # Last response object returned by HTTPie
        self.last_response = None

        # Pygments formatter, used to render output with colors in some cases
        if style:
            self.formatter = TerminalFormatter(style=style)
        else:
            self.formatter = None

    @property
    def output(self):
        return self._output

    @output.setter
    def output(self, new_output):
        if self._output:
            self._output.close()
        self._output = new_output

    def visit_method(self, node, children):
        self.method = node.text
        return node

    def visit_urlpath(self, node, children):
        path = node.text
        self.context_override.url = urljoin2(self.context_override.url, path)
        return node

    def visit_cd(self, node, children):
        _, _, _, path, _ = children

        if isinstance(path, Node):
            seg = urlparse(self.context_override.url)
            self.context_override.url = seg.scheme + '://' + seg.netloc
        else:
            self.context_override.url = urljoin2(
                self.context_override.url, path)

        return node

    def visit_rm(self, node, children):
        children = children[0]
        kind = children[3].text

        if kind == '*':
            # Clear context
            for target in [self.context.headers,
                           self.context.querystring_params,
                           self.context.body_params,
                           self.context.body_json_params,
                           self.context.options]:
                target.clear()
            return node

        name = children[5]
        if kind == '-h':
            target = self.context.headers
        elif kind == '-q':
            target = self.context.querystring_params
        elif kind == '-o':
            target = self.context.options
        else:
            assert kind == '-b'
            # TODO: This is kind of ugly, will fix it
            if name == '*':
                self.context.body_params.clear()
                self.context.body_json_params.clear()
            else:
                try:
                    del self.context.body_params[name]
                except KeyError:
                    del self.context.body_json_params[name]
            return node

        if name == '*':
            target.clear()
        else:
            del target[name]

        return node

    def visit_help(self, node, children):
        self.output.write(generate_help_text())
        return node

    def _redirect_output(self, filepath, mode):
        filepath = normalize_filepath(filepath)
        self.output = TextWriter(open(os.path.expandvars(filepath), mode))

    def visit_redir_append(self, node, children):
        self._redirect_output(children[3], 'ab')
        return node

    def visit_redir_write(self, node, children):
        self._redirect_output(children[3], 'wb')
        return node

    def visit_pipe(self, node, children):
        cmd = children[3]
        self.pipe_proc = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE)
        self.output = TextWriter(self.pipe_proc.stdin)
        return node

    def visit_exec(self, node, children):
        path = normalize_filepath(children[3])
        with open(path, encoding='utf-8') as f:
            # Wipe out context first
            execute('rm *', self.context, self.listener)
            for line in f:
                execute(line, self.context, self.listener)
        return node

    def visit_source(self, node, children):
        path = normalize_filepath(children[3])
        with open(path, encoding='utf-8') as f:
            for line in f:
                execute(line, self.context, self.listener)
        return node

    def _colorize(self, text, token_type):
        if not self.formatter:
            return text

        out = io.StringIO()
        self.formatter.format([(token_type, text)], out)
        return out.getvalue()

    def visit_ls(self, node, children):
        path = urlparse(self.context_override.url).path
        path = filter(None, path.split('/'))
        nodes = self.context.root.ls(*path)
        if self.output.isatty():
            names = []
            for node in nodes:
                token_type = String if node.data.get('type') == 'dir' else Name
                name = self._colorize(node.name, token_type)
                names.append(name)
            lines = list(colformat(list(names)))
        else:
            lines = [n.name for n in nodes]
        if lines:
            self.output.write('\n'.join(lines))
        return node

    def visit_env(self, node, children):
        text = format_to_http_prompt(self.context)
        self.output.write(text)
        return node

    def visit_exit(self, node, children):
        self.context.should_exit = True
        return node

    def visit_clear(self, node, children):
        self.output.clear()
        return node

    def visit_mutkey(self, node, children):
        if isinstance(children[0], list):
            return children[0][1]
        return children[0]

    def _mutate(self, node, key, op, val):
        if op == ':=':
            self.context_override.body_json_params[key] = json.loads(val)
        elif op == ':':
            self.context_override.headers[key] = val
        elif op == '=':
            self.context_override.body_params[key] = val
        elif op == '==':
            # You can have multiple querystring params with the same name,
            # so we use a list to store multiple values (#20)
            params = self.context_override.querystring_params
            if key not in params:
                params[key] = [val]
            else:
                params[key].append(val)
        return node

    def visit_unquoted_mut(self, node, children):
        _, key, op, val, _ = children
        return self._mutate(node, key, op, val)

    def visit_full_squoted_mut(self, node, children):
        _, _, key, op, val, _, _ = children
        return self._mutate(node, key, op, val)

    def visit_full_dquoted_mut(self, node, children):
        _, _, key, op, val, _, _ = children
        return self._mutate(node, key, op, val)

    def visit_value_squoted_mut(self, node, children):
        _, key, op, _, val, _, _ = children
        return self._mutate(node, key, op, val)

    def visit_value_dquoted_mut(self, node, children):
        _, key, op, _, val, _, _ = children
        return self._mutate(node, key, op, val)

    def _visit_mut_key_or_val(self, node, children):
        return unescape(''.join(children), exclude=':=')

    visit_unquoted_mutkey = _visit_mut_key_or_val
    visit_unquoted_mutval = _visit_mut_key_or_val
    visit_squoted_mutkey = _visit_mut_key_or_val
    visit_squoted_mutval = _visit_mut_key_or_val
    visit_dquoted_mutkey = _visit_mut_key_or_val
    visit_dquoted_mutval = _visit_mut_key_or_val

    def visit_mutop(self, node, children):
        return node.text

    def visit_flag_option_mut(self, node, children):
        _, key, _ = children
        self.context_override.options[key] = None
        return node

    def visit_flag_optname(self, node, children):
        return node.text

    def visit_value_option_mut(self, node, children):
        _, key, _, val, _ = children
        self.context_override.options[key] = val
        return node

    def visit_value_optname(self, node, children):
        return node.text

    def visit_filepath(self, node, children):
        return children[0]

    def visit_string(self, node, children):
        return children[0]

    def visit_quoted_filepath(self, node, children):
        return node.text[1:-1]

    def visit_unquoted_filepath(self, node, children):
        return node.text

    def visit_unquoted_string(self, node, children):
        return unescape(''.join(children))

    def visit_quoted_string(self, node, children):
        return self._visit_mut_key_or_val(node, children[0][1])

    def _visit_stringitem(self, node, children):
        child = children[0]
        if hasattr(child, 'text'):
            return child.text
        return child

    visit_unquoted_mutkey_item = _visit_stringitem
    visit_unquoted_stringitem = _visit_stringitem
    visit_squoted_mutkey_item = _visit_stringitem
    visit_squoted_stringitem = _visit_stringitem
    visit_dquoted_mutkey_item = _visit_stringitem
    visit_dquoted_stringitem = _visit_stringitem

    def visit_tool(self, node, children):
        self.tool = node.text
        return node

    def visit_mutation(self, node, children):
        self.context.update(self.context_override)
        self.listener.context_changed(self.context)
        return node

    def _final_context(self):
        context = self.context.copy()
        context.update(self.context_override)
        return context

    def _trace_get_response(self, frame, event, arg):
        func_name = frame.f_code.co_name
        if func_name == 'get_response':
            if event == 'call':
                return self._trace_get_response
            elif event == 'return':
                self.last_response = arg

    def _call_httpie_main(self):
        context = self._final_context()
        args = extract_args_for_httpie_main(context, self.method)
        env = Environment(stdout=self.output, stdin=sys.stdin,
                          is_windows=False)
        env.stdout_isatty = self.output.isatty()
        env.stdin_isatty = sys.stdin.isatty()

        # XXX: httpie_main() doesn't provide an API for us to get the
        # HTTP response object, so we use this super dirty hack -
        # sys.settrace() to intercept get_response() that is called in
        # httpie_main() internally. The HTTP response intercepted is
        # assigned to self.last_response, which self.listener may be
        # interested in.
        sys.settrace(self._trace_get_response)
        try:
            httpie_main([HTTPIE_PROGRAM_NAME, *args], env=env)
        finally:
            sys.settrace(None)

    def visit_immutation(self, node, children):
        self.output.close()
        if self.pipe_proc:
            Printer().write(self.pipe_proc.stdout.read())
        return node

    def visit_preview(self, node, children):
        context = self._final_context()
        if self.tool == 'httpie':
            command = format_to_httpie(context, self.method)
        else:
            assert self.tool == 'curl'
            command = format_to_curl(context, self.method)
        self.output.write(command)
        return node

    def visit_action(self, node, children):
        self._call_httpie_main()
        if self.last_response:
            self.listener.response_returned(self.context, self.last_response)
        return node

    def visit_shell_subs(self, node, children):
        cmd = children[1]
        p = Popen(cmd, shell=True, stdout=PIPE)
        return p.stdout.read().decode().rstrip()

    def visit_shell_code(self, node, children):
        return node.text

    def generic_visit(self, node, children):
        if not node.expr_name and node.children:
            if len(children) == 1:
                return children[0]
            return children
        return node


def execute(command, context, listener=None, style=None):
    try:
        root = grammar.parse(command)
    except ParseError as err:
        # TODO: Better error message
        part = command[err.pos:err.pos + 10]
        click.secho('Syntax error near "%s"' % part, err=True, fg='red')
    else:
        visitor = ExecutionVisitor(context, listener=listener, style=style)
        try:
            visitor.visit(root)
        except VisitationError as err:
            exc_class = err.original_class
            if exc_class is KeyError:
                # XXX: Need to parse VisitationError error message to get the
                # original error message as VisitationError doesn't hold the
                # original exception object
                key = re.search(r"KeyError: u?'(.*)'", str(err)).group(1)
                click.secho("Key '%s' not found" % key, err=True,
                            fg='red')
            elif issubclass(exc_class, OSError):
                msg = str(err).splitlines()[0]

                # Remove the exception class name at the beginning
                msg = msg[msg.find(':') + 2:]
                click.secho(msg, err=True, fg='red')
            else:
                # TODO: Better error message
                click.secho(str(err), err=True, fg='red')
        except CalledProcessError as err:
            click.secho(err.output + ' (exit status %d)' % err.returncode,
                        fg='red')


================================================
FILE: http_prompt/lexer.py
================================================
from pygments.lexer import (RegexLexer, bygroups, words, using, include,
                            combined)
from pygments.lexers import BashLexer

from pygments.token import Text, String, Keyword, Name, Operator

from . import options as opt


__all__ = ['HttpPromptLexer']


FLAG_OPTIONS = [name for name, _ in opt.FLAG_OPTIONS]
VALUE_OPTIONS = [name for name, _ in opt.VALUE_OPTIONS]
HTTP_METHODS = ('get', 'head', 'post', 'put', 'patch',
                'delete', 'options', 'connect')


def string_rules(state):
    return [
        (r'(")((?:[^\r\n"\\]|(?:\\.))+)(")',
         bygroups(Text, String, Text), state),

        (r'(")((?:[^\r\n"\\]|(?:\\.))+)', bygroups(Text, String), state),

        (r"(')((?:[^\r\n'\\]|(?:\\.))+)(')",
         bygroups(Text, String, Text), state),

        (r"(')((?:[^\r\n'\\]|(?:\\.))+)", bygroups(Text, String), state),

        (r'([^\s\'\\]|(\\.))+', String, state)
    ]


class HttpPromptLexer(RegexLexer):

    name = 'HttpPrompt'
    aliases = ['http-prompt']
    filenames = ['*.http-prompt']

    tokens = {
        'root': [
            (r'\s+', Text),
            (r'(cd)(\s*)', bygroups(Keyword, Text), 'cd'),
            (r'(rm)(\s*)', bygroups(Keyword, Text), 'rm_option'),
            (r'(httpie|curl)(\s*)', bygroups(Keyword, Text), 'action'),

            (words(HTTP_METHODS, prefix='(?i)', suffix=r'(?!\S)(\s*)'),
             bygroups(Keyword, Text), combined('redir_out', 'urlpath')),

            (r'(clear)(\s*)', bygroups(Keyword, Text), 'end'),
            (r'(exit)(\s*)', bygroups(Keyword, Text), 'end'),
            (r'(help)(\s)*', bygroups(Keyword, Text), 'end'),
            (r'(env)(\s*)', bygroups(Keyword, Text),
             combined('redir_out', 'pipe')),
            (r'(source)(\s*)', bygroups(Keyword, Text), 'file_path'),
            (r'(exec)(\s*)', bygroups(Keyword, Text), 'file_path'),
            (r'(ls)(\s*)', bygroups(Keyword, Text),
             combined('redir_out', 'urlpath')),
            (r'', Text, 'concat_mut')
        ],

        'cd': string_rules('end'),

        'rm_option': [
            (r'(\-(?:h|o|b|q))(\s*)', bygroups(Name, Text), 'rm_name'),
            (r'(\*)(\s*)', bygroups(Name, Text), 'end')
        ],
        'rm_name': string_rules('end'),

        'shell_command': [
            (r'(`)([^`]*)(`)', bygroups(Text, using(BashLexer), Text)),
        ],
        'pipe': [
            (r'(\s*)(\|)(.*)', bygroups(Text, Operator, using(BashLexer))),
        ],
        'concat_mut': [
            (r'$', Text, 'end'),
            (r'\s+', Text),

            # Flag options, such as (--form) and (--json)
            (words(FLAG_OPTIONS, suffix=r'\b'), Name, 'concat_mut'),

            # Options with values, such as (--style=default) and (--pretty all)
            (words(VALUE_OPTIONS, suffix=r'\b'), Name,
             combined('shell_command', 'option_op')),

            include('shell_command'),

            # Unquoted or value-quoted request mutation,
            # such as (name="John Doe") and (name=John\ Doe)
            (r'((?:[^\s\'"\\=:]|(?:\\.))*)(:=|:|==|=)',
             bygroups(Name, Operator),
             combined('shell_command', 'unquoted_mut')),

            # Full single-quoted request mutation, such as ('name=John Doe')
            (r"(')((?:[^\r\n'\\=:]|(?:\\.))+)(:=|:|==|=)",
             bygroups(Text, Name, Operator),
             combined('shell_command', 'squoted_mut')),

            # Full double-quoted request mutation, such as ("name=John Doe")
            (r'(")((?:[^\r\n"\\=:]|(?:\\.))+)(:=|:|==|=)',
             bygroups(Text, Name, Operator),
             combined('shell_command', 'dquoted_mut'))
        ],

        'option_op': [
            (r'(\s+|=)', Operator, 'option_value'),
        ],
        'option_value': string_rules('#pop:2'),
        'file_path': string_rules('end'),
        'redir_out': [
            (r'(?i)(>>?)(\s*)', bygroups(Operator, Text), 'file_path')
        ],

        'unquoted_mut': string_rules('#pop'),
        'squoted_mut': [
            (r"((?:[^\r\n'\\]|(?:\\.))+)(')", bygroups(String, Text), '#pop'),
            (r"([^\r\n'\\]|(\\.))+", String, '#pop')
        ],
        'dquoted_mut': [
            (r'((?:[^\r\n"\\]|(?:\\.))+)(")', bygroups(String, Text), '#pop'),
            (r'([^\r\n"\\]|(\\.))+', String, '#pop')
        ],

        'action': [
            (words(HTTP_METHODS, prefix='(?i)', suffix=r'(\s*)'),
             bygroups(Keyword, Text),
             combined('redir_out', 'pipe', 'urlpath')),
            (r'', Text, combined('redir_out', 'pipe', 'urlpath'))
        ],
        'urlpath': [
            (r'https?://([^\s"\'\\]|(\\.))+', String,
             combined('concat_mut', 'redir_out', 'pipe')),

            (r'(")(https?://(?:[^\r\n"\\]|(?:\\.))+)(")',
             bygroups(Text, String, Text),
             combined('concat_mut', 'redir_out', 'pipe')),

            (r'(")(https?://(?:[^\r\n"\\]|(?:\\.))+)',
             bygroups(Text, String)),

            (r"(')(https?://(?:[^\r\n'\\]|(?:\\.))+)(')",
             bygroups(Text, String, Text),
             combined('concat_mut', 'redir_out', 'pipe')),

            (r"(')(https?://(?:[^\r\n'\\]|(?:\\.))+)",
             bygroups(Text, String)),

            (r'(")((?:[^\r\n"\\=:]|(?:\\.))+)(")',
             bygroups(Text, String, Text),
             combined('concat_mut', 'redir_out', 'pipe')),

            (r'(")((?:[^\r\n"\\=:]|(?:\\.))+)', bygroups(Text, String)),

            (r"(')((?:[^\r\n'\\=:]|(?:\\.))+)(')",
             bygroups(Text, String, Text),
             combined('concat_mut', 'redir_out', 'pipe')),

            (r"(')((?:[^\r\n'\\=:]|(?:\\.))+)", bygroups(Text, String)),

            (r'([^\-](?:[^\s"\'\\=:]|(?:\\.))+)(\s+|$)',
             bygroups(String, Text),
             combined('concat_mut', 'redir_out', 'pipe')),

            (r'', Text,
             combined('concat_mut', 'redir_out', 'pipe'))
        ],

        'end': [
            (r'\n', Text, 'root')
        ]
    }


================================================
FILE: http_prompt/options.py
================================================
"""Meta data for HTTPie options."""

FLAG_OPTIONS = [
    ('--body', 'Print only response body'),
    ('--check-status', 'Check HTTP status code'),
    ('--continue', 'Resume an interrupted download'),
    ('--debug', 'Print debug information'),
    ('--download', 'Download as a file'),
    ('--follow', 'Allow full redirects'),
    ('--form', 'Send as form fields'),
    ('--headers', 'Print only response headers'),
    ('--help', 'Show tool (HTTPie, cURL) help message'),
    ('--ignore-stdin', 'Do not read stdin'),
    ('--json', 'Send as a JSON object (default)'),
    ('--stream', 'Stream the output'),
    ('--traceback', 'Print exception traceback'),
    ('--verbose', 'Print the whole request and response'),
    ('--version', 'Show version'),
    ('-b', 'Shorthand for --body'),
    ('-c', 'Shorthand for --continue'),
    ('-d', 'Shorthand for --download'),
    ('-f', 'Shorthand for --form'),
    ('-h', 'Shorthand for --headers'),
    ('-j', 'Shorthand for --json'),
    ('-S', 'Shorthand for --stream'),
    ('-v', 'Shorthand for --verbose'),
]

VALUE_OPTIONS = [
    ('--auth', 'Do authentication'),
    ('--auth-type', 'Authentication mechanism to be used'),
    ('--cert', 'Specify client SSL certificate'),
    ('--cert-key', 'The private key to use with SSL'),
    ('--output', 'Save output to a file'),
    ('--pretty', 'Control output processing'),
    ('--print', 'Specify what output should contain'),
    ('--proxy', 'Specify proxy URL'),
    ('--raw', 'Pass raw request data without extra processing'),
    ('--session', 'Create, or reuse and update a session'),
    ('--session-read-only', 'Create or read a session'),
    ('--style', 'Output coloring style'),
    ('--timeout', 'Connection timeout in seconds'),
    ('--verify', 'Set to "no" to skip SSL certificate checking'),
    ('-a', 'Shorthand for --auth'),
    ('-o', 'Shorthand for --output'),
    ('-p', 'Shorthand for --print'),
    ('-s', 'Shorthand for --style'),
]

PRETTY_CHOICES = ('all', 'colors', 'format', 'none')

STYLE_CHOICES = ('algol', 'algol_nu', 'autumn', 'borland', 'bw', 'colorful',
                 'default', 'emacs', 'friendly', 'fruity', 'igor', 'lovelace',
                 'manni', 'monokai', 'murphy', 'native', 'paraiso-dark',
                 'paraiso-light', 'pastie', 'perldoc', 'rrt', 'solarized',
                 'tango', 'trac', 'vim', 'vs', 'xcode')

AUTH_TYPE_CHOICES = ('basic', 'digest')

VERIFY_CHOICES = ('no', 'yes')

OPTION_VALUE_CHOICES = {
    '--auth-type': AUTH_TYPE_CHOICES,
    '--pretty': PRETTY_CHOICES,
    '--style': STYLE_CHOICES,
    '--verify': VERIFY_CHOICES,
    '-p': PRETTY_CHOICES,
    '-s': STYLE_CHOICES,
}


================================================
FILE: http_prompt/output.py
================================================
import sys

import click


class Printer(object):
    """Wrap click.echo_via_pager() so it accepts binary data."""

    def write(self, data):
        if isinstance(data, bytes):
            data = data.decode()

        # echo_via_pager() already appends a '\n' at the end of text,
        # so we use rstrip() to remove extra newlines (#89)
        click.echo_via_pager(data.rstrip())

    def flush(self):
        pass

    def close(self):
        pass

    def isatty(self):
        return True

    def fileno(self):
        return sys.stdout.fileno()

    def clear(self):
        click.clear()


class TextWriter(object):
    """Wrap a file-like object, opened with 'wb' or 'ab', so it accepts text
    data.
    """

    def __init__(self, fp):
        self.fp = fp

    def write(self, data):
        if isinstance(data, str):
            data = data.encode()
        self.fp.write(data)

    def flush(self):
        self.fp.flush()

    def close(self):
        self.fp.close()

    def isatty(self):
        return self.fp.isatty()

    def fileno(self):
        return self.fp.fileno()


================================================
FILE: http_prompt/tree.py
================================================
"""Tree data structure for ls command to work with OpenAPI specification."""


class Node(object):

    def __init__(self, name, data=None, parent=None):
        if name in ('.', '..'):
            raise ValueError("name cannot be '.' or '..'")

        self.name = name
        self.data = data or {}
        self.parent = parent
        self.children = set()

    def __str__(self):
        return self.name

    def __repr__(self):
        return "Node('{}', '{}')".format(self.name, self.data.get('type'))

    def __lt__(self, other):
        ta = self.data.get('type')
        tb = other.data.get('type')
        if ta != tb:
            return ta < tb
        return self.name < other.name

    def __eq__(self, other):
        return self.name == other.name and self.data == other.data

    def __hash__(self):
        return hash((self.name, self.data.get('type')))

    def add_path(self, *path, **kwargs):
        node_type = kwargs.get('node_type', 'dir')
        name = path[0]
        tail = path[1:]
        child = self.find_child(name, wildcard=False)
        if not child:
            data = {'type': 'dir' if tail else node_type}
            child = Node(name, data=data, parent=self)
            self.children.add(child)

        if tail:
            child.add_path(*tail, node_type=node_type)

    def find_child(self, name, wildcard=True):
        for child in self.children:
            if child.name == name:
                return child

        # Attempt to match wildcard like /users/{user_id}
        if wildcard:
            for child in self.children:
                if child.name.startswith('{') and child.name.endswith('}'):
                    return child

        return None

    def ls(self, *path):
        success = True
        cur = self
        for name in path:
            if not name or name == '.':
                continue
            elif name == '..':
                if cur.parent:
                    cur = cur.parent
            else:
                child = cur.find_child(name)
                if child:
                    cur = child
                else:
                    success = False
                    break
        if success:
            for node in sorted(cur.children):
                yield node


================================================
FILE: http_prompt/utils.py
================================================
import math
import re
import shlex

from prompt_toolkit.output.defaults import create_output


RE_ANSI_ESCAPE = re.compile(r'\x1b[^m]*m')


def smart_quote(s):
    return shlex.quote(s)


def unquote(s):
    quotes = ["'", '"']
    quote_str = None
    if s[0] in quotes:
        quote_str = s[0]

    if quote_str and s[-1] == quote_str:
        return s[1: -1]
    return s


def unescape(s, exclude=None):
    if exclude:
        char = '[^%s]' % exclude
    else:
        char = '.'
    return re.sub(r'\\(%s)' % char, r'\1', s)


def get_terminal_size():
    return create_output().get_size()


def strip_ansi_escapes(text):
    return RE_ANSI_ESCAPE.sub('', text)


def colformat(strings, num_sep_spaces=1, terminal_width=None):
    """Format a list of strings like ls does multi-column output."""
    if terminal_width is None:
        terminal_width = get_terminal_size().columns

    if not strings:
        return

    num_items = len(strings)
    max_len = max([len(strip_ansi_escapes(s)) for s in strings])

    num_columns = min(
        int((terminal_width + num_sep_spaces) / (max_len + num_sep_spaces)),
        num_items)
    num_columns = max(1, num_columns)

    num_lines = int(math.ceil(float(num_items) / num_columns))
    num_columns = int(math.ceil(float(num_items) / num_lines))

    num_elements_last_column = num_items % num_lines
    if num_elements_last_column == 0:
        num_elements_last_column = num_lines

    lines = []
    for i in range(num_lines):
        line_size = num_columns
        if i >= num_elements_last_column:
            line_size -= 1
        lines.append([None] * line_size)

    for i, line in enumerate(lines):
        line_size = len(line)
        for j in range(line_size):
            k = i + num_lines * j
            item = strings[k]
            if j % line_size != line_size - 1:
                item_len = len(strip_ansi_escapes(item))
                item = item + ' ' * (max_len - item_len)
            line[j] = item

    sep = ' ' * num_sep_spaces
    for line in lines:
        yield sep.join(line)


================================================
FILE: http_prompt/xdg.py
================================================
"""XDG Base Directory Specification.

See:
    https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
    https://github.com/ActiveState/appdirs
"""
import os
import sys

from functools import partial


def _get_dir(envvar_name, default_dir, resource_name=None):
    base_dir = os.getenv(envvar_name) or default_dir
    app_dir = os.path.join(base_dir, 'http-prompt')
    if not os.path.exists(app_dir):
        os.makedirs(app_dir, mode=0o700)

    if resource_name:
        app_dir = os.path.join(app_dir, resource_name)
        if not os.path.exists(app_dir):
            os.mkdir(app_dir)

    return app_dir


if sys.platform == 'win32':  # nocover
    # NOTE: LOCALAPPDATA is not available on Windows XP
    get_data_dir = partial(_get_dir, 'LOCALAPPDATA',
                           os.path.expanduser('~/AppData/Local'))
    get_config_dir = partial(_get_dir, 'LOCALAPPDATA',
                             os.path.expanduser('~/AppData/Local'))
else:
    get_data_dir = partial(_get_dir, 'XDG_DATA_HOME',
                           os.path.expanduser('~/.local/share'))
    get_config_dir = partial(_get_dir, 'XDG_CONFIG_HOME',
                             os.path.expanduser('~/.config'))


================================================
FILE: requirements-test.txt
================================================
pexpect>=4.2.1
pytest>=3.0.6
pytest-cov>=2.4.0
wheel
twine

================================================
FILE: requirements.txt
================================================
click>=5.0
httpie>=2.5.0
parsimonious>=0.6.2
prompt-toolkit>=2.0.0,<3.0.0
Pygments>=2.1.0
PyYAML>=3.0


================================================
FILE: setup.cfg
================================================
[wheel]
universal = 1


================================================
FILE: setup.py
================================================
import os
import re
from setuptools import setup


here = os.path.abspath(os.path.dirname(__file__))


# Read the version number from a source file.
# Why read it, and not import?
# see https://groups.google.com/d/topic/pypa-dev/0PkjVpcxTzQ/discussion
def find_version(*file_paths):
    # Open in Latin-1 so that we avoid encoding errors.
    with open(os.path.join(here, *file_paths), encoding='latin1') as f:
        version_file = f.read()

    # The version line must have the form
    # __version__ = 'ver'
    version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
                              version_file, re.M)
    if version_match:
        return version_match.group(1)
    raise RuntimeError('Unable to find version string')


def read_description(filename):
    with open(filename, encoding='utf-8') as f:
        return f.read()


def read_requirements(filename):
    try:
        with open(filename) as f:
            return [line.rstrip() for line in f]
    except OSError:
        raise OSError(os.getcwd())


setup(
    name='http-prompt',
    version=find_version('http_prompt', '__init__.py'),
    url='https://github.com/httpie/http-prompt',
    description='An interactive HTTP command-line client',
    long_description=read_description('README.rst'),
    author='Chang-Hung Liang',
    author_email='eliang.cs@gmail.com',
    license='MIT',
    packages=['http_prompt', 'http_prompt.context'],
    entry_points="""
        [console_scripts]
        http-prompt=http_prompt.cli:cli
    """,
    install_requires=read_requirements('requirements.txt'),
    classifiers=[
        'Development Status :: 3 - Alpha',
        'Environment :: Console',
        'Intended Audience :: Developers',
        'Intended Audience :: System Administrators',
        'License :: OSI Approved :: MIT License',
        'Topic :: Internet :: WWW/HTTP',
        'Topic :: Software Development',
        'Topic :: System :: Networking',
        'Topic :: Terminals',
        'Topic :: Text Processing',
        'Topic :: Utilities',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.9',
        'Programming Language :: Python :: 3.10',
    ]
)


================================================
FILE: snap/snapcraft.yaml
================================================
name: http-prompt
summary: Interactive command-line HTTP client
description: |
    HTTP Prompt is an interactive command-line HTTP client featuring autocomplete
    and syntax highlighting, built on HTTPie and prompt_toolkit.
    Home: http://http-prompt.com
adopt-info: http-prompt
confinement: strict

apps:
    http-prompt:
        command: bin/http-prompt
        plugs: [network]
parts:
    http-prompt:
        source: .
        plugin: python
        override-pull: |
            snapcraftctl pull
            version="$(git describe --always | sed -e 's/-/+git/;y/-/./')"
            case $version in
                v*) version=$(echo $version | tail -c +2) ;;
                *)  version=$(echo $version | head -c 32) ;;
            esac
            [ -n "$(echo $version | grep "+git")" ] && grade=devel || grade=stable
            snapcraftctl set-version "$version"
            snapcraftctl set-grade "$grade"


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/base.py
================================================
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


================================================
FILE: tests/context/test_context.py
================================================
from http_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


================================================
FILE: tests/context/test_transform.py
================================================
from http_prompt.context import Context
from http_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']


================================================
FILE: tests/test_cli.py
================================================
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 http_prompt import xdg
from http_prompt.context import Context
from http_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('http_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('http_prompt.cli.prompt')
    @patch('http_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('http_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('http_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')


================================================
FILE: tests/test_completer.py
================================================
# -*- coding: utf-8 -*-
import unittest

from prompt_toolkit.document import Document

from http_prompt.completer import HttpPromptCompleter
from http_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'])


================================================
FILE: tests/test_config.py
================================================
import hashlib
import os

from .base import TempAppDirTestCase
from http_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!')


================================================
FILE: tests/test_contextio.py
================================================
# -*- coding: utf-8 -*-
from .base import TempAppDirTestCase
from http_prompt.context import Context
from http_prompt.contextio import save_context, load_context


class TestContextIO(TempAppDirTestCase):

    def test_save_and_load_context_non_ascii(self):
        c = Context('http://localhost')
        c.headers.update({
            'User-Agent': 'Ö',
            'Authorization': '中文'
        })
        save_context(c)

        c = Context('http://0.0.0.0')
        load_context(c)

        self.assertEqual(c.url, 'http://localhost')
        self.assertEqual(c.headers, {
            'User-Agent': 'Ö',
            'Authorization': '中文'
        })


================================================
FILE: tests/test_execution.py
================================================
# -*- coding: utf-8 -*-
import hashlib
import io
import json
import shutil
import os
import sys

import pytest

from collections import namedtuple

from unittest.mock import patch

from http_prompt.context import Context
from http_prompt.execution import execute, HTTPIE_PROGRAM_NAME

from .base import TempAppDirTestCase


class ExecutionTestCase(TempAppDirTestCase):

    def setUp(self):
        super(ExecutionTestCase, self).setUp()
        self.patchers = [
            ('httpie_main', patch('http_prompt.execution.httpie_main')),
            ('echo_via_pager',
             patch('http_prompt.output.click.echo_via_pager')),
            ('secho', patch('http_prompt.execution.click.secho')),
            ('get_terminal_size', patch('http_prompt.utils.get_terminal_size'))
        ]
        for attr_name, patcher in self.patchers:
            setattr(self, attr_name, patcher.start())

        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': {}
            }
        })

        # pytest mocks to capture stdout so we can't really get_terminal_size()
        Size = namedtuple('Size', ['columns', 'rows'])
        self.get_terminal_size.return_value = Size(80, 30)

    def tearDown(self):
        super(ExecutionTestCase, self).tearDown()
        for _, patcher in self.patchers:
            patcher.stop()

    def assert_httpie_main_called_with(self, args):
        self.assertEqual(self.httpie_main.call_args[0][0], [
                         HTTPIE_PROGRAM_NAME, *args])

    def assert_stdout(self, expected_msg):
        # Append '\n' to simulate behavior of click.echo_via_pager(),
        # which we use whenever we want to output anything to stdout
        printed_msg = self.echo_via_pager.call_args[0][0] + '\n'
        self.assertEqual(printed_msg, expected_msg)

    def assert_stdout_startswith(self, expected_prefix):
        printed_msg = self.echo_via_pager.call_args[0][0]
        self.assertTrue(printed_msg.startswith(expected_prefix))

    def get_stdout(self):
        return self.echo_via_pager.call_args[0][0]

    def assert_stderr(self, expected_msg):
        printed_msg = self.secho.call_args[0][0]
        print_options = self.secho.call_args[1]
        self.assertEqual(printed_msg, expected_msg)
        self.assertEqual(print_options, {'err': True, 'fg': 'red'})


class TestExecution_noop(ExecutionTestCase):

    def test_empty_string(self):
        execute('', self.context)
        self.assertEqual(self.context.url, 'http://localhost')
        self.assertFalse(self.context.options)
        self.assertFalse(self.context.headers)
        self.assertFalse(self.context.querystring_params)
        self.assertFalse(self.context.body_params)
        self.assertFalse(self.context.should_exit)

    def test_spaces(self):
        execute('  \t \t  ', self.context)
        self.assertEqual(self.context.url, 'http://localhost')
        self.assertFalse(self.context.options)
        self.assertFalse(self.context.headers)
        self.assertFalse(self.context.querystring_params)
        self.assertFalse(self.context.body_params)
        self.assertFalse(self.context.should_exit)


class TestExecution_env(ExecutionTestCase):

    def setUp(self):
        super(TestExecution_env, self).setUp()

        self.context.url = 'http://localhost:8000/api'
        self.context.headers.update({
            'Accept': 'text/csv',
            'Authorization': 'ApiKey 1234'
        })
        self.context.querystring_params.update({
            'page': ['1'],
            'limit': ['50']
        })
        self.context.body_params.update({
            'name': 'John Doe'
        })
        self.context.options.update({
            '--verify': 'no',
            '--form': None
        })

    def test_env(self):
        execute('env', self.context)
        self.assert_stdout("--form\n--verify=no\n"
                           "cd http://localhost:8000/api\n"
                           "limit==50\npage==1\n"
                           "'name=John Doe'\n"
                           "Accept:text/csv\n"
                           "'Authorization:ApiKey 1234'\n")

    def test_env_with_spaces(self):
        execute('  env   ', self.context)
        self.assert_stdout("--form\n--verify=no\n"
                           "cd http://localhost:8000/api\n"
                           "limit==50\npage==1\n"
                           "'name=John Doe'\n"
                           "Accept:text/csv\n"
                           "'Authorization:ApiKey 1234'\n")

    def test_env_non_ascii(self):
        self.context.body_params['name'] = '許 功蓋'
        execute('env', self.context)
        self.assert_stdout("--form\n--verify=no\n"
                           "cd http://localhost:8000/api\n"
                           "limit==50\npage==1\n"
                           "'name=許 功蓋'\n"
                           "Accept:text/csv\n"
                           "'Authorization:ApiKey 1234'\n")

    def test_env_write_to_file(self):
        filename = self.make_tempfile()

        # write something first to make sure it's a full overwrite
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute('env > %s' % filename, self.context)

        with open(filename) as f:
            content = f.read()

        self.assertEqual(content,
                         "--form\n--verify=no\n"
                         "cd http://localhost:8000/api\n"
                         "limit==50\npage==1\n"
                         "'name=John Doe'\n"
                         "Accept:text/csv\n"
                         "'Authorization:ApiKey 1234'\n")

    def test_env_write_to_file_with_env_vars(self):
        filename = self.make_tempfile('hello world\n', 'testenvvar')
        filename_with_var = filename.replace("testenvvar", "${MYPRIVATEVAR}")

        os.environ['MYPRIVATEVAR'] = 'testenvvar'
        execute('env > %s' % filename_with_var, self.context)
        os.environ['MYPRIVATEVAR'] = ''

        with open(filename) as f:
            content = f.read()

        self.assertEqual(content,
                         "--form\n--verify=no\n"
                         "cd http://localhost:8000/api\n"
                         "limit==50\npage==1\n"
                         "'name=John Doe'\n"
                         "Accept:text/csv\n"
                         "'Authorization:ApiKey 1234'\n")

    def test_env_non_ascii_and_write_to_file(self):
        filename = self.make_tempfile()

        # write something first to make sure it's a full overwrite
        with open(filename, 'w') as f:
            f.write('hello world\n')

        self.context.body_params['name'] = '許 功蓋'
        execute('env > %s' % filename, self.context)

        with open(filename, encoding='utf-8') as f:
            content = f.read()

        self.assertEqual(content,
                         "--form\n--verify=no\n"
                         "cd http://localhost:8000/api\n"
                         "limit==50\npage==1\n"
                         "'name=許 功蓋'\n"
                         "Accept:text/csv\n"
                         "'Authorization:ApiKey 1234'\n")

    def test_env_write_to_quoted_filename(self):
        filename = self.make_tempfile()

        # Write something first to make sure it's a full overwrite
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute("env > '%s'" % filename, self.context)

        with open(filename) as f:
            content = f.read()

        self.assertEqual(content,
                         "--form\n--verify=no\n"
                         "cd http://localhost:8000/api\n"
                         "limit==50\npage==1\n"
                         "'name=John Doe'\n"
                         "Accept:text/csv\n"
                         "'Authorization:ApiKey 1234'\n")

    def test_env_append_to_file(self):
        filename = self.make_tempfile()

        # Write something first to make sure it's an append
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute('env >> %s' % filename, self.context)

        with open(filename) as f:
            content = f.read()

        self.assertEqual(content,
                         "hello world\n"
                         "--form\n--verify=no\n"
                         "cd http://localhost:8000/api\n"
                         "limit==50\npage==1\n"
                         "'name=John Doe'\n"
                         "Accept:text/csv\n"
                         "'Authorization:ApiKey 1234'\n")


class TestExecution_source_and_exec(ExecutionTestCase):

    def setUp(self):
        super(TestExecution_source_and_exec, self).setUp()

        self.context.url = 'http://localhost:8000/api'
        self.context.headers.update({
            'Accept': 'text/csv',
            'Authorization': 'ApiKey 1234'
        })
        self.context.querystring_params.update({
            'page': ['1'],
            'limit': ['50']
        })
        self.context.body_params.update({
            'name': 'John Doe'
        })
        self.context.options.update({
            '--verify': 'no',
            '--form': None
        })

        # The file that is about to be sourced/exec'd
        self.filename = self.make_tempfile(
            "Language:en Authorization:'ApiKey 5678'\n"
            "name='Jane Doe'  username=jane   limit==25\n"
            "rm -o --form\n"
            "cd v2/user\n")

    def test_source(self):
        execute('source %s' % self.filename, self.context)

        self.assertEqual(self.context.url,
                         'http://localhost:8000/api/v2/user')
        self.assertEqual(self.context.headers, {
            'Accept': 'text/csv',
            'Authorization': 'ApiKey 5678',
            'Language': 'en'
        })
        self.assertEqual(self.context.querystring_params, {
            'page': ['1'],
            'limit': ['25']
        })
        self.assertEqual(self.context.body_params, {
            'name': 'Jane Doe',
            'username': 'jane'
        })
        self.assertEqual(self.context.options, {
            '--verify': 'no'
        })

    def test_source_with_spaces(self):
        execute(' source       %s   ' % self.filename, self.context)

        self.assertEqual(self.context.url,
                         'http://localhost:8000/api/v2/user')
        self.assertEqual(self.context.headers, {
            'Accept': 'text/csv',
            'Authorization': 'ApiKey 5678',
            'Language': 'en'
        })
        self.assertEqual(self.context.querystring_params, {
            'page': ['1'],
            'limit': ['25']
        })
        self.assertEqual(self.context.body_params, {
            'name': 'Jane Doe',
            'username': 'jane'
        })
        self.assertEqual(self.context.options, {
            '--verify': 'no'
        })

    def test_source_non_existing_file(self):
        c = self.context.copy()
        execute('source no_such_file.txt', self.context)
        self.assertEqual(self.context, c)

        # Expect the error message would be the same as when we open the
        # non-existing file
        try:
            with open('no_such_file.txt'):
                pass
        except OSError as err:
            err_msg = str(err)
        else:
            assert False, 'what?! no_such_file.txt exists!'

        self.assert_stderr(err_msg)

    def test_source_quoted_filename(self):
        execute('source "%s"' % self.filename, self.context)

        self.assertEqual(self.context.url,
                         'http://localhost:8000/api/v2/user')
        self.assertEqual(self.context.headers, {
            'Accept': 'text/csv',
            'Authorization': 'ApiKey 5678',
            'Language': 'en'
        })
        self.assertEqual(self.context.querystring_params, {
            'page': ['1'],
            'limit': ['25']
        })
        self.assertEqual(self.context.body_params, {
            'name': 'Jane Doe',
            'username': 'jane'
        })
        self.assertEqual(self.context.options, {
            '--verify': 'no'
        })

    @pytest.mark.skipif(sys.platform == 'win32',
                        reason="Windows doesn't use backslashes to escape")
    def test_source_escaped_filename(self):
        new_filename = self.filename + r' copy'
        shutil.copyfile(self.filename, new_filename)

        new_filename = new_filename.replace(' ', r'\ ')

        execute('source %s' % new_filename, self.context)

        self.assertEqual(self.context.url,
                         'http://localhost:8000/api/v2/user')
        self.assertEqual(self.context.headers, {
            'Accept': 'text/csv',
            'Authorization': 'ApiKey 5678',
            'Language': 'en'
        })
        self.assertEqual(self.context.querystring_params, {
            'page': ['1'],
            'limit': ['25']
        })
        self.assertEqual(self.context.body_params, {
            'name': 'Jane Doe',
            'username': 'jane'
        })
        self.assertEqual(self.context.options, {
            '--verify': 'no'
        })

    def test_exec(self):
        execute('exec %s' % self.filename, self.context)

        self.assertEqual(self.context.url,
                         'http://localhost:8000/api/v2/user')
        self.assertEqual(self.context.headers, {
            'Authorization': 'ApiKey 5678',
            'Language': 'en'
        })
        self.assertEqual(self.context.querystring_params, {
            'limit': ['25']
        })
        self.assertEqual(self.context.body_params, {
            'name': 'Jane Doe',
            'username': 'jane'
        })

    def test_exec_with_spaces(self):
        execute('  exec    %s   ' % self.filename, self.context)

        self.assertEqual(self.context.url,
                         'http://localhost:8000/api/v2/user')
        self.assertEqual(self.context.headers, {
            'Authorization': 'ApiKey 5678',
            'Language': 'en'
        })
        self.assertEqual(self.context.querystring_params, {
            'limit': ['25']
        })
        self.assertEqual(self.context.body_params, {
            'name': 'Jane Doe',
            'username': 'jane'
        })

    def test_exec_non_existing_file(self):
        c = self.context.copy()
        execute('exec no_such_file.txt', self.context)
        self.assertEqual(self.context, c)

        # Try to get the error message when opening a non-existing file
        try:
            with open('no_such_file.txt'):
                pass
        except OSError as err:
            err_msg = str(err)
        else:
            assert False, 'what?! no_such_file.txt exists!'

        self.assert_stderr(err_msg)

    def test_exec_quoted_filename(self):
        execute("exec '%s'" % self.filename, self.context)

        self.assertEqual(self.context.url,
                         'http://localhost:8000/api/v2/user')
        self.assertEqual(self.context.headers, {
            'Authorization': 'ApiKey 5678',
            'Language': 'en'
        })
        self.assertEqual(self.context.querystring_params, {
            'limit': ['25']
        })
        self.assertEqual(self.context.body_params, {
            'name': 'Jane Doe',
            'username': 'jane'
        })

    @pytest.mark.skipif(sys.platform == 'win32',
                        reason="Windows doesn't use backslashes to escape")
    def test_exec_escaped_filename(self):
        new_filename = self.filename + r' copy'
        shutil.copyfile(self.filename, new_filename)

        new_filename = new_filename.replace(' ', r'\ ')

        execute('exec %s' % new_filename, self.context)
        self.assertEqual(self.context.url,
                         'http://localhost:8000/api/v2/user')
        self.assertEqual(self.context.headers, {
            'Authorization': 'ApiKey 5678',
            'Language': 'en'
        })
        self.assertEqual(self.context.querystring_params, {
            'limit': ['25']
        })
        self.assertEqual(self.context.body_params, {
            'name': 'Jane Doe',
            'username': 'jane'
        })


class TestExecution_env_and_source(ExecutionTestCase):

    def test_env_and_source(self):
        c = Context()
        c.url = 'http://localhost:8000/api'
        c.headers.update({
            'Accept': 'text/csv',
            'Authorization': 'ApiKey 1234'
        })
        c.querystring_params.update({
            'page': ['1'],
            'limit': ['50']
        })
        c.body_params.update({
            'name': 'John Doe'
        })
        c.options.update({
            '--verify': 'no',
            '--form': None
        })

        c2 = c.copy()

        filename = self.make_tempfile()
        execute('env > %s' % filename, c)
        execute('rm *', c)

        self.assertFalse(c.headers)
        self.assertFalse(c.querystring_params)
        self.assertFalse(c.body_params)
        self.assertFalse(c.options)

        execute('source %s' % filename, c)

        self.assertEqual(c, c2)

    def test_env_and_source_non_ascii(self):
        c = Context()
        c.url = 'http://localhost:8000/api'
        c.headers.update({
            'Accept': 'text/csv',
            'Authorization': 'ApiKey 1234'
        })
        c.querystring_params.update({
            'page': ['1'],
            'limit': ['50']
        })
        c.body_params.update({
            'name': '許 功蓋'
        })
        c.options.update({
            '--verify': 'no',
            '--form': None
        })

        c2 = c.copy()

        filename = self.make_tempfile()
        execute('env > %s' % filename, c)
        execute('rm *', c)

        self.assertFalse(c.headers)
        self.assertFalse(c.querystring_params)
        self.assertFalse(c.body_params)
        self.assertFalse(c.options)

        execute('source %s' % filename, c)

        self.assertEqual(c, c2)


class TestExecution_help(ExecutionTestCase):

    def test_help(self):
        execute('help', self.context)
        self.assert_stdout_startswith('Commands:\n\tcd')

    def test_help_with_spaces(self):
        execute('  help   ', self.context)
        self.assert_stdout_startswith('Commands:\n\tcd')


class TestExecution_exit(ExecutionTestCase):

    def test_exit(self):
        execute('exit', self.context)
        self.assertTrue(self.context.should_exit)

    def test_exit_with_spaces(self):
        execute('   exit  ', self.context)
        self.assertTrue(self.context.should_exit)


class TestExecution_cd(ExecutionTestCase):

    def test_single_level(self):
        execute('cd api', self.context)
        self.assertEqual(self.context.url, 'http://localhost/api')

    def test_many_levels(self):
        execute('cd api/v2/movie/50', self.context)
        self.assertEqual(self.context.url, 'http://localhost/api/v2/movie/50')

    def test_change_base(self):
        execute('cd //example.com/api', self.context)
        self.assertEqual(self.context.url, 'http://example.com/api')

    def test_root(self):
        execute('cd /api/v2', self.context)
        self.assertEqual(self.context.url, 'http://localhost/api/v2')

        execute('cd /index.html', self.context)
        self.assertEqual(self.context.url, 'http://localhost/index.html')

    def test_dot_dot(self):
        execute('cd api/v1', self.context)
        self.assertEqual(self.context.url, 'http://localhost/api/v1')

        execute('cd ..', self.context)
        self.assertEqual(self.context.url, 'http://localhost/api')

        # If dot-dot has a trailing slash, the resulting URL should have a
        # trailing slash
        execute('cd ../rest/api/', self.context)
        self.assertEqual(self.context.url, 'http://localhost/rest/api/')

    def test_url_with_trailing_slash(self):
        self.context.url = 'http://localhost/'
        execute('cd api', self.context)
        self.assertEqual(self.context.url, 'http://localhost/api')

        execute('cd v2/', self.context)
        self.assertEqual(self.context.url, 'http://localhost/api/v2/')

        execute('cd /objects/', self.context)
        self.assertEqual(self.context.url, 'http://localhost/objects/')

    def test_path_with_trailing_slash(self):
        execute('cd api/', self.context)
        self.assertEqual(self.context.url, 'http://localhost/api/')

        execute('cd movie/1/', self.context)
        self.assertEqual(self.context.url, 'http://localhost/api/movie/1/')

    def test_without_url(self):
        execute('cd api/', self.context)
        self.assertEqual(self.context.url, 'http://localhost/api/')

        execute('cd', self.context)
        self.assertEqual(self.context.url, 'http://localhost')


class TestExecution_rm(ExecutionTestCase):

    def test_header(self):
        self.context.headers['Content-Type'] = 'text/html'
        execute('rm -h Content-Type', self.context)
        self.assertFalse(self.context.headers)

    def test_option(self):
        self.context.options['--form'] = None
        execute('rm -o --form', self.context)
        self.assertFalse(self.context.options)

    def test_querystring(self):
        self.context.querystring_params['page'] = '1'
        execute('rm -q page', self.context)
        self.assertFalse(self.context.querystring_params)

    def test_body_param(self):
        self.context.body_params['name'] = 'alice'
        execute('rm -b name', self.context)
        self.assertFalse(self.context.body_params)

    def test_body_json_param(self):
        self.context.body_json_params['name'] = 'bob'
        execute('rm -b name', self.context)
        self.assertFalse(self.context.body_json_params)

    def test_header_single_quoted(self):
        self.context.headers['Content-Type'] = 'text/html'
        execute("rm -h 'Content-Type'", self.context)
        self.assertFalse(self.context.headers)

    def test_option_double_quoted(self):
        self.context.options['--form'] = None
        execute('rm -o "--form"', self.context)
        self.assertFalse(self.context.options)

    def test_querystring_double_quoted(self):
        self.context.querystring_params['page size'] = '10'
        execute('rm -q "page size"', self.context)
        self.assertFalse(self.context.querystring_params)

    def test_body_param_double_quoted(self):
        self.context.body_params['family name'] = 'Doe Doe'
        execute('rm -b "family name"', self.context)
        self.assertFalse(self.context.body_params)

    def test_body_param_escaped(self):
        self.context.body_params['family name'] = 'Doe Doe'
        execute(r'rm -b family\ name', self.context)
        self.assertFalse(self.context.body_params)

    def test_body_json_param_escaped_colon(self):
        self.context.body_json_params[r'where[id\:gt]'] = 2
        execute(r'rm -b where[id\:gt]', self.context)
        self.assertFalse(self.context.body_json_params)

    def test_body_param_escaped_equal(self):
        self.context.body_params[r'foo\=bar'] = 'hello'
        execute(r'rm -b foo\=bar', self.context)
        self.assertFalse(self.context.body_params)

    def test_non_existing_key(self):
        execute('rm -q abcd', self.context)
        self.assert_stderr("Key 'abcd' not found")

    def test_non_existing_key_unicode(self):  # See #25
        execute(u'rm -q abcd', self.context)
        self.assert_stderr("Key 'abcd' not found")

    def test_body_reset(self):
        self.context.body_params.update({
            'first_name': 'alice',
            'last_name': 'bryne'
        })
        execute('rm -b *', self.context)
        self.assertFalse(self.context.body_params)

    def test_querystring_reset(self):
        self.context.querystring_params.update({
            'first_name': 'alice',
            'last_name': 'bryne'
        })
        execute('rm -q *', self.context)
        self.assertFalse(self.context.querystring_params)

    def test_headers_reset(self):
        self.context.headers.update({
            'Content-Type': 'text/html',
            'Accept': 'application/json'
        })
        execute('rm -h *', self.context)
        self.assertFalse(self.context.headers)

    def test_options_reset(self):
        self.context.options.update({
            '--form': None,
            '--body': None
        })
        execute('rm -o *', self.context)
        self.assertFalse(self.context.options)

    def test_reset(self):
        self.context.options.update({
            '--form': None,
            '--verify': 'no'
        })
        self.context.headers.update({
            'Accept': 'dontcare',
            'Content-Type': 'dontcare'
        })
        self.context.querystring_params.update({
            'name': 'dontcare',
            'email': 'dontcare'
        })
        self.context.body_params.update({
            'name': 'dontcare',
            'email': 'dontcare'
        })
        self.context.body_json_params.update({
            'name': 'dontcare'
        })

        execute('rm *', self.context)

        self.assertFalse(self.context.options)
        self.assertFalse(self.context.headers)
        self.assertFalse(self.context.querystring_params)
        self.assertFalse(self.context.body_params)
        self.assertFalse(self.context.body_json_params)


class TestExecution_ls(ExecutionTestCase):

    def test_root(self):
        execute('ls', self.context)
        self.assert_stdout('orgs  users\n')

    def test_relative_path(self):
        self.context.url = 'http://localhost/users'
        execute('ls 101', self.context)
        self.assert_stdout('events orgs\n')

    def test_absolute_path(self):
        self.context.url = 'http://localhost/users'
        execute('ls /orgs/1', self.context)
        self.assert_stdout('events  members\n')

    def test_redirect_write(self):
        filename = self.make_tempfile()

        # Write something first to make sure it's a full overwrite
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute('ls > %s' % filename, self.context)

        with open(filename) as f:
            content = f.read()
        self.assertEqual(content, 'orgs\nusers')

    def test_redirect_append(self):
        filename = self.make_tempfile()

        # Write something first to make sure it's an append
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute('ls >> %s' % filename, self.context)

        with open(filename) as f:
            content = f.read()
        self.assertEqual(content, 'hello world\norgs\nusers')

    def test_grep(self):
        execute('ls | grep users', self.context)
        self.assert_stdout('users\n')


class TestMutation(ExecutionTestCase):

    def test_simple_headers(self):
        execute('Accept:text/html User-Agent:HttpPrompt', self.context)
        self.assertEqual(self.context.headers, {
            'Accept': 'text/html',
            'User-Agent': 'HttpPrompt'
        })

    def test_header_value_with_double_quotes(self):
        execute('Accept:text/html User-Agent:"HTTP Prompt"', self.context)
        self.assertEqual(self.context.headers, {
            'Accept': 'text/html',
            'User-Agent': 'HTTP Prompt'
        })

    def test_header_value_with_single_quotes(self):
        execute("Accept:text/html User-Agent:'HTTP Prompt'", self.context)
        self.assertEqual(self.context.headers, {
            'Accept': 'text/html',
            'User-Agent': 'HTTP Prompt'
        })

    def test_header_with_double_quotes(self):
        execute('Accept:text/html "User-Agent:HTTP Prompt"', self.context)
        self.assertEqual(self.context.headers, {
            'Accept': 'text/html',
            'User-Agent': 'HTTP Prompt'
        })

    def test_header_with_single_quotes(self):
        execute("Accept:text/html 'User-Agent:HTTP Prompt'", self.context)
        self.assertEqual(self.context.headers, {
            'Accept': 'text/html',
            'User-Agent': 'HTTP Prompt'
        })

    def test_header_escaped_chars(self):
        execute(r'X-Name:John\'s\ Doe', self.context)
        self.assertEqual(self.context.headers, {
            'X-Name': "John's Doe"
        })

    def test_header_value_escaped_quote(self):
        execute(r"'X-Name:John\'s Doe'", self.context)
        self.assertEqual(self.context.headers, {
            'X-Name': "John's Doe"
        })

    def test_simple_querystring(self):
        execute('page==1 limit==20', self.context)
        self.assertEqual(self.context.querystring_params, {
            'page': ['1'],
            'limit': ['20']
        })

    def test_querystring_with_double_quotes(self):
        execute('page==1 name=="John Doe"', self.context)
        self.assertEqual(self.context.querystring_params, {
            'page': ['1'],
            'name': ['John Doe']
        })

    def test_querystring_with_single_quotes(self):
        execute("page==1 name=='John Doe'", self.context)
        self.assertEqual(self.context.querystring_params, {
            'page': ['1'],
            'name': ['John Doe']
        })

    def test_querystring_with_chinese(self):
        execute("name==王小明", self.context)
        self.assertEqual(self.context.querystring_params, {
            'name': ['王小明']
        })

    def test_querystring_escaped_chars(self):
        execute(r'name==John\'s\ Doe', self.context)
        self.assertEqual(self.context.querystring_params, {
            'name': ["John's Doe"]
        })

    def test_querytstring_value_escaped_quote(self):
        execute(r"'name==John\'s Doe'", self.context)
        self.assertEqual(self.context.querystring_params, {
            'name': ["John's Doe"]
        })

    def test_querystring_key_escaped_quote(self):
        execute(r"'john\'s last name==Doe'", self.context)
        self.assertEqual(self.context.querystring_params, {
            "john's last name": ['Doe']
        })

    def test_simple_body_params(self):
        execute('username=john password=123', self.context)
        self.assertEqual(self.context.body_params, {
            'username': 'john',
            'password': '123'
        })

    def test_body_param_value_with_double_quotes(self):
        execute('name="John Doe" password=123', self.context)
        self.assertEqual(self.context.body_params, {
            'name': 'John Doe',
            'password': '123'
        })

    def test_body_param_value_with_single_quotes(self):
        execute("name='John Doe' password=123", self.context)
        self.assertEqual(self.context.body_params, {
            'name': 'John Doe',
            'password': '123'
        })

    def test_body_param_with_double_quotes(self):
        execute('"name=John Doe" password=123', self.context)
        self.assertEqual(self.context.body_params, {
            'name': 'John Doe',
            'password': '123'
        })

    def test_body_param_with_spanish(self):
        execute('name=Jesús', self.context)
        self.assertEqual(self.context.body_params, {
            'name': 'Jesús'
        })

    def test_body_param_escaped_chars(self):
        execute(r'name=John\'s\ Doe', self.context)
        self.assertEqual(self.context.body_params, {
            'name': "John's Doe"
        })

    def test_body_param_value_escaped_quote(self):
        execute(r"'name=John\'s Doe'", self.context)
        self.assertEqual(self.context.body_params, {
            'name': "John's Doe"
        })

    def test_body_param_key_escaped_quote(self):
        execute(r"'john\'s last name=Doe'", self.context)
        self.assertEqual(self.context.body_params, {
            "john's last name": 'Doe'
        })

    def test_long_option_names(self):
        execute('--auth user:pass --form', self.context)
        self.assertEqual(self.context.options, {
            '--form': None,
            '--auth': 'user:pass'
        })

    def test_long_option_names_with_its_prefix(self):
        execute('--auth-type basic --auth user:pass --session user '
                '--session-read-only user', self.context)
        self.assertEqual(self.context.options, {
            '--auth-type': 'basic',
            '--auth': 'user:pass',
            '--session-read-only': 'user',
            '--session': 'user'
        })

    def test_long_short_option_names_mixed(self):
        execute('--style=default -j --stream', self.context)
        self.assertEqual(self.context.options, {
            '-j': None,
            '--stream': None,
            '--style': 'default'
        })

    def test_option_and_body_param(self):
        execute('--form name="John Doe"', self.context)
        self.assertEqual(self.context.options, {
            '--form': None
        })
        self.assertEqual(self.context.body_params, {
            'name': 'John Doe'
        })

    def test_mixed(self):
        execute('   --form  name="John Doe"   password=1234\\ 5678    '
                'User-Agent:HTTP\\ Prompt  -a   \'john:1234 5678\'  '
                '"Accept:text/html"  ', self.context)
        self.assertEqual(self.context.options, {
            '--form': None,
            '-a': 'john:1234 5678'
        })
        self.assertEqual(self.context.headers, {
            'User-Agent': 'HTTP Prompt',
            'Accept': 'text/html'
        })
        self.assertEqual(self.context.options, {
            '--form': None,
            '-a': 'john:1234 5678'
        })
        self.assertEqual(self.context.body_params, {
            'name': 'John Doe',
            'password': '1234 5678'
        })

    def test_multi_querystring(self):
        execute('name==john name==doe', self.context)
        self.assertEqual(self.context.querystring_params, {
            'name': ['john', 'doe']
        })

        execute('name==jane', self.context)
        self.assertEqual(self.context.querystring_params, {
            'name': ['jane']
        })

    def test_raw_json_object(self):
        execute("""definition:={"id":819,"name":"ML"}""", self.context)
        self.assertEqual(self.context.body_json_params, {
            'definition': {
                'id': 819,
                'name': 'ML'
            }
        })

    def test_raw_json_object_quoted(self):
        execute("""definition:='{"id": 819, "name": "ML"}'""", self.context)
        self.assertEqual(self.context.body_json_params, {
            'definition': {
                'id': 819,
                'name': 'ML'
            }
        })

    def test_raw_json_array(self):
        execute("""names:=["foo","bar"]""", self.context)
        self.assertEqual(self.context.body_json_params, {
            'names': ["foo", "bar"]
        })

    def test_raw_json_array_quoted(self):
        execute("""names:='["foo", "bar"]'""", self.context)
        self.assertEqual(self.context.body_json_params, {
            'names': ["foo", "bar"]
        })

    def test_raw_json_integer(self):
        execute('number:=999', self.context)
        self.assertEqual(self.context.body_json_params, {'number': 999})

    def test_raw_json_string(self):
        execute("""name:='"john doe"'""", self.context)
        self.assertEqual(self.context.body_json_params, {'name': 'john doe'})

    def test_escape_colon(self):
        execute(r'where[id\:gt]:=2', self.context)
        self.assertEqual(self.context.body_json_params, {
            r'where[id\:gt]': 2
        })

    def test_escape_equal(self):
        execute(r'foo\=bar=hello', self.context)
        self.assertEqual(self.context.body_params, {
            r'foo\=bar': 'hello'
        })


class TestHttpAction(ExecutionTestCase):

    def test_get(self):
        execute('get', self.context)
        self.assert_httpie_main_called_with(['GET', 'http://localhost'])

    def test_get_uppercase(self):
        execute('GET', self.context)
        self.assert_httpie_main_called_with(['GET', 'http://localhost'])

    def test_get_multi_querystring(self):
        execute('get foo==1 foo==2 foo==3', self.context)
        self.assert_httpie_main_called_with([
            'GET', 'http://localhost', 'foo==1', 'foo==2', 'foo==3'])

    def test_post(self):
        execute('post page==1', self.context)
        self.assert_httpie_main_called_with(['POST', 'http://localhost',
                                             'page==1'])
        self.assertFalse(self.context.querystring_params)

    def test_post_with_absolute_path(self):
        execute('post /api/v3 name=bob', self.context)
        self.assert_httpie_main_called_with(['POST', 'http://localhost/api/v3',
                                             'name=bob'])
        self.assertFalse(self.context.body_params)
        self.assertEqual(self.context.url, 'http://localhost')

    def test_post_with_relative_path(self):
        self.context.url = 'http://localhost/api/v3'
        execute('post ../v2/movie id=8', self.context)
        self.assert_httpie_main_called_with([
            'POST', 'http://localhost/api/v2/movie', 'id=8'])
        self.assertFalse(self.context.body_params)
        self.assertEqual(self.context.url, 'http://localhost/api/v3')

    def test_post_with_full_url(self):
        execute('post http://httpbin.org/post id=9', self.context)
        self.assert_httpie_main_called_with([
            'POST', 'http://httpbin.org/post', 'id=9'])
        self.assertFalse(self.context.body_params)
        self.assertEqual(self.context.url, 'http://localhost')

    def test_post_with_full_https_url(self):
        execute('post https://httpbin.org/post id=9', self.context)
        self.assert_httpie_main_called_with([
            'POST', 'https://httpbin.org/post', 'id=9'])
        self.assertFalse(self.context.body_params)
        self.assertEqual(self.context.url, 'http://localhost')

    def test_post_uppercase(self):
        execute('POST content=text', self.context)
        self.assert_httpie_main_called_with(['POST', 'http://localhost',
                                             'content=text'])
        self.assertFalse(self.context.body_params)

    def test_post_raw_json_object(self):
        execute("""post definition:={"id":819,"name":"ML"}""",
                self.context)
        self.assert_httpie_main_called_with([
            'POST', 'http://localhost',
            """definition:={"id": 819, "name": "ML"}"""])
        self.assertFalse(self.context.body_json_params)

    def test_post_raw_json_object_quoted(self):
        execute("""post definition:='{"id": 819, "name": "ML"}'""",
                self.context)
        self.assert_httpie_main_called_with([
            'POST', 'http://localhost',
            'definition:={"id": 819, "name": "ML"}'])
        self.assertFalse(self.context.body_json_params)

    def test_post_raw_json_array(self):
        execute("""post hobbies:=["foo","bar"]""",
                self.context)
        self.assert_httpie_main_called_with([
            'POST', 'http://localhost',
            'hobbies:=["foo", "bar"]'])
        self.assertFalse(self.context.body_json_params)

    def test_post_raw_json_array_quoted(self):
        execute("""post hobbies:='["foo", "bar"]'""",
                self.context)
        self.assert_httpie_main_called_with([
            'POST', 'http://localhost',
            'hobbies:=["foo", "bar"]'])
        self.assertFalse(self.context.body_json_params)

    def test_post_raw_json_integer(self):
        execute('post number:=123',
                self.context)
        self.assert_httpie_main_called_with([
            'POST', 'http://localhost', 'number:=123'])
        self.assertFalse(self.context.body_json_params)

    def test_post_raw_json_boolean(self):
        execute('post foo:=true',
                self.context)
        self.assert_httpie_main_called_with([
            'POST', 'http://localhost', 'foo:=true'])
        self.assertFalse(self.context.body_json_params)

    def test_delete(self):
        execute('delete', self.context)
        self.assert_httpie_main_called_with(['DELETE', 'http://localhost'])

    def test_delete_uppercase(self):
        execute('DELETE', self.context)
        self.assert_httpie_main_called_with(['DELETE', 'http://localhost'])

    def test_patch(self):
        execute('patch', self.context)
        self.assert_httpie_main_called_with(['PATCH', 'http://localhost'])

    def test_patch_uppercase(self):
        execute('PATCH', self.context)
        self.assert_httpie_main_called_with(['PATCH', 'http://localhost'])

    def test_head(self):
        execute('head', self.context)
        self.assert_httpie_main_called_with(['HEAD', 'http://localhost'])

    def test_head_uppercase(self):
        execute('HEAD', self.context)
        self.assert_httpie_main_called_with(['HEAD', 'http://localhost'])

    def test_options(self):
        execute('options', self.context)
        self.assert_httpie_main_called_with(['OPTIONS', 'http://localhost'])


class TestHttpActionRedirection(ExecutionTestCase):

    def test_get(self):
        execute('get > data.json', self.context)
        self.assert_httpie_main_called_with(['GET', 'http://localhost'])

        env = self.httpie_main.call_args[1]['env']
        self.assertFalse(env.stdout_isatty)
        self.assertEqual(env.stdout.fp.name, 'data.json')


@pytest.mark.slow
class TestHttpBin(TempAppDirTestCase):
    """Send real requests to http://httpbin.org, save the responses to files,
    and asserts on the file content.
    """

    def setUp(self):
        super(TestHttpBin, self).setUp()

        # XXX: pytest doesn't allow HTTPie to read stdin while it's capturing
        # stdout, so we replace stdin with a file temporarily during the test.
        class MockStdin(object):
            def __init__(self, fp):
                self.fp = fp

            def isatty(self):
                return True

            def __getattr__(self, name):
                if name == 'isatty':
                    return self.isatty
                return getattr(self.fp, name)

        self.orig_stdin = sys.stdin
        filename = self.make_tempfile()
        sys.stdin = MockStdin(open(filename, 'rb'))
        sys.stdin.isatty = lambda: True

        # Mock echo_via_pager() so that we can catch data fed to stdout
        self.patcher = patch('http_prompt.output.click.echo_via_pager')
        self.echo_via_pager = self.patcher.start()

    def tearDown(self):
        self.patcher.stop()

        sys.stdin.close()
        sys.stdin = self.orig_stdin

        super(TestHttpBin, self).tearDown()

    def get_stdout(self):
        return self.echo_via_pager.call_args[0][0]

    def execute_redirection(self, command):
        context = Context('http://httpbin.org')
        filename = self.make_tempfile()
        execute('%s > %s' % (command, filename), context)

        with open(filename, 'rb') as f:
            return f.read()

    def execute_pipe(self, command):
        context = Context('http://httpbin.org')
        execute(command, context)

    def test_get_image(self):
        data = self.execute_redirection('get /image/png')
        self.assertTrue(data)
        self.assertEqual(hashlib.sha1(data).hexdigest(),
                         '379f5137831350c900e757b39e525b9db1426d53')

    def test_get_querystring(self):
        data = self.execute_redirection(
            'get /get id==1234 X-Custom-Header:5678')
        data = json.loads(data.decode())
        self.assertEqual(data['args'], {
            'id': '1234'
        })
        self.assertEqual(data['headers']['X-Custom-Header'], '5678')

    def test_post_json(self):
        data = self.execute_redirection(
            'post /post id=1234 X-Custom-Header:5678')
        data = json.loads(data.decode())
        self.assertEqual(data['json'], {
            'id': '1234'
        })
        self.assertEqual(data['headers']['X-Custom-Header'], '5678')

    def test_post_form(self):
        data = self.execute_redirection(
            'post /post --form id=1234 X-Custom-Header:5678')
        data = json.loads(data.decode())
        self.assertEqual(data['form'], {
            'id': '1234'
        })
        self.assertEqual(data['headers']['X-Custom-Header'], '5678')

    @pytest.mark.skipif(sys.platform == 'win32', reason="Unix only")
    def test_get_and_tee(self):
        filename = self.make_tempfile()
        self.execute_pipe('get /get hello==world | tee %s' % filename)

        with open(filename) as f:
            data = json.load(f)
        self.assertEqual(data['args'], {'hello': 'world'})

        printed_msg = self.get_stdout()
        data = json.loads(printed_msg)
        self.assertEqual(data['args'], {'hello': 'world'})


class TestCommandPreview(ExecutionTestCase):

    def test_httpie_without_args(self):
        execute('httpie', self.context)
        self.assert_stdout('http http://localhost\n')

    def test_httpie_with_post(self):
        execute('httpie post name=alice', self.context)
        self.assert_stdout('http POST http://localhost name=alice\n')
        self.assertFalse(self.context.body_params)

    def test_httpie_with_absolute_path(self):
        execute('httpie post /api name=alice', self.context)
        self.assert_stdout('http POST http://localhost/api name=alice\n')
        self.assertFalse(self.context.body_params)

    def test_httpie_with_full_url(self):
        execute('httpie POST http://httpbin.org/post name=alice', self.context)
        self.assert_stdout('http POST http://httpbin.org/post name=alice\n')
        self.assertEqual(self.context.url, 'http://localhost')
        self.assertFalse(self.context.body_params)

    def test_httpie_with_full_https_url(self):
        execute('httpie post https://httpbin.org/post name=alice',
                self.context)
        self.assert_stdout('http POST https://httpbin.org/post name=alice\n')
        self.assertEqual(self.context.url, 'http://localhost')
        self.assertFalse(self.context.body_params)

    def test_httpie_with_quotes(self):
        execute(r'httpie post http://httpbin.org/post name="john doe" '
                r"apikey==abc\ 123 'Authorization:ApiKey 1234'",
                self.context)
        self.assert_stdout(
            "http POST http://httpbin.org/post 'apikey==abc 123' "
            "'name=john doe' 'Authorization:ApiKey 1234'\n")
        self.assertEqual(self.context.url, 'http://localhost')
        self.assertFalse(self.context.body_params)
        self.assertFalse(self.context.querystring_params)
        self.assertFalse(self.context.headers)

    def test_httpie_with_multi_querystring(self):
        execute('httpie get foo==1 foo==2 foo==3', self.context)
        self.assert_stdout('http GET http://localhost foo==1 foo==2 foo==3\n')
        self.assertEqual(self.context.url, 'http://localhost')
        self.assertFalse(self.context.querystring_params)


class TestPipe(ExecutionTestCase):

    @pytest.mark.skipif(sys.platform == 'win32', reason="Unix only")
    def test_httpie_sed(self):
        execute("httpie get some==data | sed 's/data$/input/'", self.context)
        self.assert_stdout('http GET http://localhost some==input\n')

    @pytest.mark.skipif(sys.platform == 'win32', reason="Unix only")
    def test_httpie_sed_with_echo(self):
        execute("httpie post | `echo \"sed 's/localhost$/127.0.0.1/'\"`",
                self.context)
        self.assert_stdout("http POST http://127.0.0.1\n")

    @pytest.mark.skipif(sys.platform == 'win32', reason="Unix only")
    def test_env_grep(self):
        self.context.body_params = {
            'username': 'jane',
            'name': 'Jane',
            'password': '1234'
        }
        execute('env | grep name', self.context)
        self.assert_stdout('name=Jane\nusername=jane\n')


class TestShellSubstitution(ExecutionTestCase):

    def test_unquoted_option(self):
        execute("--auth `echo user:pass`", self.context)
        self.assertEqual(self.context.options, {
            '--auth': 'user:pass'
        })

    def test_partial_unquoted_option(self):
        execute("--auth user:`echo pass`", self.context)
        self.assertEqual(self.context.options, {
            '--auth': 'user:pass'
        })

    def test_partial_squoted_option(self):
        execute("--auth='user:`echo pass`'", self.context)
        self.assertEqual(self.context.options, {
            '--auth': 'user:pass'
        })

    def test_partial_dquoted_option(self):
        execute('--auth="user:`echo pass`"', self.context)
        self.assertEqual(self.context.options, {
            '--auth': 'user:pass'
        })

    def test_unquoted_header(self):
        execute("`echo 'X-Greeting'`:`echo 'hello world'`", self.context)
        if sys.platform == 'win32':
            expected_key = "'X-Greeting'"
            expected_value = "'hello world'"
        else:
            expected_key = 'X-Greeting'
            expected_value = 'hello world'

        self.assertEqual(self.context.headers, {
            expected_key: expected_value
        })

    def test_full_squoted_header(self):
        execute("'`echo X-Greeting`:`echo hello`'", self.context)
        self.assertEqual(self.context.headers, {
            'X-Greeting': 'hello'
        })

    def test_full_dquoted_header(self):
        execute('"`echo X-Greeting`:`echo hello`"', self.context)
        self.assertEqual(self.context.headers, {
            'X-Greeting': 'hello'
        })

    def test_value_squoted_header(self):
        execute("`echo X-Greeting`:'`echo hello`'", self.context)
        self.assertEqual(self.context.headers, {
            'X-Greeting': 'hello'
        })

    def test_value_dquoted_header(self):
        execute('`echo X-Greeting`:"`echo hello`"', self.context)
        self.assertEqual(self.context.headers, {
            'X-Greeting': 'hello'
        })

    def test_partial_value_dquoted_header(self):
        execute('Authorization:"Bearer `echo OAUTH TOKEN`"', self.context)
        self.assertEqual(self.context.headers, {
            'Authorization': 'Bearer OAUTH TOKEN'
        })

    def test_partial_full_dquoted_header(self):
        execute('"Authorization:Bearer `echo OAUTH TOKEN`"', self.context)
        self.assertEqual(self.context.headers, {
            'Authorization': 'Bearer OAUTH TOKEN'
        })

    def test_unquoted_querystring(self):
        execute("`echo greeting`==`echo 'hello world'`", self.context)
        expected = ("'hello world'"
                    if sys.platform == 'win32' else 'hello world')
        self.assertEqual(self.context.querystring_params, {
            'greeting': [expected]
        })

    def test_full_squoted_querystring(self):
        execute("'`echo greeting`==`echo hello`'", self.context)
        self.assertEqual(self.context.querystring_params, {
            'greeting': ['hello']
        })

    def test_value_squoted_querystring(self):
        execute("`echo greeting`=='`echo hello`'", self.context)
        self.assertEqual(self.context.querystring_params, {
            'greeting': ['hello']
        })

    def test_value_dquoted_querystring(self):
        execute('`echo greeting`=="`echo hello`"', self.context)
        self.assertEqual(self.context.querystring_params, {
            'greeting': ['hello']
        })

    def test_unquoted_body_param(self):
        execute("`echo greeting`=`echo 'hello world'`", self.context)
        expected = ("'hello world'"
                    if sys.platform == 'win32' else 'hello world')
        self.assertEqual(self.context.body_params, {
            'greeting': expected
        })

    def test_full_squoted_body_param(self):
        execute("'`echo greeting`=`echo hello`'", self.context)
        self.assertEqual(self.context.body_params, {
            'greeting': 'hello'
        })

    def test_value_squoted_body_param(self):
        execute("`echo greeting`='`echo hello`'", self.context)
        self.assertEqual(self.context.body_params, {
            'greeting': 'hello'
        })

    def test_full_dquoted_body_param(self):
        execute('"`echo greeting`=`echo hello`"', self.context)
        self.assertEqual(self.context.body_params, {
            'greeting': 'hello'
        })

    def test_bad_command(self):
        execute("name=`bad command test`", self.context)
        self.assertEqual(self.context.body_params, {'name': ''})

    @pytest.mark.skipif(sys.platform == 'win32', reason="Unix only")
    def test_pipe_and_grep(self):
        execute("greeting=`echo 'hello world\nhihi\n' | grep hello`",
                self.context)
        self.assertEqual(self.context.body_params, {
            'greeting': 'hello world'
        })


class TestCommandPreviewRedirection(ExecutionTestCase):

    def test_httpie_redirect_write(self):
        filename = self.make_tempfile()

        # Write something first to make sure it's a full overwrite
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute('httpie > %s' % filename, self.context)

        with open(filename) as f:
            content = f.read()
        self.assertEqual(content, 'http http://localhost\n')

    def test_httpie_redirect_write_quoted_filename(self):
        filename = self.make_tempfile()

        # Write something first to make sure it's a full overwrite
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute('httpie > "%s"' % filename, self.context)

        with open(filename) as f:
            content = f.read()
        self.assertEqual(content, 'http http://localhost\n')

    @pytest.mark.skipif(sys.platform == 'win32',
                        reason="Windows doesn't use backslashes to escape")
    def test_httpie_redirect_write_escaped_filename(self):
        filename = self.make_tempfile()
        filename += r' copy'

        # Write something first to make sure it's a full overwrite
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute('httpie > %s' % filename.replace(' ', r'\ '), self.context)

        with open(filename) as f:
            content = f.read()
        self.assertEqual(content, 'http http://localhost\n')

    def test_httpie_redirect_write_with_args(self):
        filename = self.make_tempfile()

        # Write something first to make sure it's a full overwrite
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute('httpie post http://example.org name=john > %s' % filename,
                self.context)

        with open(filename) as f:
            content = f.read()
        self.assertEqual(content, 'http POST http://example.org name=john\n')

    def test_httpie_redirect_append(self):
        filename = self.make_tempfile()

        # Write something first to make sure it's an append
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute('httpie >> %s' % filename, self.context)

        with open(filename) as f:
            content = f.read()
        self.assertEqual(content, 'hello world\nhttp http://localhost\n')

    def test_httpie_redirect_append_without_spaces(self):
        filename = self.make_tempfile()

        # Write something first to make sure it's an append
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute('httpie>>%s' % filename, self.context)

        with open(filename) as f:
            content = f.read()
        self.assertEqual(content, 'hello world\nhttp http://localhost\n')

    def test_httpie_redirect_append_quoted_filename(self):
        filename = self.make_tempfile()

        # Write something first to make sure it's an append
        with open(filename, 'w') as f:
            f.write('hello world\n')

        execute("httpie >> '%s'" % filename, self.context)

        with open(filename) as f:
            content = f.read()
        self.assertEqual(content, 'hello world\nhttp http://localhost\n')


================================================
FILE: tests/test_installation.py
================================================
"""Test if http-prompt is installed correctly."""

import subprocess

import pytest

from subprocess import PIPE

from .utils import get_http_prompt_path
from http_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


================================================
FILE: tests/test_interaction.py
================================================
import os
import sys

import pexpect
import pytest

from .base import TempAppDirTestCase
from .utils import get_http_prompt_path
from http_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()


================================================
FILE: tests/test_lexer.py
================================================
import unittest

from pygments.token import Keyword, String, Text, Error, Name, Operator

from http_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 Test
Download .txt
gitextract_2twf_0e5/

├── .coveragerc
├── .github/
│   └── workflows/
│       └── build.yml
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── docs/
│   ├── LICENSE
│   ├── Makefile
│   ├── README.md
│   ├── _templates/
│   │   └── layout.html
│   ├── conf.py
│   ├── contributor-guide.rst
│   ├── index.rst
│   ├── make.bat
│   └── user-guide.rst
├── http_prompt/
│   ├── __init__.py
│   ├── cli.py
│   ├── completer.py
│   ├── completion.py
│   ├── config.py
│   ├── context/
│   │   ├── __init__.py
│   │   └── transform.py
│   ├── contextio.py
│   ├── defaultconfig.py
│   ├── execution.py
│   ├── lexer.py
│   ├── options.py
│   ├── output.py
│   ├── tree.py
│   ├── utils.py
│   └── xdg.py
├── requirements-test.txt
├── requirements.txt
├── setup.cfg
├── setup.py
├── snap/
│   └── snapcraft.yaml
├── tests/
│   ├── __init__.py
│   ├── base.py
│   ├── context/
│   │   ├── test_context.py
│   │   └── test_transform.py
│   ├── test_cli.py
│   ├── test_completer.py
│   ├── test_config.py
│   ├── test_contextio.py
│   ├── test_execution.py
│   ├── test_installation.py
│   ├── test_interaction.py
│   ├── test_lexer.py
│   ├── test_tree.py
│   ├── test_utils.py
│   ├── test_xdg.py
│   └── utils.py
└── tox.ini
Download .txt
SYMBOL INDEX (576 symbols across 28 files)

FILE: http_prompt/cli.py
  function fix_incomplete_url (line 33) | def fix_incomplete_url(url):
  function update_cookies (line 43) | def update_cookies(base_value, cookies):
  class ExecutionListener (line 50) | class ExecutionListener(object):
    method __init__ (line 52) | def __init__(self, cfg):
    method context_changed (line 55) | def context_changed(self, context):
    method response_returned (line 59) | def response_returned(self, context, response):
  function normalize_url (line 73) | def normalize_url(ctx, param, value):
  function cli (line 89) | def cli(spec, env, url, http_options):

FILE: http_prompt/completer.py
  function compile_rules (line 37) | def compile_rules(rules):
  function fuzzyfinder (line 48) | def fuzzyfinder(text, collection):
  function match_completions (line 63) | def match_completions(cur_word, word_dict):
  class CompletionGenerator (line 71) | class CompletionGenerator(object):
    method root_commands (line 73) | def root_commands(self, context, match):
    method header_values (line 80) | def header_values(self, context, match):
    method preview (line 87) | def preview(self, context, match):
    method actions (line 93) | def actions(self, context, match):
    method concat_mutations (line 96) | def concat_mutations(self, context, match):
    method existing_body_params (line 109) | def existing_body_params(self, context, match):
    method existing_querystring_params (line 114) | def existing_querystring_params(self, context, match):
    method existing_header_names (line 119) | def existing_header_names(self, context, match):
    method existing_option_names (line 123) | def existing_option_names(self, context, match):
    method urlpaths (line 127) | def urlpaths(self, context, match):
    method _generic_generate (line 141) | def _generic_generate(self, names, values, descs):
  class HttpPromptCompleter (line 159) | class HttpPromptCompleter(Completer):
    method __init__ (line 161) | def __init__(self, context):
    method get_completions (line 165) | def get_completions(self, document, complete_event):

FILE: http_prompt/config.py
  function get_user_config_path (line 10) | def get_user_config_path():
  function initialize (line 15) | def initialize():
  function _module_to_dict (line 32) | def _module_to_dict(module):
  function load_default (line 41) | def load_default():
  function load_user (line 46) | def load_user():
  function load (line 64) | def load():

FILE: http_prompt/context/__init__.py
  class Context (line 4) | class Context(object):
    method __init__ (line 6) | def __init__(self, url=None, spec=None):
    method __eq__ (line 76) | def __eq__(self, other):
    method copy (line 85) | def copy(self):
    method update (line 95) | def update(self, context):

FILE: http_prompt/context/transform.py
  function _noop (line 8) | def _noop(s):
  function _extract_httpie_options (line 12) | def _extract_httpie_options(context, quote=False, join_key_value=False,
  function _extract_httpie_request_items (line 38) | def _extract_httpie_request_items(context, quote=False):
  function extract_args_for_httpie_main (line 69) | def extract_args_for_httpie_main(context, method=None):
  function format_to_curl (line 83) | def format_to_curl(context, method=None):
  function format_to_raw (line 88) | def format_to_raw(context, method=None):
  function format_to_httpie (line 93) | def format_to_httpie(context, method=None):
  function format_to_http_prompt (line 104) | def format_to_http_prompt(context, excluded_options=None):

FILE: http_prompt/contextio.py
  function _get_context_filepath (line 18) | def _get_context_filepath():
  function load_context (line 23) | def load_context(context, file_path=None):
  function save_context (line 33) | def save_context(context):

FILE: http_prompt/execution.py
  function urljoin2 (line 147) | def urljoin2(base, path, **kwargs):
  function generate_help_text (line 156) | def generate_help_text():
  function normalize_filepath (line 174) | def normalize_filepath(path):
  function normalize_filepath (line 177) | def normalize_filepath(path):
  class DummyExecutionListener (line 181) | class DummyExecutionListener(object):
    method context_changed (line 183) | def context_changed(self, context):
    method response_returned (line 186) | def response_returned(self, context, response):
  class ExecutionVisitor (line 190) | class ExecutionVisitor(NodeVisitor):
    method __init__ (line 194) | def __init__(self, context, listener=None, style=None):
    method output (line 221) | def output(self):
    method output (line 225) | def output(self, new_output):
    method visit_method (line 230) | def visit_method(self, node, children):
    method visit_urlpath (line 234) | def visit_urlpath(self, node, children):
    method visit_cd (line 239) | def visit_cd(self, node, children):
    method visit_rm (line 251) | def visit_rm(self, node, children):
    method visit_help (line 292) | def visit_help(self, node, children):
    method _redirect_output (line 296) | def _redirect_output(self, filepath, mode):
    method visit_redir_append (line 300) | def visit_redir_append(self, node, children):
    method visit_redir_write (line 304) | def visit_redir_write(self, node, children):
    method visit_pipe (line 308) | def visit_pipe(self, node, children):
    method visit_exec (line 314) | def visit_exec(self, node, children):
    method visit_source (line 323) | def visit_source(self, node, children):
    method _colorize (line 330) | def _colorize(self, text, token_type):
    method visit_ls (line 338) | def visit_ls(self, node, children):
    method visit_env (line 355) | def visit_env(self, node, children):
    method visit_exit (line 360) | def visit_exit(self, node, children):
    method visit_clear (line 364) | def visit_clear(self, node, children):
    method visit_mutkey (line 368) | def visit_mutkey(self, node, children):
    method _mutate (line 373) | def _mutate(self, node, key, op, val):
    method visit_unquoted_mut (line 390) | def visit_unquoted_mut(self, node, children):
    method visit_full_squoted_mut (line 394) | def visit_full_squoted_mut(self, node, children):
    method visit_full_dquoted_mut (line 398) | def visit_full_dquoted_mut(self, node, children):
    method visit_value_squoted_mut (line 402) | def visit_value_squoted_mut(self, node, children):
    method visit_value_dquoted_mut (line 406) | def visit_value_dquoted_mut(self, node, children):
    method _visit_mut_key_or_val (line 410) | def _visit_mut_key_or_val(self, node, children):
    method visit_mutop (line 420) | def visit_mutop(self, node, children):
    method visit_flag_option_mut (line 423) | def visit_flag_option_mut(self, node, children):
    method visit_flag_optname (line 428) | def visit_flag_optname(self, node, children):
    method visit_value_option_mut (line 431) | def visit_value_option_mut(self, node, children):
    method visit_value_optname (line 436) | def visit_value_optname(self, node, children):
    method visit_filepath (line 439) | def visit_filepath(self, node, children):
    method visit_string (line 442) | def visit_string(self, node, children):
    method visit_quoted_filepath (line 445) | def visit_quoted_filepath(self, node, children):
    method visit_unquoted_filepath (line 448) | def visit_unquoted_filepath(self, node, children):
    method visit_unquoted_string (line 451) | def visit_unquoted_string(self, node, children):
    method visit_quoted_string (line 454) | def visit_quoted_string(self, node, children):
    method _visit_stringitem (line 457) | def _visit_stringitem(self, node, children):
    method visit_tool (line 470) | def visit_tool(self, node, children):
    method visit_mutation (line 474) | def visit_mutation(self, node, children):
    method _final_context (line 479) | def _final_context(self):
    method _trace_get_response (line 484) | def _trace_get_response(self, frame, event, arg):
    method _call_httpie_main (line 492) | def _call_httpie_main(self):
    method visit_immutation (line 512) | def visit_immutation(self, node, children):
    method visit_preview (line 518) | def visit_preview(self, node, children):
    method visit_action (line 528) | def visit_action(self, node, children):
    method visit_shell_subs (line 534) | def visit_shell_subs(self, node, children):
    method visit_shell_code (line 539) | def visit_shell_code(self, node, children):
    method generic_visit (line 542) | def generic_visit(self, node, children):
  function execute (line 550) | def execute(command, context, listener=None, style=None):

FILE: http_prompt/lexer.py
  function string_rules (line 19) | def string_rules(state):
  class HttpPromptLexer (line 35) | class HttpPromptLexer(RegexLexer):

FILE: http_prompt/output.py
  class Printer (line 6) | class Printer(object):
    method write (line 9) | def write(self, data):
    method flush (line 17) | def flush(self):
    method close (line 20) | def close(self):
    method isatty (line 23) | def isatty(self):
    method fileno (line 26) | def fileno(self):
    method clear (line 29) | def clear(self):
  class TextWriter (line 33) | class TextWriter(object):
    method __init__ (line 38) | def __init__(self, fp):
    method write (line 41) | def write(self, data):
    method flush (line 46) | def flush(self):
    method close (line 49) | def close(self):
    method isatty (line 52) | def isatty(self):
    method fileno (line 55) | def fileno(self):

FILE: http_prompt/tree.py
  class Node (line 4) | class Node(object):
    method __init__ (line 6) | def __init__(self, name, data=None, parent=None):
    method __str__ (line 15) | def __str__(self):
    method __repr__ (line 18) | def __repr__(self):
    method __lt__ (line 21) | def __lt__(self, other):
    method __eq__ (line 28) | def __eq__(self, other):
    method __hash__ (line 31) | def __hash__(self):
    method add_path (line 34) | def add_path(self, *path, **kwargs):
    method find_child (line 47) | def find_child(self, name, wildcard=True):
    method ls (line 60) | def ls(self, *path):

FILE: http_prompt/utils.py
  function smart_quote (line 11) | def smart_quote(s):
  function unquote (line 15) | def unquote(s):
  function unescape (line 26) | def unescape(s, exclude=None):
  function get_terminal_size (line 34) | def get_terminal_size():
  function strip_ansi_escapes (line 38) | def strip_ansi_escapes(text):
  function colformat (line 42) | def colformat(strings, num_sep_spaces=1, terminal_width=None):

FILE: http_prompt/xdg.py
  function _get_dir (line 13) | def _get_dir(envvar_name, default_dir, resource_name=None):

FILE: setup.py
  function find_version (line 12) | def find_version(*file_paths):
  function read_description (line 26) | def read_description(filename):
  function read_requirements (line 31) | def read_requirements(filename):

FILE: tests/base.py
  class TempAppDirTestCase (line 8) | class TempAppDirTestCase(unittest.TestCase):
    method setUp (line 13) | def setUp(self):
    method tearDown (line 38) | def tearDown(self):
    method make_tempfile (line 48) | def make_tempfile(self, data='', subdir_name=''):

FILE: tests/context/test_context.py
  function test_creation (line 4) | def test_creation():
  function test_creation_with_longer_url (line 14) | def test_creation_with_longer_url():
  function test_eq (line 24) | def test_eq():
  function test_copy (line 33) | def test_copy():
  function test_update (line 40) | def test_update():
  function test_spec (line 67) | def test_spec():
  function test_override (line 111) | def test_override():

FILE: tests/context/test_transform.py
  function test_extract_args_for_httpie_main_get (line 5) | def test_extract_args_for_httpie_main_get():
  function test_extract_args_for_httpie_main_post (line 21) | def test_extract_args_for_httpie_main_post():
  function test_extract_raw_json_args_for_httpie_main_post (line 43) | def test_extract_raw_json_args_for_httpie_main_post():
  function test_format_to_httpie_get (line 60) | def test_format_to_httpie_get():
  function test_format_to_httpie_post (line 78) | def test_format_to_httpie_post():
  function test_format_to_http_prompt_1 (line 99) | def test_format_to_http_prompt_1():
  function test_format_to_http_prompt_2 (line 118) | def test_format_to_http_prompt_2():
  function test_format_raw_json_string_to_http_prompt (line 143) | def test_format_raw_json_string_to_http_prompt():
  function test_extract_httpie_options (line 154) | def test_extract_httpie_options():

FILE: tests/test_cli.py
  function run_and_exit (line 16) | def run_and_exit(cli_args=None, prompt_commands=None):
  class TestCli (line 50) | class TestCli(TempAppDirTestCase):
    method test_without_args (line 52) | def test_without_args(self):
    method test_incomplete_url1 (line 61) | def test_incomplete_url1(self):
    method test_incomplete_url2 (line 70) | def test_incomplete_url2(self):
    method test_incomplete_url3 (line 79) | def test_incomplete_url3(self):
    method test_httpie_oprions (line 88) | def test_httpie_oprions(self):
    method test_persistent_context (line 99) | def test_persistent_context(self):
    method test_cli_args_bypasses_persistent_context (line 116) | def test_cli_args_bypasses_persistent_context(self):
    method test_config_file (line 132) | def test_config_file(self):
    method test_cli_arguments_with_spaces (line 143) | def test_cli_arguments_with_spaces(self):
    method test_spec_from_local (line 153) | def test_spec_from_local(self):
    method test_spec_basePath (line 167) | def test_spec_basePath(self):
    method test_spec_from_http (line 188) | def test_spec_from_http(self):
    method test_spec_from_http_only (line 199) | def test_spec_from_http_only(self):
    method test_spec_with_trailing_slash (line 212) | def test_spec_with_trailing_slash(self):
    method test_env_only (line 229) | def test_env_only(self):
    method test_env_with_url (line 240) | def test_env_with_url(self):
    method test_env_with_options (line 252) | def test_env_with_options(self):
    method test_press_ctrl_d (line 266) | def test_press_ctrl_d(self, execute_mock, prompt_mock):
  class TestExecutionListenerSetCookies (line 273) | class TestExecutionListenerSetCookies(unittest.TestCase):
    method setUp (line 275) | def setUp(self):
    method test_auto (line 287) | def test_auto(self):
    method test_ask_and_yes (line 295) | def test_ask_and_yes(self, confirm_mock):
    method test_ask_and_no (line 305) | def test_ask_and_no(self, confirm_mock):
    method test_off (line 314) | def test_off(self):

FILE: tests/test_completer.py
  class TestCompleter (line 10) | class TestCompleter(unittest.TestCase):
    method setUp (line 12) | def setUp(self):
    method get_completions (line 28) | def get_completions(self, command):
    method test_header_name (line 37) | def test_header_name(self):
    method test_header_value (line 41) | def test_header_value(self):
    method test_verify_option (line 45) | def test_verify_option(self):
    method test_preview_then_action (line 49) | def test_preview_then_action(self):
    method test_rm_body_param (line 53) | def test_rm_body_param(self):
    method test_rm_body_json_param (line 58) | def test_rm_body_json_param(self):
    method test_rm_querystring_param (line 63) | def test_rm_querystring_param(self):
    method test_rm_header (line 68) | def test_rm_header(self):
    method test_rm_option (line 73) | def test_rm_option(self):
    method test_querystring_with_chinese (line 78) | def test_querystring_with_chinese(self):
    method test_header_with_spanish (line 82) | def test_header_with_spanish(self):
    method test_options_method (line 86) | def test_options_method(self):
    method test_ls_no_path (line 90) | def test_ls_no_path(self):
    method test_ls_no_path_substring (line 94) | def test_ls_no_path_substring(self):
    method test_ls_absolute_path (line 98) | def test_ls_absolute_path(self):
    method test_ls_absolute_path_substring (line 102) | def test_ls_absolute_path_substring(self):
    method test_ls_relative_path (line 106) | def test_ls_relative_path(self):
    method test_cd_no_path (line 111) | def test_cd_no_path(self):
    method test_cd_no_path_substring (line 115) | def test_cd_no_path_substring(self):
    method test_cd_absolute_path (line 119) | def test_cd_absolute_path(self):
    method test_cd_absolute_path_substring (line 123) | def test_cd_absolute_path_substring(self):
    method test_cd_relative_path (line 127) | def test_cd_relative_path(self):

FILE: tests/test_config.py
  function _hash_file (line 8) | def _hash_file(path):
  class TestConfig (line 14) | class TestConfig(TempAppDirTestCase):
    method test_initialize (line 16) | def test_initialize(self):
    method test_load_default (line 42) | def test_load_default(self):
    method test_load_user (line 48) | def test_load_user(self):
    method test_load (line 58) | def test_load(self):

FILE: tests/test_contextio.py
  class TestContextIO (line 7) | class TestContextIO(TempAppDirTestCase):
    method test_save_and_load_context_non_ascii (line 9) | def test_save_and_load_context_non_ascii(self):

FILE: tests/test_execution.py
  class ExecutionTestCase (line 21) | class ExecutionTestCase(TempAppDirTestCase):
    method setUp (line 23) | def setUp(self):
    method tearDown (line 52) | def tearDown(self):
    method assert_httpie_main_called_with (line 57) | def assert_httpie_main_called_with(self, args):
    method assert_stdout (line 61) | def assert_stdout(self, expected_msg):
    method assert_stdout_startswith (line 67) | def assert_stdout_startswith(self, expected_prefix):
    method get_stdout (line 71) | def get_stdout(self):
    method assert_stderr (line 74) | def assert_stderr(self, expected_msg):
  class TestExecution_noop (line 81) | class TestExecution_noop(ExecutionTestCase):
    method test_empty_string (line 83) | def test_empty_string(self):
    method test_spaces (line 92) | def test_spaces(self):
  class TestExecution_env (line 102) | class TestExecution_env(ExecutionTestCase):
    method setUp (line 104) | def setUp(self):
    method test_env (line 124) | def test_env(self):
    method test_env_with_spaces (line 133) | def test_env_with_spaces(self):
    method test_env_non_ascii (line 142) | def test_env_non_ascii(self):
    method test_env_write_to_file (line 152) | def test_env_write_to_file(self):
    method test_env_write_to_file_with_env_vars (line 172) | def test_env_write_to_file_with_env_vars(self):
    method test_env_non_ascii_and_write_to_file (line 191) | def test_env_non_ascii_and_write_to_file(self):
    method test_env_write_to_quoted_filename (line 212) | def test_env_write_to_quoted_filename(self):
    method test_env_append_to_file (line 232) | def test_env_append_to_file(self):
  class TestExecution_source_and_exec (line 254) | class TestExecution_source_and_exec(ExecutionTestCase):
    method setUp (line 256) | def setUp(self):
    method test_source (line 283) | def test_source(self):
    method test_source_with_spaces (line 305) | def test_source_with_spaces(self):
    method test_source_non_existing_file (line 327) | def test_source_non_existing_file(self):
    method test_source_quoted_filename (line 344) | def test_source_quoted_filename(self):
    method test_source_escaped_filename (line 368) | def test_source_escaped_filename(self):
    method test_exec (line 395) | def test_exec(self):
    method test_exec_with_spaces (line 412) | def test_exec_with_spaces(self):
    method test_exec_non_existing_file (line 429) | def test_exec_non_existing_file(self):
    method test_exec_quoted_filename (line 445) | def test_exec_quoted_filename(self):
    method test_exec_escaped_filename (line 464) | def test_exec_escaped_filename(self):
  class TestExecution_env_and_source (line 486) | class TestExecution_env_and_source(ExecutionTestCase):
    method test_env_and_source (line 488) | def test_env_and_source(self):
    method test_env_and_source_non_ascii (line 522) | def test_env_and_source_non_ascii(self):
  class TestExecution_help (line 557) | class TestExecution_help(ExecutionTestCase):
    method test_help (line 559) | def test_help(self):
    method test_help_with_spaces (line 563) | def test_help_with_spaces(self):
  class TestExecution_exit (line 568) | class TestExecution_exit(ExecutionTestCase):
    method test_exit (line 570) | def test_exit(self):
    method test_exit_with_spaces (line 574) | def test_exit_with_spaces(self):
  class TestExecution_cd (line 579) | class TestExecution_cd(ExecutionTestCase):
    method test_single_level (line 581) | def test_single_level(self):
    method test_many_levels (line 585) | def test_many_levels(self):
    method test_change_base (line 589) | def test_change_base(self):
    method test_root (line 593) | def test_root(self):
    method test_dot_dot (line 600) | def test_dot_dot(self):
    method test_url_with_trailing_slash (line 612) | def test_url_with_trailing_slash(self):
    method test_path_with_trailing_slash (line 623) | def test_path_with_trailing_slash(self):
    method test_without_url (line 630) | def test_without_url(self):
  class TestExecution_rm (line 638) | class TestExecution_rm(ExecutionTestCase):
    method test_header (line 640) | def test_header(self):
    method test_option (line 645) | def test_option(self):
    method test_querystring (line 650) | def test_querystring(self):
    method test_body_param (line 655) | def test_body_param(self):
    method test_body_json_param (line 660) | def test_body_json_param(self):
    method test_header_single_quoted (line 665) | def test_header_single_quoted(self):
    method test_option_double_quoted (line 670) | def test_option_double_quoted(self):
    method test_querystring_double_quoted (line 675) | def test_querystring_double_quoted(self):
    method test_body_param_double_quoted (line 680) | def test_body_param_double_quoted(self):
    method test_body_param_escaped (line 685) | def test_body_param_escaped(self):
    method test_body_json_param_escaped_colon (line 690) | def test_body_json_param_escaped_colon(self):
    method test_body_param_escaped_equal (line 695) | def test_body_param_escaped_equal(self):
    method test_non_existing_key (line 700) | def test_non_existing_key(self):
    method test_non_existing_key_unicode (line 704) | def test_non_existing_key_unicode(self):  # See #25
    method test_body_reset (line 708) | def test_body_reset(self):
    method test_querystring_reset (line 716) | def test_querystring_reset(self):
    method test_headers_reset (line 724) | def test_headers_reset(self):
    method test_options_reset (line 732) | def test_options_reset(self):
    method test_reset (line 740) | def test_reset(self):
  class TestExecution_ls (line 770) | class TestExecution_ls(ExecutionTestCase):
    method test_root (line 772) | def test_root(self):
    method test_relative_path (line 776) | def test_relative_path(self):
    method test_absolute_path (line 781) | def test_absolute_path(self):
    method test_redirect_write (line 786) | def test_redirect_write(self):
    method test_redirect_append (line 799) | def test_redirect_append(self):
    method test_grep (line 812) | def test_grep(self):
  class TestMutation (line 817) | class TestMutation(ExecutionTestCase):
    method test_simple_headers (line 819) | def test_simple_headers(self):
    method test_header_value_with_double_quotes (line 826) | def test_header_value_with_double_quotes(self):
    method test_header_value_with_single_quotes (line 833) | def test_header_value_with_single_quotes(self):
    method test_header_with_double_quotes (line 840) | def test_header_with_double_quotes(self):
    method test_header_with_single_quotes (line 847) | def test_header_with_single_quotes(self):
    method test_header_escaped_chars (line 854) | def test_header_escaped_chars(self):
    method test_header_value_escaped_quote (line 860) | def test_header_value_escaped_quote(self):
    method test_simple_querystring (line 866) | def test_simple_querystring(self):
    method test_querystring_with_double_quotes (line 873) | def test_querystring_with_double_quotes(self):
    method test_querystring_with_single_quotes (line 880) | def test_querystring_with_single_quotes(self):
    method test_querystring_with_chinese (line 887) | def test_querystring_with_chinese(self):
    method test_querystring_escaped_chars (line 893) | def test_querystring_escaped_chars(self):
    method test_querytstring_value_escaped_quote (line 899) | def test_querytstring_value_escaped_quote(self):
    method test_querystring_key_escaped_quote (line 905) | def test_querystring_key_escaped_quote(self):
    method test_simple_body_params (line 911) | def test_simple_body_params(self):
    method test_body_param_value_with_double_quotes (line 918) | def test_body_param_value_with_double_quotes(self):
    method test_body_param_value_with_single_quotes (line 925) | def test_body_param_value_with_single_quotes(self):
    method test_body_param_with_double_quotes (line 932) | def test_body_param_with_double_quotes(self):
    method test_body_param_with_spanish (line 939) | def test_body_param_with_spanish(self):
    method test_body_param_escaped_chars (line 945) | def test_body_param_escaped_chars(self):
    method test_body_param_value_escaped_quote (line 951) | def test_body_param_value_escaped_quote(self):
    method test_body_param_key_escaped_quote (line 957) | def test_body_param_key_escaped_quote(self):
    method test_long_option_names (line 963) | def test_long_option_names(self):
    method test_long_option_names_with_its_prefix (line 970) | def test_long_option_names_with_its_prefix(self):
    method test_long_short_option_names_mixed (line 980) | def test_long_short_option_names_mixed(self):
    method test_option_and_body_param (line 988) | def test_option_and_body_param(self):
    method test_mixed (line 997) | def test_mixed(self):
    method test_multi_querystring (line 1018) | def test_multi_querystring(self):
    method test_raw_json_object (line 1029) | def test_raw_json_object(self):
    method test_raw_json_object_quoted (line 1038) | def test_raw_json_object_quoted(self):
    method test_raw_json_array (line 1047) | def test_raw_json_array(self):
    method test_raw_json_array_quoted (line 1053) | def test_raw_json_array_quoted(self):
    method test_raw_json_integer (line 1059) | def test_raw_json_integer(self):
    method test_raw_json_string (line 1063) | def test_raw_json_string(self):
    method test_escape_colon (line 1067) | def test_escape_colon(self):
    method test_escape_equal (line 1073) | def test_escape_equal(self):
  class TestHttpAction (line 1080) | class TestHttpAction(ExecutionTestCase):
    method test_get (line 1082) | def test_get(self):
    method test_get_uppercase (line 1086) | def test_get_uppercase(self):
    method test_get_multi_querystring (line 1090) | def test_get_multi_querystring(self):
    method test_post (line 1095) | def test_post(self):
    method test_post_with_absolute_path (line 1101) | def test_post_with_absolute_path(self):
    method test_post_with_relative_path (line 1108) | def test_post_with_relative_path(self):
    method test_post_with_full_url (line 1116) | def test_post_with_full_url(self):
    method test_post_with_full_https_url (line 1123) | def test_post_with_full_https_url(self):
    method test_post_uppercase (line 1130) | def test_post_uppercase(self):
    method test_post_raw_json_object (line 1136) | def test_post_raw_json_object(self):
    method test_post_raw_json_object_quoted (line 1144) | def test_post_raw_json_object_quoted(self):
    method test_post_raw_json_array (line 1152) | def test_post_raw_json_array(self):
    method test_post_raw_json_array_quoted (line 1160) | def test_post_raw_json_array_quoted(self):
    method test_post_raw_json_integer (line 1168) | def test_post_raw_json_integer(self):
    method test_post_raw_json_boolean (line 1175) | def test_post_raw_json_boolean(self):
    method test_delete (line 1182) | def test_delete(self):
    method test_delete_uppercase (line 1186) | def test_delete_uppercase(self):
    method test_patch (line 1190) | def test_patch(self):
    method test_patch_uppercase (line 1194) | def test_patch_uppercase(self):
    method test_head (line 1198) | def test_head(self):
    method test_head_uppercase (line 1202) | def test_head_uppercase(self):
    method test_options (line 1206) | def test_options(self):
  class TestHttpActionRedirection (line 1211) | class TestHttpActionRedirection(ExecutionTestCase):
    method test_get (line 1213) | def test_get(self):
  class TestHttpBin (line 1223) | class TestHttpBin(TempAppDirTestCase):
    method setUp (line 1228) | def setUp(self):
    method tearDown (line 1254) | def tearDown(self):
    method get_stdout (line 1262) | def get_stdout(self):
    method execute_redirection (line 1265) | def execute_redirection(self, command):
    method execute_pipe (line 1273) | def execute_pipe(self, command):
    method test_get_image (line 1277) | def test_get_image(self):
    method test_get_querystring (line 1283) | def test_get_querystring(self):
    method test_post_json (line 1292) | def test_post_json(self):
    method test_post_form (line 1301) | def test_post_form(self):
    method test_get_and_tee (line 1311) | def test_get_and_tee(self):
  class TestCommandPreview (line 1324) | class TestCommandPreview(ExecutionTestCase):
    method test_httpie_without_args (line 1326) | def test_httpie_without_args(self):
    method test_httpie_with_post (line 1330) | def test_httpie_with_post(self):
    method test_httpie_with_absolute_path (line 1335) | def test_httpie_with_absolute_path(self):
    method test_httpie_with_full_url (line 1340) | def test_httpie_with_full_url(self):
    method test_httpie_with_full_https_url (line 1346) | def test_httpie_with_full_https_url(self):
    method test_httpie_with_quotes (line 1353) | def test_httpie_with_quotes(self):
    method test_httpie_with_multi_querystring (line 1365) | def test_httpie_with_multi_querystring(self):
  class TestPipe (line 1372) | class TestPipe(ExecutionTestCase):
    method test_httpie_sed (line 1375) | def test_httpie_sed(self):
    method test_httpie_sed_with_echo (line 1380) | def test_httpie_sed_with_echo(self):
    method test_env_grep (line 1386) | def test_env_grep(self):
  class TestShellSubstitution (line 1396) | class TestShellSubstitution(ExecutionTestCase):
    method test_unquoted_option (line 1398) | def test_unquoted_option(self):
    method test_partial_unquoted_option (line 1404) | def test_partial_unquoted_option(self):
    method test_partial_squoted_option (line 1410) | def test_partial_squoted_option(self):
    method test_partial_dquoted_option (line 1416) | def test_partial_dquoted_option(self):
    method test_unquoted_header (line 1422) | def test_unquoted_header(self):
    method test_full_squoted_header (line 1435) | def test_full_squoted_header(self):
    method test_full_dquoted_header (line 1441) | def test_full_dquoted_header(self):
    method test_value_squoted_header (line 1447) | def test_value_squoted_header(self):
    method test_value_dquoted_header (line 1453) | def test_value_dquoted_header(self):
    method test_partial_value_dquoted_header (line 1459) | def test_partial_value_dquoted_header(self):
    method test_partial_full_dquoted_header (line 1465) | def test_partial_full_dquoted_header(self):
    method test_unquoted_querystring (line 1471) | def test_unquoted_querystring(self):
    method test_full_squoted_querystring (line 1479) | def test_full_squoted_querystring(self):
    method test_value_squoted_querystring (line 1485) | def test_value_squoted_querystring(self):
    method test_value_dquoted_querystring (line 1491) | def test_value_dquoted_querystring(self):
    method test_unquoted_body_param (line 1497) | def test_unquoted_body_param(self):
    method test_full_squoted_body_param (line 1505) | def test_full_squoted_body_param(self):
    method test_value_squoted_body_param (line 1511) | def test_value_squoted_body_param(self):
    method test_full_dquoted_body_param (line 1517) | def test_full_dquoted_body_param(self):
    method test_bad_command (line 1523) | def test_bad_command(self):
    method test_pipe_and_grep (line 1528) | def test_pipe_and_grep(self):
  class TestCommandPreviewRedirection (line 1536) | class TestCommandPreviewRedirection(ExecutionTestCase):
    method test_httpie_redirect_write (line 1538) | def test_httpie_redirect_write(self):
    method test_httpie_redirect_write_quoted_filename (line 1551) | def test_httpie_redirect_write_quoted_filename(self):
    method test_httpie_redirect_write_escaped_filename (line 1566) | def test_httpie_redirect_write_escaped_filename(self):
    method test_httpie_redirect_write_with_args (line 1580) | def test_httpie_redirect_write_with_args(self):
    method test_httpie_redirect_append (line 1594) | def test_httpie_redirect_append(self):
    method test_httpie_redirect_append_without_spaces (line 1607) | def test_httpie_redirect_append_without_spaces(self):
    method test_httpie_redirect_append_quoted_filename (line 1620) | def test_httpie_redirect_append_quoted_filename(self):

FILE: tests/test_installation.py
  function run_http_prompt (line 13) | def run_http_prompt(args):
  function test_help (line 21) | def test_help():
  function test_version (line 27) | def test_version():

FILE: tests/test_interaction.py
  class TestInteraction (line 12) | class TestInteraction(TempAppDirTestCase):
    method setUp (line 14) | def setUp(self):
    method tearDown (line 26) | def tearDown(self):
    method write_config (line 36) | def write_config(self, content):
    method test_interaction (line 44) | def test_interaction(self):
    method test_vi_mode (line 57) | def test_vi_mode(self):

FILE: tests/test_lexer.py
  class LexerTestCase (line 8) | class LexerTestCase(unittest.TestCase):
    method setUp (line 10) | def setUp(self):
    method get_tokens (line 13) | def get_tokens(self, text, filter_spaces=True):
  class TestLexer_mutation (line 21) | class TestLexer_mutation(LexerTestCase):
    method test_querystring (line 23) | def test_querystring(self):
    method test_body_param (line 30) | def test_body_param(self):
    method test_header (line 37) | def test_header(self):
    method test_json_integer (line 44) | def test_json_integer(self):
    method test_json_boolean (line 51) | def test_json_boolean(self):
    method test_json_string (line 58) | def test_json_string(self):
    method test_json_array (line 67) | def test_json_array(self):
    method test_json_array_quoted (line 74) | def test_json_array_quoted(self):
    method test_json_object (line 83) | def test_json_object(self):
    method test_json_object_quoted (line 90) | def test_json_object_quoted(self):
    method test_json_escaped_colon (line 99) | def test_json_escaped_colon(self):
    method test_body_param_escaped_equal (line 106) | def test_body_param_escaped_equal(self):
    method test_parameter_name_including_http_method_name (line 113) | def test_parameter_name_including_http_method_name(self):
  class TestLexer_cd (line 121) | class TestLexer_cd(LexerTestCase):
    method test_simple (line 123) | def test_simple(self):
    method test_double_quoted (line 129) | def test_double_quoted(self):
    method test_single_quoted (line 137) | def test_single_quoted(self):
    method test_escape (line 145) | def test_escape(self):
    method test_second_path (line 151) | def test_second_path(self):
    method test_leading_trailing_spaces (line 159) | def test_leading_trailing_spaces(self):
  class TestLexer_ls (line 166) | class TestLexer_ls(LexerTestCase):
    method test_no_path (line 168) | def test_no_path(self):
    method test_path (line 173) | def test_path(self):
    method test_second_path (line 179) | def test_second_path(self):
    method test_leading_trailing_spaces (line 187) | def test_leading_trailing_spaces(self):
    method test_redirect (line 193) | def test_redirect(self):
  class TestLexer_env (line 202) | class TestLexer_env(LexerTestCase):
    method test_env_simple (line 204) | def test_env_simple(self):
    method test_env_with_spaces (line 209) | def test_env_with_spaces(self):
    method test_env_write (line 214) | def test_env_write(self):
    method test_env_append (line 220) | def test_env_append(self):
    method test_env_write_quoted_filename (line 226) | def test_env_write_quoted_filename(self):
    method test_env_append_escaped_filename (line 232) | def test_env_append_escaped_filename(self):
    method test_env_pipe (line 238) | def test_env_pipe(self):
  class TestLexer_rm (line 245) | class TestLexer_rm(LexerTestCase):
    method test_header (line 247) | def test_header(self):
    method test_header_escaped (line 254) | def test_header_escaped(self):
    method test_querystring (line 261) | def test_querystring(self):
    method test_querystring_double_quoted (line 268) | def test_querystring_double_quoted(self):
    method test_body_param (line 277) | def test_body_param(self):
    method test_body_param_single_quoted (line 284) | def test_body_param_single_quoted(self):
    method test_option (line 293) | def test_option(self):
    method test_reset (line 300) | def test_reset(self):
    method test_option_leading_trailing_spaces (line 306) | def test_option_leading_trailing_spaces(self):
    method test_invalid_type (line 313) | def test_invalid_type(self):
  class TestLexer_help (line 321) | class TestLexer_help(LexerTestCase):
    method test_help_simple (line 323) | def test_help_simple(self):
    method test_help_with_spaces (line 328) | def test_help_with_spaces(self):
  class TestLexer_source (line 334) | class TestLexer_source(LexerTestCase):
    method test_source_simple_filename (line 336) | def test_source_simple_filename(self):
    method test_source_with_spaces (line 341) | def test_source_with_spaces(self):
    method test_source_quoted_filename (line 346) | def test_source_quoted_filename(self):
    method test_source_escaped_filename (line 352) | def test_source_escaped_filename(self):
  class TestLexer_exec (line 358) | class TestLexer_exec(LexerTestCase):
    method test_exec_simple_filename (line 360) | def test_exec_simple_filename(self):
    method test_exec_with_spaces (line 365) | def test_exec_with_spaces(self):
    method test_exec_quoted_filename (line 370) | def test_exec_quoted_filename(self):
    method test_exec_escaped_filename (line 376) | def test_exec_escaped_filename(self):
  class TestLexer_exit (line 382) | class TestLexer_exit(LexerTestCase):
    method test_exit_simple (line 384) | def test_exit_simple(self):
    method test_exit_with_spaces (line 389) | def test_exit_with_spaces(self):
  class TestLexerPreview (line 395) | class TestLexerPreview(LexerTestCase):
    method test_httpie_without_action (line 397) | def test_httpie_without_action(self):
    method test_httpie_without_action_and_url (line 405) | def test_httpie_without_action_and_url(self):
    method test_httpie_absolute_url (line 413) | def test_httpie_absolute_url(self):
    method test_httpie_option_first (line 421) | def test_httpie_option_first(self):
    method test_httpie_body_param_first (line 428) | def test_httpie_body_param_first(self):
    method test_httpie_options (line 435) | def test_httpie_options(self):
    method test_httpie_relative_path (line 441) | def test_httpie_relative_path(self):
  class TestShellCode (line 452) | class TestShellCode(LexerTestCase):
    method test_unquoted_querystring (line 454) | def test_unquoted_querystring(self):
    method test_unquoted_bodystring (line 472) | def test_unquoted_bodystring(self):
    method test_header_option_value (line 490) | def test_header_option_value(self):
    method test_httpie_body_param (line 500) | def test_httpie_body_param(self):
    method test_httpie_post_pipe (line 512) | def test_httpie_post_pipe(self):
    method test_post_pipe (line 521) | def test_post_pipe(self):
  class TestLexerPreviewRedirection (line 530) | class TestLexerPreviewRedirection(LexerTestCase):
    method test_httpie_write (line 532) | def test_httpie_write(self):
    method test_httpie_write_without_spaces (line 538) | def test_httpie_write_without_spaces(self):
    method test_httpie_append (line 544) | def test_httpie_append(self):
    method test_httpie_append_without_spaces (line 550) | def test_httpie_append_without_spaces(self):
    method test_httpie_write_with_post_param (line 556) | def test_httpie_write_with_post_param(self):
    method test_httpie_append_with_post_param (line 563) | def test_httpie_append_with_post_param(self):
    method test_httpie_write_quoted_filename (line 570) | def test_httpie_write_quoted_filename(self):
    method test_httpie_append_quoted_filename (line 576) | def test_httpie_append_quoted_filename(self):
    method test_httpie_append_with_many_params (line 582) | def test_httpie_append_with_many_params(self):
    method test_curl_write (line 595) | def test_curl_write(self):
    method test_curl_write_without_spaces (line 601) | def test_curl_write_without_spaces(self):
    method test_curl_append (line 607) | def test_curl_append(self):
    method test_curl_append_without_spaces (line 613) | def test_curl_append_without_spaces(self):
    method test_curl_write_with_post_param (line 619) | def test_curl_write_with_post_param(self):
    method test_curl_append_with_post_param (line 626) | def test_curl_append_with_post_param(self):
    method test_curl_write_quoted_filename (line 633) | def test_curl_write_quoted_filename(self):
    method test_curl_append_quoted_filename (line 639) | def test_curl_append_quoted_filename(self):
    method test_curl_append_with_many_params (line 645) | def test_curl_append_with_many_params(self):
  class TestLexerAction (line 659) | class TestLexerAction(LexerTestCase):
    method test_get (line 661) | def test_get(self):
    method test_post_with_spaces (line 666) | def test_post_with_spaces(self):
    method test_capital_head (line 671) | def test_capital_head(self):
    method test_delete_random_capitals (line 676) | def test_delete_random_capitals(self):
    method test_patch (line 681) | def test_patch(self):
    method test_get_with_querystring_params (line 686) | def test_get_with_querystring_params(self):
    method test_capital_get_with_querystring_params (line 694) | def test_capital_get_with_querystring_params(self):
    method test_post_with_body_params (line 702) | def test_post_with_body_params(self):
    method test_post_with_spaces_and_body_params (line 710) | def test_post_with_spaces_and_body_params(self):
    method test_options (line 718) | def test_options(self):
    method test_post_relative_path (line 723) | def test_post_relative_path(self):
  class TestLexerActionRedirection (line 734) | class TestLexerActionRedirection(LexerTestCase):
    method test_get_write (line 736) | def test_get_write(self):
    method test_get_write_quoted_filename (line 741) | def test_get_write_quoted_filename(self):
    method test_get_append (line 747) | def test_get_append(self):
    method test_get_append_escaped_filename (line 752) | def test_get_append_escaped_filename(self):
    method test_post_append_with_spaces (line 758) | def test_post_append_with_spaces(self):
    method test_capital_head_write (line 763) | def test_capital_head_write(self):
    method test_get_append_with_querystring_params (line 768) | def test_get_append_with_querystring_params(self):
    method test_post_write_escaped_filename_with_body_params (line 777) | def test_post_write_escaped_filename_with_body_params(self):
    method test_post_append_with_spaces_and_body_params (line 786) | def test_post_append_with_spaces_and_body_params(self):

FILE: tests/test_tree.py
  class TestNode (line 6) | class TestNode(unittest.TestCase):
    method setUp (line 8) | def setUp(self):
    method test_illegal_name (line 25) | def test_illegal_name(self):
    method test_str (line 29) | def test_str(self):
    method test_cmp_same_type (line 33) | def test_cmp_same_type(self):
    method test_cmp_different_type (line 38) | def test_cmp_different_type(self):
    method test_eq (line 43) | def test_eq(self):
    method test_add_path_and_find_child (line 52) | def test_add_path_and_find_child(self):
    method test_find_child_wildcard (line 77) | def test_find_child_wildcard(self):
    method test_ls (line 88) | def test_ls(self):
    method test_ls_root (line 95) | def test_ls_root(self):
    method test_ls_non_existing (line 98) | def test_ls_non_existing(self):
    method test_ls_parent (line 102) | def test_ls_parent(self):
    method test_ls_dot (line 112) | def test_ls_dot(self):
    method test_ls_sort_by_types (line 123) | def test_ls_sort_by_types(self):

FILE: tests/test_utils.py
  function test_colformat_zero_items (line 4) | def test_colformat_zero_items():
  function test_colformat_one_item (line 8) | def test_colformat_one_item():
  function test_colformat_single_line (line 12) | def test_colformat_single_line():
  function test_colformat_single_column (line 19) | def test_colformat_single_column():
  function test_colformat_multi_columns_no_remainder (line 28) | def test_colformat_multi_columns_no_remainder():
  function test_colformat_multi_columns_remainder_1 (line 39) | def test_colformat_multi_columns_remainder_1():
  function test_colformat_multi_columns_remainder_2 (line 52) | def test_colformat_multi_columns_remainder_2():
  function test_colformat_wider_than_terminal (line 65) | def test_colformat_wider_than_terminal():
  function test_colformat_long_short_mixed (line 73) | def test_colformat_long_short_mixed():
  function test_colformat_github_top_endpoints (line 83) | def test_colformat_github_top_endpoints():

FILE: tests/test_xdg.py
  class TestXDG (line 9) | class TestXDG(TempAppDirTestCase):
    method test_get_app_data_home (line 11) | def test_get_app_data_home(self):
    method test_get_app_config_home (line 25) | def test_get_app_config_home(self):
    method test_get_resource_data_dir (line 39) | def test_get_resource_data_dir(self):
    method test_get_resource_config_dir (line 50) | def test_get_resource_config_dir(self):

FILE: tests/utils.py
  function get_http_prompt_path (line 5) | def get_http_prompt_path():
Condensed preview — 54 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (238K chars).
[
  {
    "path": ".coveragerc",
    "chars": 57,
    "preview": "[report]\nshow_missing = True\nexclude_lines =\n    nocover\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 553,
    "preview": "name: Build\non: [push, pull_request]\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os:"
  },
  {
    "path": ".gitignore",
    "chars": 99,
    "preview": "*.egg-info\n*.pyc\n.cache\n.coverage\n.DS_Store\n.python-version\n.tox\n_build\nbuild\ndist\ndata.json\nvenv*\n"
  },
  {
    "path": "LICENSE",
    "chars": 1078,
    "preview": "MIT License\n\nCopyright (c) 2016-2021 Chang-Hung Liang\n\nPermission is hereby granted, free of charge, to any person obtai"
  },
  {
    "path": "MANIFEST.in",
    "chars": 66,
    "preview": "include README.rst LICENSE requirements.txt requirements-test.txt\n"
  },
  {
    "path": "Makefile",
    "chars": 320,
    "preview": ".PHONY: build\n\ninstall:\n\tpython -m pip install -e .\n\tpython -m pip install -r requirements-test.txt\n\nclean:\n\trm -rf dist"
  },
  {
    "path": "README.rst",
    "chars": 1378,
    "preview": "HTTP Prompt\n===========\n\n|PyPI| |Docs| |Build| |Coverage| |Discord|\n\nHTTP Prompt is an interactive command-line HTTP cli"
  },
  {
    "path": "docs/LICENSE",
    "chars": 74,
    "preview": "Refer to LICENSE in the main repo:\n\nhttps://github.com/httpie/http-prompt\n"
  },
  {
    "path": "docs/Makefile",
    "chars": 607,
    "preview": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHI"
  },
  {
    "path": "docs/README.md",
    "chars": 308,
    "preview": "# HTTP Prompt Documentation\n\nThis repo contains the documentation for HTTP Prompt, published on\nhttp://docs.http-prompt."
  },
  {
    "path": "docs/_templates/layout.html",
    "chars": 30,
    "preview": "{%- extends \"!layout.html\" %}\n"
  },
  {
    "path": "docs/conf.py",
    "chars": 5185,
    "preview": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# HTTP Prompt documentation build configuration file, created by\n# sphi"
  },
  {
    "path": "docs/contributor-guide.rst",
    "chars": 7320,
    "preview": ".. _contributor-guide:\n\nContributor Guide\n=================\n\nThis document is for developers who want to contribute code"
  },
  {
    "path": "docs/index.rst",
    "chars": 1668,
    "preview": "HTTP Prompt Documentation\n=========================\n\nHTTP Prompt is an interactive command-line HTTP client featuring au"
  },
  {
    "path": "docs/make.bat",
    "chars": 814,
    "preview": "@ECHO OFF\r\n\r\npushd %~dp0\r\n\r\nREM Command file for Sphinx documentation\r\n\r\nif \"%SPHINXBUILD%\" == \"\" (\r\n\tset SPHINXBUILD=sp"
  },
  {
    "path": "docs/user-guide.rst",
    "chars": 10537,
    "preview": ".. _user-guide:\n\nUser Guide\n==========\n\nInstallation\n------------\n\nJust install it like a regular Python package::\n\n    "
  },
  {
    "path": "http_prompt/__init__.py",
    "chars": 22,
    "preview": "__version__ = '2.1.0'\n"
  },
  {
    "path": "http_prompt/cli.py",
    "chars": 5433,
    "preview": "import json\nfrom http.cookies import SimpleCookie\nfrom urllib.request import pathname2url, urlopen\n\nimport yaml\nimport o"
  },
  {
    "path": "http_prompt/completer.py",
    "chars": 6339,
    "preview": "# -*- coding: utf-8 -*-\nimport re\n\nfrom collections import OrderedDict\n\nfrom itertools import chain\nfrom urllib.parse im"
  },
  {
    "path": "http_prompt/completion.py",
    "chars": 4686,
    "preview": "\"\"\"Meta data for autocomplete.\"\"\"\n\nfrom collections import OrderedDict\n\nfrom . import options as opt\n\n\nROOT_COMMANDS = O"
  },
  {
    "path": "http_prompt/config.py",
    "chars": 1780,
    "preview": "\"\"\"Functions that deal with the user configuration.\"\"\"\n\nimport os\nimport shutil\n\nfrom . import defaultconfig\nfrom . impo"
  },
  {
    "path": "http_prompt/context/__init__.py",
    "chars": 4872,
    "preview": "from http_prompt.tree import Node\n\n\nclass Context(object):\n\n    def __init__(self, url=None, spec=None):\n        self.ur"
  },
  {
    "path": "http_prompt/context/transform.py",
    "chars": 3382,
    "preview": "\"\"\"Functions that transform a Context object to a different representation.\"\"\"\n\nimport json\n\nfrom http_prompt.utils impo"
  },
  {
    "path": "http_prompt/contextio.py",
    "chars": 1118,
    "preview": "\"\"\"Serialization and deserialization of a Context object.\"\"\"\n\nimport io\nimport os\n\nfrom . import xdg\nfrom .context.trans"
  },
  {
    "path": "http_prompt/defaultconfig.py",
    "chars": 1180,
    "preview": "# Highlighting style for prompt commands. Available values:\n# algol, algol_nu, autumn, borland, bw, colorful, default, e"
  },
  {
    "path": "http_prompt/execution.py",
    "chars": 20121,
    "preview": "import io\nimport json\nimport re\nimport os\nimport sys\nfrom urllib.parse import urlparse, urljoin\n\nimport click\n\nfrom subp"
  },
  {
    "path": "http_prompt/lexer.py",
    "chars": 6018,
    "preview": "from pygments.lexer import (RegexLexer, bygroups, words, using, include,\n                            combined)\nfrom pygm"
  },
  {
    "path": "http_prompt/options.py",
    "chars": 2656,
    "preview": "\"\"\"Meta data for HTTPie options.\"\"\"\n\nFLAG_OPTIONS = [\n    ('--body', 'Print only response body'),\n    ('--check-status',"
  },
  {
    "path": "http_prompt/output.py",
    "chars": 1100,
    "preview": "import sys\n\nimport click\n\n\nclass Printer(object):\n    \"\"\"Wrap click.echo_via_pager() so it accepts binary data.\"\"\"\n\n    "
  },
  {
    "path": "http_prompt/tree.py",
    "chars": 2268,
    "preview": "\"\"\"Tree data structure for ls command to work with OpenAPI specification.\"\"\"\n\n\nclass Node(object):\n\n    def __init__(sel"
  },
  {
    "path": "http_prompt/utils.py",
    "chars": 2069,
    "preview": "import math\nimport re\nimport shlex\n\nfrom prompt_toolkit.output.defaults import create_output\n\n\nRE_ANSI_ESCAPE = re.compi"
  },
  {
    "path": "http_prompt/xdg.py",
    "chars": 1219,
    "preview": "\"\"\"XDG Base Directory Specification.\n\nSee:\n    https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.h"
  },
  {
    "path": "requirements-test.txt",
    "chars": 58,
    "preview": "pexpect>=4.2.1\npytest>=3.0.6\npytest-cov>=2.4.0\nwheel\ntwine"
  },
  {
    "path": "requirements.txt",
    "chars": 102,
    "preview": "click>=5.0\nhttpie>=2.5.0\nparsimonious>=0.6.2\nprompt-toolkit>=2.0.0,<3.0.0\nPygments>=2.1.0\nPyYAML>=3.0\n"
  },
  {
    "path": "setup.cfg",
    "chars": 22,
    "preview": "[wheel]\nuniversal = 1\n"
  },
  {
    "path": "setup.py",
    "chars": 2387,
    "preview": "import os\nimport re\nfrom setuptools import setup\n\n\nhere = os.path.abspath(os.path.dirname(__file__))\n\n\n# Read the versio"
  },
  {
    "path": "snap/snapcraft.yaml",
    "chars": 923,
    "preview": "name: http-prompt\nsummary: Interactive command-line HTTP client\ndescription: |\n    HTTP Prompt is an interactive command"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/base.py",
    "chars": 1836,
    "preview": "import os\nimport shutil\nimport sys\nimport tempfile\nimport unittest\n\n\nclass TempAppDirTestCase(unittest.TestCase):\n    \"\""
  },
  {
    "path": "tests/context/test_context.py",
    "chars": 5024,
    "preview": "from http_prompt.context import Context\n\n\ndef test_creation():\n    context = Context('http://example.com')\n    assert co"
  },
  {
    "path": "tests/context/test_transform.py",
    "chars": 4738,
    "preview": "from http_prompt.context import Context\nfrom http_prompt.context import transform as t\n\n\ndef test_extract_args_for_httpi"
  },
  {
    "path": "tests/test_cli.py",
    "chars": 13258,
    "preview": "import json\nimport os\nimport sys\nimport unittest\nfrom unittest.mock import patch, DEFAULT\n\nfrom click.testing import Cli"
  },
  {
    "path": "tests/test_completer.py",
    "chars": 4461,
    "preview": "# -*- coding: utf-8 -*-\nimport unittest\n\nfrom prompt_toolkit.document import Document\n\nfrom http_prompt.completer import"
  },
  {
    "path": "tests/test_config.py",
    "chars": 2237,
    "preview": "import hashlib\nimport os\n\nfrom .base import TempAppDirTestCase\nfrom http_prompt import config\n\n\ndef _hash_file(path):\n  "
  },
  {
    "path": "tests/test_contextio.py",
    "chars": 655,
    "preview": "# -*- coding: utf-8 -*-\nfrom .base import TempAppDirTestCase\nfrom http_prompt.context import Context\nfrom http_prompt.co"
  },
  {
    "path": "tests/test_execution.py",
    "chars": 56172,
    "preview": "# -*- coding: utf-8 -*-\nimport hashlib\nimport io\nimport json\nimport shutil\nimport os\nimport sys\n\nimport pytest\n\nfrom col"
  },
  {
    "path": "tests/test_installation.py",
    "chars": 747,
    "preview": "\"\"\"Test if http-prompt is installed correctly.\"\"\"\n\nimport subprocess\n\nimport pytest\n\nfrom subprocess import PIPE\n\nfrom ."
  },
  {
    "path": "tests/test_interaction.py",
    "chars": 2285,
    "preview": "import os\nimport sys\n\nimport pexpect\nimport pytest\n\nfrom .base import TempAppDirTestCase\nfrom .utils import get_http_pro"
  },
  {
    "path": "tests/test_lexer.py",
    "chars": 25723,
    "preview": "import unittest\n\nfrom pygments.token import Keyword, String, Text, Error, Name, Operator\n\nfrom http_prompt.lexer import "
  },
  {
    "path": "tests/test_tree.py",
    "chars": 4871,
    "preview": "import unittest\n\nfrom http_prompt.tree import Node\n\n\nclass TestNode(unittest.TestCase):\n\n    def setUp(self):\n        # "
  },
  {
    "path": "tests/test_utils.py",
    "chars": 3637,
    "preview": "from http_prompt import utils\n\n\ndef test_colformat_zero_items():\n    assert list(utils.colformat([], terminal_width=80))"
  },
  {
    "path": "tests/test_xdg.py",
    "chars": 2194,
    "preview": "import os\nimport stat\nimport sys\n\nfrom .base import TempAppDirTestCase\nfrom http_prompt import xdg\n\n\nclass TestXDG(TempA"
  },
  {
    "path": "tests/utils.py",
    "chars": 623,
    "preview": "import os\nimport sys\n\n\ndef get_http_prompt_path():\n    \"\"\"Get the path to http-prompt executable.\"\"\"\n    python_dir = os"
  },
  {
    "path": "tox.ini",
    "chars": 484,
    "preview": "# Tox (http://tox.testrun.org/) is a tool for running tests\n# in multiple virtualenvs. This configuration file will run "
  }
]

About this extraction

This page contains the full source code of the httpie/http-prompt GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 54 files (221.5 KB), approximately 54.5k tokens, and a symbol index with 576 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!