Permalink
Browse files

Revorked output

Binary now works everywhere. Also added `--output FILE` for Windows.
  • Loading branch information...
1 parent 6eed0d9 commit 923a8b71bd68c56b2dc1c39e26b9605fe16be495 @jakubroztocil committed Jul 30, 2012
Showing with 223 additions and 84 deletions.
  1. +2 −0 README.rst
  2. +23 −5 httpie/cli.py
  3. +14 −5 httpie/core.py
  4. +23 −1 httpie/input.py
  5. +17 −10 httpie/models.py
  6. +38 −19 httpie/output.py
  7. +106 −44 tests/tests.py
View
@@ -334,6 +334,8 @@ Changelog
=========
* `0.2.7dev`_
+ * Windows: Added ``--output FILE`` to store output into a file
+ (piping results into corrupted data on Windows).
* Proper handling of binary requests and responses.
* Fixed printing of ``multipart/form-data`` requests.
* Renamed ``--traceback`` to ``--debug``.
View
@@ -3,6 +3,10 @@
NOTE: the CLI interface may change before reaching v1.0.
"""
+import argparse
+
+from requests.compat import is_windows
+
from . import __doc__
from . import __version__
from .output import AVAILABLE_STYLES
@@ -50,12 +54,19 @@ def _(text):
# Output options.
#############################################
+
parser.add_argument(
- '--debug', action='store_true', default=False,
- help=_('''
- Prints exception traceback should one occur and other
- information useful for debugging HTTPie itself.
- ''')
+ '--output', '-o', type=argparse.FileType('wb'),
+ metavar='FILE',
+ help= argparse.SUPPRESS if not is_windows else _(
+ '''
+ Save output to FILE.
+ This option is a replacement for piping output to FILE,
+ which would on Windows result into corrupted data
+ being saved.
+
+ '''
+ )
)
prettify = parser.add_mutually_exclusive_group(required=False)
@@ -200,6 +211,13 @@ def _(text):
(Use socket.setdefaulttimeout() as fallback).
''')
)
+parser.add_argument(
+ '--debug', action='store_true', default=False,
+ help=_('''
+ Prints exception traceback should one occur and other
+ information useful for debugging HTTPie itself.
+ ''')
+)
# Positional arguments.
View
@@ -148,7 +148,15 @@ def main(args=sys.argv[1:], env=Environment()):
Return exit status.
"""
+
+ if env.is_windows and not env.stdout_isatty:
+ env.stderr.write(
+ 'http: error: Output redirection is not supported on Windows.'
+ ' Please use `--output FILE\' instead.\n')
+ return 1
+
args = parser.parse_args(args=args, env=env)
+
response = get_response(args, env)
status = 0
@@ -159,12 +167,13 @@ def main(args=sys.argv[1:], env=Environment()):
if status and not env.stdout_isatty:
err = 'http error: %s %s\n' % (
response.raw.status, response.raw.reason)
- env.stderr.write(err.encode('utf8'))
+ env.stderr.write(err)
- output_bytes = get_output(args, env, response.request, response)
+ output = get_output(args, env, response.request, response)
- # output_bytes = output.encode('utf8')
- f = getattr(env.stdout, 'buffer', env.stdout)
- f.write(output_bytes)
+ try:
+ env.stdout.buffer.write(output)
+ except AttributeError:
+ env.stdout.write(output)
return status
View
@@ -89,8 +89,15 @@ def __init__(self, *args, **kwargs):
#noinspection PyMethodOverriding
def parse_args(self, env, args=None, namespace=None):
+
+ self.env = env
+
args = super(Parser, self).parse_args(args, namespace)
+ if args.output:
+ env.stdout = args.output
+ env.stdout_isatty = False
+
self._process_output_options(args, env)
self._guess_method(args, env)
self._parse_items(args)
@@ -104,9 +111,24 @@ def parse_args(self, env, args=None, namespace=None):
if args.prettify == PRETTIFY_STDOUT_TTY_ONLY:
args.prettify = env.stdout_isatty
+ elif args.prettify and env.is_windows:
+ self.error('Only terminal output can be prettified on Windows.')
return args
+ def _print_message(self, message, file=None):
+ # Sneak in our stderr/stdout.
+ file = {
+ sys.stdout: self.env.stdout,
+ sys.stderr: self.env.stderr,
+ None: self.env.stderr
+ }.get(file, file)
+
+ #if isinstance(message, str):
+ # message = message.encode('utf8')
+
+ super(Parser, self)._print_message(message, file)
+
def _body_from_file(self, args, data):
"""There can only be one source of request data."""
if args.data:
@@ -398,7 +420,7 @@ def parse_items(items, data=None, headers=None, files=None, params=None):
target = params
elif item.sep == SEP_FILES:
try:
- with open(os.path.expanduser(value), 'r') as f:
+ with open(os.path.expanduser(value)) as f:
value = (os.path.basename(f.name), f.read())
except IOError as e:
raise ParseError(
View
@@ -10,23 +10,18 @@ class Environment(object):
and allows for mocking.
"""
+
+ #noinspection PyUnresolvedReferences
+ is_windows = is_windows
+
progname = os.path.basename(sys.argv[0])
if progname not in ['http', 'https']:
progname = 'http'
stdin_isatty = sys.stdin.isatty()
stdin = sys.stdin
-
- if is_windows:
- # `colorama` patches `sys.stdout` so its initialization
- # needs to happen before the default environment is set.
- import colorama
- colorama.init()
- del colorama
-
stdout_isatty = sys.stdout.isatty()
stdout = sys.stdout
-
stderr = sys.stderr
# Can be set to 0 to disable colors completely.
@@ -35,6 +30,18 @@ class Environment(object):
def __init__(self, **kwargs):
self.__dict__.update(**kwargs)
+ def init_colors(self):
+ # We check for real Window here, not self.is_windows as
+ # it could be mocked.
+ if (is_windows and not self.__colors_initialized
+ and self.stdout == sys.stdout):
+ import colorama.initialise
+ self.stdout = colorama.initialise.wrap_stream(
+ self.stdout, autoreset=False,
+ convert=None, strip=None, wrap=True)
+ self.__colors_initialized = True
+ __colors_initialized = False
+
class HTTPMessage(object):
"""Model representing an HTTP message."""
@@ -49,7 +56,7 @@ def __init__(self, line, headers, body, encoding=None, content_type=None):
self.line = line # {Request,Status}-Line
self.headers = headers
self.body = body
- self.encoding = encoding
+ self.encoding = encoding or 'utf8'
self.content_type = content_type
@classmethod
View
@@ -41,45 +41,64 @@ def format(msg, prettifier=None, with_headers=True, with_body=True,
precision.
"""
- chunks = []
+
+ # Output encoding.
+ if env.stdout_isatty:
+ # Use encoding suitable for the terminal. Unsupported characters
+ # will be replaced in the output.
+ errors = 'replace'
+ output_encoding = getattr(env.stdout, 'encoding', None)
+ else:
+ # Preserve the message encoding.
+ errors = 'strict'
+ output_encoding = msg.encoding
+ if not output_encoding:
+ # Default to utf8
+ output_encoding = 'utf8'
+
+ if prettifier:
+ env.init_colors()
+
+ #noinspection PyArgumentList
+ output = bytearray()
if with_headers:
headers = '\n'.join([msg.line, msg.headers])
if prettifier:
headers = prettifier.process_headers(headers)
- chunks.append(headers.strip().encode('utf8'))
+ output.extend(
+ headers.encode(output_encoding, errors).strip())
- if with_body and msg.body or env.stdout_isatty:
- chunks.append(b'\n\n')
+ if with_body and msg.body:
+ output.extend(b'\n\n')
if with_body and msg.body:
body = msg.body
- bin_suppressed = False
- if prettifier or env.stdout_isatty:
+ if not (env.stdout_isatty or prettifier):
+ # Verbatim body even if it's binary.
+ pass
+ else:
try:
- body = msg.body.decode(msg.encoding or 'utf8')
+ body = body.decode(msg.encoding)
except UnicodeDecodeError:
- # Assume binary
- bin_suppressed = True
- body = BINARY_SUPPRESSED_NOTICE.encode('utf8')
+ # Suppress binary data.
+ body = BINARY_SUPPRESSED_NOTICE.encode(output_encoding)
if not with_headers:
- body = b'\n' + body
+ output.extend(b'\n')
else:
- body = body.encode('utf8')
+ if prettifier and msg.content_type:
+ body = prettifier.process_body(
+ body, msg.content_type).strip()
- if not bin_suppressed and prettifier and msg.content_type:
- body = (prettifier
- .process_body(body.decode('utf8'), msg.content_type)
- .strip()
- .encode('utf8'))
+ body = body.encode(output_encoding, errors)
- chunks.append(body)
+ output.extend(body)
- return b''.join(chunks)
+ return bytes(output)
class HTTPLexer(lexer.RegexLexer):
Oops, something went wrong.

0 comments on commit 923a8b7

Please sign in to comment.