2 ###############################################################################
3 # Formatting filter for urllib2's HTTPHandler(debuglevel=1) output
4 # Copyright (c) 2013, Analytics Pros
6 # This project is free software, distributed under the BSD license.
7 # Analytics Pros offers consulting and integration services if your firm needs
8 # assistance in strategy, implementation, or auditing existing work.
9 ###############################################################################
13 from cStringIO import StringIO
17 class BufferTranslator(object):
18 """ Provides a buffer-compatible interface for filtering buffer content.
22 def __init__(self, output):
24 self.encoding = getattr(output, 'encoding', None)
26 def write(self, content):
27 content = self.translate(content)
28 self.output.write(content)
32 def stripslashes(content):
33 return content.decode('string_escape')
36 def addslashes(content):
37 return content.encode('string_escape')
39 def translate(self, line):
40 for pattern, method in self.parsers:
41 match = pattern.match(line)
49 class LineBufferTranslator(BufferTranslator):
50 """ Line buffer implementation supports translation of line-format input
51 even when input is not already line-buffered. Caches input until newlines
52 occur, and then dispatches translated input to output buffer.
54 def __init__(self, *a, **kw):
55 self._linepending = []
56 super(LineBufferTranslator, self).__init__(*a, **kw)
58 def write(self, _input):
59 lines = _input.splitlines(True)
60 for i in range(0, len(lines)):
62 if lines[i].endswith('\n'):
63 prefix = len(self._linepending) and ''.join(self._linepending) or ''
64 self.output.write(self.translate(prefix + lines[i]))
65 del self._linepending[0:]
69 self._linepending.append(lines[ last ])
73 if len(self._linepending):
74 self.output.write(self.translate(''.join(self._linepending)))
77 class HTTPTranslator(LineBufferTranslator):
78 """ Translates output from |urllib2| HTTPHandler(debuglevel = 1) into
79 HTTP-compatible, readible text structures for human analysis.
82 RE_LINE_PARSER = re.compile(r'^(?:([a-z]+):)\s*(\'?)([^\r\n]*)\2(?:[\r\n]*)$')
83 RE_LINE_BREAK = re.compile(r'(\r?\n|(?:\\r)?\\n)')
84 RE_HTTP_METHOD = re.compile(r'^(POST|GET|HEAD|DELETE|PUT|TRACE|OPTIONS)')
85 RE_PARAMETER_SPACER = re.compile(r'&([a-z0-9]+)=')
88 def spacer(cls, line):
89 return cls.RE_PARAMETER_SPACER.sub(r' &\1= ', line)
91 def translate(self, line):
93 parsed = self.RE_LINE_PARSER.match(line)
96 value = parsed.group(3)
97 stage = parsed.group(1)
99 if stage == 'send': # query string is rendered here
100 return '\n# HTTP Request:\n' + self.stripslashes(value)
101 elif stage == 'reply':
102 return '\n\n# HTTP Response:\n' + self.stripslashes(value)
103 elif stage == 'header':
112 def consume(outbuffer = None): # Capture standard output
113 sys.stdout = HTTPTranslator(outbuffer or sys.stdout)
117 if __name__ == '__main__':
118 consume(sys.stdout).write(sys.stdin.read())
121 # vim: set nowrap tabstop=4 shiftwidth=4 softtabstop=0 expandtab textwidth=0 filetype=python foldmethod=indent foldcolumn=4