Compare commits

...

39 Commits
0.1.4 ... 0.1.6

Author SHA1 Message Date
c446d756ab Fixed IOError in setup.py. 2012-03-04 13:47:09 +01:00
7ca6191902 v0.1.5 2012-03-04 13:33:18 +01:00
ebb271334b Corrected line breaks in the output. 2012-03-04 13:03:21 +01:00
bd9209f77a Fixed --pretty tests. 2012-03-04 12:23:11 +01:00
7d629b4d94 Fixed tests II. 2012-03-04 12:02:30 +01:00
a44ef6c443 Fixed tests. 2012-03-04 11:58:23 +01:00
f4dde9d9b3 Updated travis config. 2012-03-04 11:54:27 +01:00
6d14097844 Added travis-ci configuration. 2012-03-04 11:31:37 +01:00
8a4f501706 Fixed tests for Python 2.6. 2012-03-04 11:22:21 +01:00
6774998012 Updated README. 2012-03-04 11:22:09 +01:00
2195280a70 Added argument parsing tests. 2012-03-04 10:50:23 +01:00
f5d5ec22af Added --version. 2012-03-04 10:49:37 +01:00
b728710760 Factored out CLI parsing. 2012-03-04 10:49:17 +01:00
715e1b1047 Improved setup.py 2012-03-04 03:36:17 +01:00
ca8779d879 Merge branch 'main-module-convention' of https://github.com/gandaro/httpie into gandaro-main-module-convention
Renamed httpie.httpie to httpie.__main__ so that one can invoke it via python -m httpie.
2012-03-04 03:13:50 +01:00
5ff43b659f Fixed README. 2012-03-04 02:48:31 +01:00
b802f2b960 Added field-name:=raw-json
Closes #14
2012-03-04 02:44:30 +01:00
73d0f9cd56 Updated readme (--flags). 2012-03-04 01:59:00 +01:00
00312ead28 Refactored --pretty and added tests.
#16
2012-03-04 01:54:28 +01:00
d02ac54130 Merge pull request #16 from tictactix/master
Added a way to force pretty printing
2012-03-03 16:17:41 -08:00
81568cf91c Merge pull request #19 from faulkner/fix-readme
A few minor corrections to the README.
2012-03-03 15:37:02 -08:00
6df9ff67eb Merge pull request #17 from faulkner/fix-redirects
Pass allow_redirects to request so --allow-redirects works.
2012-03-03 15:36:43 -08:00
5d3176115a Correct usage string in README. 2012-03-03 14:12:49 -08:00
81798ad537 Typo. 2012-03-03 14:09:13 -08:00
dd8faecbf7 Pass allow_redirects to request so --allow-redirects works. 2012-03-03 11:54:53 -08:00
58f74fe14a Force pretty printing (ignore last commit; stupid undo mistake) 2012-03-02 17:00:20 -05:00
84a0d4a35d Added forcing pretty printing for piping purposes. 2012-03-02 16:54:18 -05:00
d670513c9f use the __main__ submodule convention to make it possible to use python -m httpie 2012-03-02 18:35:33 +01:00
860a851a4b Fixed a missing line between headers and body. 2012-03-02 09:02:50 +01:00
9634dca7d8 Fixed a UnicodeError in Python 2.6. 2012-03-02 02:36:21 +01:00
bb653bf1a9 Added first tests. 2012-03-02 01:42:23 +01:00
94c605fac1 Added --style
Closes #6. Thanks, @iromli.
2012-03-02 01:39:22 +01:00
3442a5d037 Made argparse required only for Python < 2.7.
Closes #7.
2012-03-01 23:21:44 +01:00
5cd40916fe Merge pull request #12 from tomekwojcik/master
Added argparse to install_requires
2012-03-01 14:18:36 -08:00
ed3a491c81 Merge pull request #10 from marblar/osx
Support for terminals not using 256 color
2012-03-01 14:11:11 -08:00
d768959084 * Added argparse to install_requires. 2012-03-01 09:02:48 +01:00
f934f4345e Support for terminals not using 256 color
As documented in issue #8, the default terminal in OS X 10.6 is xterm-color, which does not support Formatter256Terminal
2012-02-29 15:39:56 -05:00
b752b59d92 remove unnecessary partial call 2012-02-29 21:35:20 +07:00
553941c98d added support to use other pygments styles, falback to solarized 2012-02-29 02:06:36 +07:00
9 changed files with 584 additions and 225 deletions

11
.travis.yml Normal file
View File

@ -0,0 +1,11 @@
language: python
python:
- 2.6
- 2.7
# TODO: Python 3
#- 3.2
script: python tests.py
install:
- pip install requests pygments
- "if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install argparse; fi"

View File

@ -1,13 +1,22 @@
## HTTPie: cURL for humans
HTTPie is a CLI frontend for [python-requests](http://python-requests.org) built out of frustration. It provides an `http` command that can be used to easily issue HTTP requests. It is meant to be used by humans to interact with HTTP-based APIs and web servers. The response headers are colorized and the body is syntax-highlighed if its `Content-Type` is known to [Pygments](http://pygments.org/) (unless the output is redirected).
[![Build Status](https://secure.travis-ci.org/jkbr/httpie.png)](http://travis-ci.org/jkbr/httpie)
HTTPie is a CLI frontend for [python-requests](http://python-requests.org) built out of frustration. It provides an `http` command that can be used to easily issue HTTP requests. It is meant to be used by humans to interact with HTTP-based APIs and web servers. The response headers are colorized and the body is syntax-highlighted if its `Content-Type` is known to [Pygments](http://pygments.org/) (unless the output is redirected).
![httpie](https://github.com/jkbr/httpie/raw/master/httpie.png)
### Installation
pip install httpie
Latest stable version using [pip](http://www.pip-installer.org/en/latest/index.html):
pip install -U httpie
# easy_install httpie
Master:
pip install -U https://github.com/jkbr/httpie/tarball/master
### Usage
@ -27,6 +36,14 @@ Will issue the following request:
{"name": "John", "email": "john@example.org"}
You can pass other types than just strings using the `field:=value` notation. It allows you to set arbitrary JSON to the data fields:
http PUT httpie.org/pies bool:=true list:=[1,2,3] 'object:={"a": "b", "c": "d"}'
Produces the following JSON request:
{"bool": true, "list": [1, 2, 3], "object": {"a": "b", "c": "d"}}
You can use the `--form` flag to set `Content-Type` and serialize the data as `application/x-www-form-urlencoded`.
The data to be sent can also be passed via `stdin`:
@ -35,33 +52,43 @@ The data to be sent can also be passed via `stdin`:
Most of the flags mirror the arguments you would use with `requests.request`. See `http -h`:
$ http -h
usage: http [-h] [--json | --form] [--traceback] [--ugly] [--headers | --body]
usage: http [-h] [--version] [--json | --form] [--traceback]
[--pretty | --ugly] [--headers | --body] [--style STYLE]
[--auth AUTH] [--verify VERIFY] [--proxy PROXY]
[--allow-redirects] [--file PATH] [--timeout TIMEOUT]
method URL [item [item ...]]
METHOD URL [items [items ...]]
HTTPie - cURL for humans.
positional arguments:
method HTTP method to be used for the request (GET, POST,
METHOD HTTP method to be used for the request (GET, POST,
PUT, DELETE, PATCH, ...).
URL Protocol defaults to http:// if the URL does not
include it.
item HTTP header (key:value) or data field (key=value)
items HTTP header (key:value), data field (key=value) or raw
JSON field (field:=value).
optional arguments:
-h, --help show this help message and exit
--version show program's version number and exit
--json, -j Serialize data items as a JSON object and set Content-
Type to application/json, if not specified.
--form, -f Serialize data items as form values and set Content-
Type to application/x-www-form-urlencoded, if not
specified.
--traceback Print a full exception traceback should one be raised
by `requests`.
--traceback Print exception traceback should one occur.
--pretty, -p If stdout is a terminal, the response is prettified by
default (colorized and indented if it is JSON). This
flag ensures prettifying even when stdout is
redirected.
--ugly, -u Do not prettify the response.
--headers, -t Print only the response headers.
--body, -b Print only the response body.
--style STYLE, -s STYLE
Output coloring style, one of autumn, borland, bw,
colorful, default, emacs, friendly, fruity, manni,
monokai, murphy, native, pastie, perldoc, solarized,
tango, trac, vim, vs. Defaults to solarized.
--auth AUTH, -a AUTH username:password
--verify VERIFY Set to "yes" to check the host's SSL certificate. You
can also pass the path to a CA_BUNDLE file for private
@ -75,3 +102,6 @@ Most of the flags mirror the arguments you would use with `requests.request`. Se
--timeout TIMEOUT Float describes the timeout of the request (Use
socket.setdefaulttimeout() as fallback).
### Changelog
* [0.1.6](https://github.com/jkbr/httpie/compare/0.1.4...0.1.6) (2012-03-04)

View File

@ -3,5 +3,5 @@ HTTPie - cURL for humans.
"""
__author__ = 'Jakub Roztocil'
__version__ = '0.1.4'
__version__ = '0.1.6'
__licence__ = 'BSD'

116
httpie/__main__.py Normal file
View File

@ -0,0 +1,116 @@
#!/usr/bin/env python
import os
import sys
import json
import requests
try:
from collections import OrderedDict
except ImportError:
OrderedDict = dict
from requests.structures import CaseInsensitiveDict
from . import cli
from . import pretty
from . import __version__ as version
DEFAULT_UA = 'HTTPie/%s' % version
TYPE_FORM = 'application/x-www-form-urlencoded; charset=utf-8'
TYPE_JSON = 'application/json; charset=utf-8'
def main(args=None,
stdin=sys.stdin,
stdin_isatty=sys.stdin.isatty(),
stdout=sys.stdout,
stdout_isatty=sys.stdout.isatty()):
parser = cli.parser
args = parser.parse_args(args if args is not None else sys.argv[1:])
do_prettify = (args.prettify is True or
(args.prettify == cli.PRETTIFY_STDOUT_TTY_ONLY and stdout_isatty))
# Parse request headers and data from the command line.
headers = CaseInsensitiveDict()
headers['User-Agent'] = DEFAULT_UA
data = OrderedDict()
try:
cli.parse_items(items=args.items, headers=headers, data=data)
except cli.ParseError as e:
if args.traceback:
raise
parser.error(e.message)
if not stdin_isatty:
if data:
parser.error('Request body (stdin) and request '
'data (key=value) cannot be mixed.')
data = stdin.read()
# JSON/Form content type.
if args.json or (not args.form and data):
if stdin_isatty:
data = json.dumps(data)
if 'Content-Type' not in headers and (data or args.json):
headers['Content-Type'] = TYPE_JSON
elif 'Content-Type' not in headers:
headers['Content-Type'] = TYPE_FORM
# Fire the request.
try:
response = requests.request(
method=args.method.lower(),
url=args.url if '://' in args.url else 'http://%s' % args.url,
headers=headers,
data=data,
verify=True if args.verify == 'yes' else args.verify,
timeout=args.timeout,
auth=(args.auth.key, args.auth.value) if args.auth else None,
proxies=dict((p.key, p.value) for p in args.proxy),
files=dict((os.path.basename(f.name), f) for f in args.file),
allow_redirects=args.allow_redirects,
)
except (KeyboardInterrupt, SystemExit):
sys.stderr.write('\n')
sys.exit(1)
except Exception as e:
if args.traceback:
raise
sys.stderr.write(str(e.message) + '\n')
sys.exit(1)
# Reconstruct the raw response.
encoding = response.encoding or 'ISO-8859-1'
original = response.raw._original_response
status_line, headers, body = (
'HTTP/{version} {status} {reason}'.format(
version='.'.join(str(original.version)),
status=original.status, reason=original.reason,
),
str(original.msg).decode(encoding),
response.content.decode(encoding) if response.content else u''
)
if do_prettify:
prettify = pretty.PrettyHttp(args.style)
if args.print_headers:
status_line = prettify.headers(status_line)
headers = prettify.headers(headers)
if args.print_body and 'Content-Type' in response.headers:
body = prettify.body(body, response.headers['Content-Type'])
# Output.
# TODO: preserve leading/trailing whitespaces in the body.
# Some of the Pygments styles add superfluous line breaks.
if args.print_headers:
stdout.write(status_line.strip())
stdout.write('\n')
stdout.write(headers.strip().encode('utf-8'))
stdout.write('\n\n')
if args.print_body:
stdout.write(body.strip().encode('utf-8'))
stdout.write('\n')
if __name__ == '__main__':
main()

217
httpie/cli.py Normal file
View File

@ -0,0 +1,217 @@
import json
import argparse
from collections import namedtuple
from . import pretty
from . import __doc__ as doc
from . import __version__ as version
SEP_COMMON = ':'
SEP_HEADERS = SEP_COMMON
SEP_DATA = '='
SEP_DATA_RAW_JSON = ':='
PRETTIFY_STDOUT_TTY_ONLY = object()
class ParseError(Exception):
pass
KeyValue = namedtuple('KeyValue', ['key', 'value', 'sep', 'orig'])
class KeyValueType(object):
"""A type used with `argparse`."""
def __init__(self, *separators):
self.separators = separators
def __call__(self, string):
found = dict((string.find(sep), sep)
for sep in self.separators
if string.find(sep) != -1)
if not found:
#noinspection PyExceptionInherit
raise argparse.ArgumentTypeError(
'"%s" is not a valid value' % string)
sep = found[min(found.keys())]
key, value = string.split(sep, 1)
return KeyValue(key=key, value=value, sep=sep, orig=string)
def parse_items(items, data=None, headers=None):
"""Parse `KeyValueType` `items` into `data` and `headers`."""
if headers is None:
headers = {}
if data is None:
data = {}
for item in items:
value = item.value
if item.sep == SEP_HEADERS:
target = headers
elif item.sep in [SEP_DATA, SEP_DATA_RAW_JSON]:
if item.sep == SEP_DATA_RAW_JSON:
try:
value = json.loads(item.value)
except ValueError:
raise ParseError('%s is not valid JSON' % item.orig)
target = data
else:
raise ParseError('%s is not valid item' % item.orig)
if item.key in target:
ParseError('duplicate item %s (%s)' % (item.key, item.orig))
target[item.key] = value
return headers, data
def _(text):
"""Normalize white space."""
return ' '.join(text.strip().split())
parser = argparse.ArgumentParser(description=doc.strip(),)
parser.add_argument('--version', action='version', version=version)
# Content type.
#############################################
group_type = parser.add_mutually_exclusive_group(required=False)
group_type.add_argument(
'--json', '-j', action='store_true',
help=_('''
Serialize data items as a JSON object and set
Content-Type to application/json, if not specified.
''')
)
group_type.add_argument(
'--form', '-f', action='store_true',
help=_('''
Serialize data items as form values and set
Content-Type to application/x-www-form-urlencoded,
if not specified.
''')
)
# Output options.
#############################################
parser.add_argument(
'--traceback', action='store_true', default=False,
help=_('''
Print exception traceback should one occur.
''')
)
prettify = parser.add_mutually_exclusive_group(required=False)
prettify.add_argument(
'--pretty', '-p', dest='prettify', action='store_true',
default=PRETTIFY_STDOUT_TTY_ONLY,
help=_('''
If stdout is a terminal,
the response is prettified by default (colorized and
indented if it is JSON). This flag ensures
prettifying even when stdout is redirected.
''')
)
prettify.add_argument(
'--ugly', '-u', dest='prettify', action='store_false',
help=_('''
Do not prettify the response.
''')
)
only = parser.add_mutually_exclusive_group(required=False)
only.add_argument(
'--headers', '-t', dest='print_body',
action='store_false', default=True,
help=('''
Print only the response headers.
''')
)
only.add_argument(
'--body', '-b', dest='print_headers',
action='store_false', default=True,
help=('''
Print only the response body.
''')
)
parser.add_argument(
'--style', '-s', dest='style', default='solarized', metavar='STYLE',
choices=pretty.AVAILABLE_STYLES,
help=_('''
Output coloring style, one of %s. Defaults to solarized.
''') % ', '.join(sorted(pretty.AVAILABLE_STYLES))
)
# ``requests.request`` keyword arguments.
parser.add_argument(
'--auth', '-a', help='username:password',
type=KeyValueType(SEP_COMMON)
)
parser.add_argument(
'--verify',
help=_('''
Set to "yes" to check the host\'s SSL certificate.
You can also pass the path to a CA_BUNDLE
file for private certs. You can also set
the REQUESTS_CA_BUNDLE environment variable.
''')
)
parser.add_argument(
'--proxy', default=[], action='append',
type=KeyValueType(SEP_COMMON),
help=_('''
String mapping protocol to the URL of the proxy
(e.g. http:foo.bar:3128).
''')
)
parser.add_argument(
'--allow-redirects', default=False, action='store_true',
help=_('''
Set this flag if full redirects are allowed
(e.g. re-POST-ing of data at new ``Location``)
''')
)
parser.add_argument(
'--file', metavar='PATH', type=argparse.FileType(),
default=[], action='append',
help='File to multipart upload'
)
parser.add_argument(
'--timeout', type=float,
help=_('''
Float describes the timeout of the request
(Use socket.setdefaulttimeout() as fallback).
''')
)
# Positional arguments.
#############################################
parser.add_argument(
'method', metavar='METHOD',
help=_('''
HTTP method to be used for the request
(GET, POST, PUT, DELETE, PATCH, ...).
''')
)
parser.add_argument(
'url', metavar='URL',
help=_('''
Protocol defaults to http:// if the
URL does not include it.
''')
)
parser.add_argument(
'items', nargs='*',
type=KeyValueType(SEP_COMMON, SEP_DATA, SEP_DATA_RAW_JSON),
help=_('''
HTTP header (key:value), data field (key=value)
or raw JSON field (field:=value).
''')
)

View File

@ -1,183 +0,0 @@
#!/usr/bin/env python
import os
import sys
import json
import argparse
from collections import namedtuple
import requests
from requests.structures import CaseInsensitiveDict
from . import pretty
from . import __version__ as version
from . import __doc__ as doc
DEFAULT_UA = 'HTTPie/%s' % version
SEP_COMMON = ':'
SEP_DATA = '='
TYPE_FORM = 'application/x-www-form-urlencoded; charset=utf-8'
TYPE_JSON = 'application/json; charset=utf-8'
KeyValue = namedtuple('KeyValue', ['key', 'value', 'sep'])
class KeyValueType(object):
def __init__(self, separators):
self.separators = separators
def __call__(self, string):
found = dict((string.find(sep), sep)
for sep in self.separators
if string.find(sep) != -1)
if not found:
raise argparse.ArgumentTypeError(
'"%s" is not a valid value' % string)
sep = found[min(found.keys())]
key, value = string.split(sep, 1)
return KeyValue(key=key, value=value, sep=sep)
parser = argparse.ArgumentParser(
description=doc.strip())
# Content type.
group_type = parser.add_mutually_exclusive_group(required=False)
group_type.add_argument('--json', '-j', action='store_true',
help='Serialize data items as a JSON object and set'
' Content-Type to application/json, if not specified.')
group_type.add_argument('--form', '-f', action='store_true',
help='Serialize data items as form values and set'
' Content-Type to application/x-www-form-urlencoded,'
' if not specified.')
# Output options.
parser.add_argument('--traceback', action='store_true', default=False,
help='Print a full exception traceback should one'
' be raised by `requests`.')
parser.add_argument('--ugly', '-u', help='Do not prettify the response.',
dest='prettify', action='store_false', default=True)
group_only = parser.add_mutually_exclusive_group(required=False)
group_only.add_argument('--headers', '-t', dest='print_body',
action='store_false', default=True,
help='Print only the response headers.')
group_only.add_argument('--body', '-b', dest='print_headers',
action='store_false', default=True,
help='Print only the response body.')
# ``requests.request`` keyword arguments.
parser.add_argument('--auth', '-a', help='username:password',
type=KeyValueType(SEP_COMMON))
parser.add_argument('--verify',
help='Set to "yes" to check the host\'s SSL certificate.'
' You can also pass the path to a CA_BUNDLE'
' file for private certs. You can also set '
'the REQUESTS_CA_BUNDLE environment variable.')
parser.add_argument('--proxy', default=[], action='append',
type=KeyValueType(SEP_COMMON),
help='String mapping protocol to the URL of the proxy'
' (e.g. http:foo.bar:3128).')
parser.add_argument('--allow-redirects', default=False, action='store_true',
help='Set this flag if full redirects are allowed'
' (e.g. re-POST-ing of data at new ``Location``)')
parser.add_argument('--file', metavar='PATH', type=argparse.FileType(),
default=[], action='append',
help='File to multipart upload')
parser.add_argument('--timeout', type=float,
help='Float describes the timeout of the request'
' (Use socket.setdefaulttimeout() as fallback).')
# Positional arguments.
parser.add_argument('method',
help='HTTP method to be used for the request'
' (GET, POST, PUT, DELETE, PATCH, ...).')
parser.add_argument('url', metavar='URL',
help='Protocol defaults to http:// if the'
' URL does not include it.')
parser.add_argument('items', metavar='item', nargs='*',
type=KeyValueType([SEP_COMMON, SEP_DATA]),
help='HTTP header (key:value) or data field (key=value)')
def main():
args = parser.parse_args()
# Parse request headers and data from the command line.
headers = CaseInsensitiveDict()
headers['User-Agent'] = DEFAULT_UA
data = {}
for item in args.items:
if item.sep == SEP_COMMON:
target = headers
else:
if not sys.stdin.isatty():
parser.error('Request body (stdin) and request '
'data (key=value) cannot be mixed.')
target = data
target[item.key] = item.value
if not sys.stdin.isatty():
data = sys.stdin.read()
# JSON/Form content type.
if args.json or (not args.form and data):
if sys.stdin.isatty():
data = json.dumps(data)
if 'Content-Type' not in headers and (data or args.json):
headers['Content-Type'] = TYPE_JSON
elif 'Content-Type' not in headers:
headers['Content-Type'] = TYPE_FORM
# Fire the request.
try:
response = requests.request(
method=args.method.lower(),
url=args.url if '://' in args.url else 'http://%s' % args.url,
headers=headers,
data=data,
verify=True if args.verify == 'yes' else args.verify,
timeout=args.timeout,
auth=(args.auth.key, args.auth.value) if args.auth else None,
proxies=dict((p.key, p.value) for p in args.proxy),
files=dict((os.path.basename(f.name), f) for f in args.file),
)
except (KeyboardInterrupt, SystemExit) as e:
sys.stderr.write('\n')
sys.exit(1)
except Exception as e:
if args.traceback:
raise
sys.stderr.write(str(e.message) + '\n')
sys.exit(1)
# Display the response.
encoding = response.encoding or 'ISO-8859-1'
original = response.raw._original_response
status_line, headers, body = (
u'HTTP/{version} {status} {reason}'.format(
version='.'.join(str(original.version)),
status=original.status, reason=original.reason,
),
str(original.msg).decode(encoding),
response.content.decode(encoding) if response.content else u''
)
if args.prettify and sys.stdout.isatty():
if args.print_headers:
status_line = pretty.prettify_http(status_line).strip()
headers = pretty.prettify_http(headers)
if args.print_body:
body = pretty.prettify_body(body,
response.headers['content-type'])
if args.print_headers:
print status_line
print headers
if args.print_body:
print body
if __name__ == '__main__':
main()

View File

@ -1,14 +1,22 @@
import os
import json
from functools import partial
import pygments
from pygments import token
from pygments.util import ClassNotFound
from pygments.lexers import get_lexer_for_mimetype
from pygments.formatters.terminal256 import Terminal256Formatter
from pygments.formatters.terminal import TerminalFormatter
from pygments.lexer import RegexLexer, bygroups
from pygments import token
from pygments.styles import get_style_by_name, STYLE_MAP
from . import solarized
DEFAULT_STYLE = 'solarized'
AVAILABLE_STYLES = [DEFAULT_STYLE] + STYLE_MAP.keys()
TYPE_JS = 'application/javascript'
FORMATTER = (Terminal256Formatter
if os.environ.get('TERM') == 'xterm-256color'
else TerminalFormatter)
class HTTPLexer(RegexLexer):
@ -24,32 +32,31 @@ class HTTPLexer(RegexLexer):
]}
highlight = partial(pygments.highlight,
formatter=Terminal256Formatter(
style=solarized.SolarizedStyle))
highlight_http = partial(highlight, lexer=HTTPLexer())
class PrettyHttp(object):
def __init__(self, style_name):
if style_name == 'solarized':
style = solarized.SolarizedStyle
else:
style = get_style_by_name(style_name)
self.formatter = FORMATTER(style=style)
def prettify_http(headers):
return highlight_http(headers)
def headers(self, content):
return pygments.highlight(content, HTTPLexer(), self.formatter)
def prettify_body(content, content_type):
content_type = content_type.split(';')[0]
if 'json' in content_type:
content_type = TYPE_JS
def body(self, content, content_type):
content_type = content_type.split(';')[0]
if 'json' in content_type:
content_type = TYPE_JS
try:
# Indent JSON
content = json.dumps(json.loads(content),
sort_keys=True, indent=4)
except Exception:
pass
try:
# Indent JSON
content = json.dumps(json.loads(content),
sort_keys=True, indent=4)
except Exception:
pass
try:
lexer = get_lexer_for_mimetype(content_type)
content = highlight(code=content, lexer=lexer)
if content:
content = content[:-1]
except Exception:
pass
return content
lexer = get_lexer_for_mimetype(content_type)
except ClassNotFound:
return content
content = pygments.highlight(content, lexer, self.formatter)
return content

View File

@ -1,12 +1,58 @@
import os
import sys
from setuptools import setup
import httpie
setup(name='httpie',version=httpie.__version__,
if sys.argv[-1] == 'test':
os.system('python tests.py')
sys.exit()
requirements = ['requests>=0.10.4', 'Pygments>=1.4']
if sys.version_info < (2, 7):
requirements.append('argparse>=1.2.1')
try:
long_description = open('README.md').read()
except IOError:
long_description = ''
setup(
name='httpie',version=httpie.__version__,
description=httpie.__doc__.strip(),
url='https://github.com/jkbr/httpie',
long_description=long_description,
url='http://httpie.org/',
download_url='https://github.com/jkbr/httpie',
author=httpie.__author__,
author_email='jakub@roztocil.name',
license=httpie.__licence__,
packages=['httpie'],
entry_points={'console_scripts': ['http = httpie.httpie:main']},
install_requires=['requests>=0.10.4', 'Pygments>=1.4'])
entry_points={
'console_scripts': [
'http = httpie.__main__:main',
],
},
install_requires=requirements,
classifiers=[
'Development Status :: 5 - Production/Stable',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
# TODO: Python 3
# 'Programming Language :: Python :: 3.1'
# 'Programming Language :: Python :: 3.2'
# 'Programming Language :: Python :: 3.3'
'Environment :: Console',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: BSD License',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Software Development',
'Topic :: System :: Networking',
'Topic :: Terminals',
'Topic :: Text Processing',
],
)

115
tests.py Normal file
View File

@ -0,0 +1,115 @@
import sys
import unittest
import argparse
from StringIO import StringIO
from httpie import __main__
from httpie import cli
TERMINAL_COLOR_CHECK = '\x1b['
def http(*args, **kwargs):
http_kwargs = {
'stdin_isatty': True,
'stdout_isatty': False
}
http_kwargs.update(kwargs)
stdout = http_kwargs.setdefault('stdout', StringIO())
__main__.main(args=args, **http_kwargs)
return stdout.getvalue()
class BaseTest(unittest.TestCase):
if sys.version < (2, 7):
def assertIn(self, member, container, msg=None):
self.assert_(member in container, msg)
def assertNotIn(self, member, container, msg=None):
self.assert_(member not in container, msg)
def assertDictEqual(self, d1, d2, msg=None):
self.assertEqual(set(d1.keys()), set(d2.keys()), msg)
self.assertEqual(sorted(d1.values()), sorted(d2.values()), msg)
class TestItemParsing(BaseTest):
def setUp(self):
self.kv = cli.KeyValueType(
cli.SEP_HEADERS,
cli.SEP_DATA,
cli.SEP_DATA_RAW_JSON
)
def test_invalid_items(self):
items = ['no-separator']
for item in items:
self.assertRaises(argparse.ArgumentTypeError,
lambda: self.kv(item))
def test_valid_items(self):
headers, data = cli.parse_items([
self.kv('string=value'),
self.kv('header:value'),
self.kv('list:=["a", 1, {}, false]'),
self.kv('obj:={"a": "b"}'),
self.kv('eh:'),
self.kv('ed='),
self.kv('bool:=true'),
])
self.assertDictEqual(headers, {
'header': 'value',
'eh': ''
})
self.assertDictEqual(data, {
"ed": "",
"string": "value",
"bool": True,
"list": ["a", 1, {}, False],
"obj": {"a": "b"}
})
class TestHTTPie(BaseTest):
def test_get(self):
http('GET', 'http://httpbin.org/get')
def test_json(self):
response = http('POST', 'http://httpbin.org/post', 'foo=bar')
self.assertIn('"foo": "bar"', response)
def test_form(self):
response = http('POST', '--form', 'http://httpbin.org/post', 'foo=bar')
self.assertIn('"foo": "bar"', response)
def test_headers(self):
response = http('GET', 'http://httpbin.org/headers', 'Foo:bar')
self.assertIn('"User-Agent": "HTTPie', response)
self.assertIn('"Foo": "bar"', response)
class TestPrettyFlag(BaseTest):
"""Test the --pretty / --ugly flag handling."""
def test_pretty_enabled_by_default(self):
r = http('GET', 'http://httpbin.org/get', stdout_isatty=True)
self.assertIn(TERMINAL_COLOR_CHECK, r)
def test_pretty_enabled_by_default_unless_stdin_redirected(self):
r = http('GET', 'http://httpbin.org/get', stdout_isatty=False)
self.assertNotIn(TERMINAL_COLOR_CHECK, r)
def test_force_pretty(self):
r = http('GET', '--pretty', 'http://httpbin.org/get', stdout_isatty=False)
self.assertIn(TERMINAL_COLOR_CHECK, r)
def test_force_ugly(self):
r = http('GET', '--ugly', 'http://httpbin.org/get', stdout_isatty=True)
self.assertNotIn(TERMINAL_COLOR_CHECK, r)
if __name__ == '__main__':
unittest.main()