@ 260802.2 · fetching
current
import json
import re
import requests

from_cmdline = False
try:
    __file__
    from_cmdline = True
except NameError:
    pass

if not from_cmdline:
    import vim


METHOD_REGEX = re.compile('^(GET|POST|DELETE|PUT|HEAD|OPTIONS|PATCH) (.*)$')
HEADER_REGEX = re.compile('^([^()<>@,;:\<>/\[\]?={}]+):\\s*(.*)$')
VAR_REGEX = re.compile('^# ?(:[^: ]+)\\s*=\\s*(.+)$')
GLOBAL_VAR_REGEX = re.compile('^# ?(\$[^$ ]+)\\s*=\\s*(.+)$')
FILE_REGEX = re.compile("!((?:file)|(?:(?:content)))\((.+)\)")


def replace_vars(string, variables):
    for var, val in variables.items():
        string = string.replace(var, val)
    return string


def is_comment(s):
    return s.startswith('#')


def do_request(block, buf):
    variables = dict((m.groups() for m in (GLOBAL_VAR_REGEX.match(l) for l in buf) if m))
    variables.update(dict((m.groups() for m in (VAR_REGEX.match(l) for l in block) if m)))

    block = [line for line in block if not is_comment(line) and line.strip() != '']

    if len(block) == 0:
        print 'Request was empty.'
        return

    method_url = block.pop(0)
    method_url_match = METHOD_REGEX.match(method_url)
    if not method_url_match:
        print 'Could not find method or URL!'
        return

    method, url = method_url_match.groups()
    url = replace_vars(url, variables)

    headers = {}
    while len(block) > 0:
        header_match = HEADER_REGEX.match(block[0])
        if header_match:
            block.pop(0)
            header_name, header_value = header_match.groups()
            headers[header_name] = replace_vars(header_value, variables)
        else:
            break

    data = [ replace_vars(l, variables) for l in block ]
    files = None
    if all([ '=' in l for l in data ]):
      # Form data: separate entries into data dict, and files dict
      key_value_pairs = dict([ l.split('=', 1) for l in data ])
      def to_file(expr):
        type, arg = FILE_REGEX.match(expr).groups()
        arg = arg.replace('\\(', '(').replace('\\)', ')')
        return open(arg, 'rb') if type == 'file' else (arg)

      files = dict(map(lambda (k,v): (k, to_file(v)), filter(lambda (k,v): FILE_REGEX.match(v), key_value_pairs.items())))
      data = dict(filter(lambda (k,v): not FILE_REGEX.match(v), key_value_pairs.items()))
    else:
      # Straight data: just send it off as a string.
      data = '\n'.join(data)

    response = requests.request(method, url, headers=headers, data=data, files=files)
    content_type = response.headers.get('Content-Type', '').split(';')[0]

    response_body = response.text
    if content_type == 'application/json':
        try:
            response_body = json.dumps(
                json.loads(response.text), sort_keys=True, indent=2,
                separators=(',', ': '),
                ensure_ascii=vim.eval('g:http_client_json_escape_utf')=='1')
        except ValueError:
            pass

    display = (
        response_body.split('\n') +
        ['', '// status code: %s' % response.status_code] +
        ['// %s: %s' % (k, v) for k, v in response.headers.items()]
    )

    return display, content_type


# Vim methods.

def vim_filetypes_by_content_type():
    return {
        'application/json': vim.eval('g:http_client_json_ft'),
        'application/xml': 'xml',
        'text/html': 'html'
    }

BUFFER_NAME = '__HTTP_Client_Response__'


def find_block(buf, line_num):
    length = len(buf)
    is_buffer_terminator = lambda s: s.strip() == ''

    block_start = line_num
    while block_start > 0 and not is_buffer_terminator(buf[block_start]):
        block_start -= 1

    block_end = line_num
    while block_end < length and not is_buffer_terminator(buf[block_end]):
        block_end += 1

    return buf[block_start:block_end + 1]


def open_scratch_buffer(contents, filetype):
    previous_window = vim.current.window
    existing_buffer_window_id = vim.eval('bufwinnr("%s")' % BUFFER_NAME)
    if existing_buffer_window_id == '-1':
        if vim.eval('g:http_client_result_vsplit') == '1':
            split_cmd = 'vsplit'
        else:
            split_cmd = 'split'
        vim.command('rightbelow %s %s' % (split_cmd, BUFFER_NAME))
        vim.command('setlocal buftype=nofile nospell')
    else:
        vim.command('%swincmd w' % existing_buffer_window_id)

    vim.command('set filetype=%s' % filetype)
    vim.current.buffer[:] = contents

    if vim.eval('g:http_client_focus_output_window') != '1':
        vim.current.window = previous_window


def do_request_from_buffer():
    win = vim.current.window
    line_num = win.cursor[0] - 1
    block = find_block(win.buffer, line_num)
    result = do_request(block, win.buffer)
    if result:
        response, content_type = result
        vim_ft = vim_filetypes_by_content_type().get(content_type, 'text')
        open_scratch_buffer(response, vim_ft)


# Tests.

def run_tests():
    import json

    def extract_json(resp):
        return json.loads(''.join([ l for l in resp[0] if not l.startswith('//') ]))

    def test(assertion, test):
        print 'Test %s: %s' % ('passed' if assertion else 'failed', test)
        if not assertion:
            raise AssertionError

    resp = extract_json(do_request([
        '# comment',
        '# :a=barf',
        'GET http://httpbin.org/headers',
        'X-Hey: :a',
        '# comment'
    ], []))
    test(resp['headers']['X-Hey'] == 'barf', 'Headers are passed with variable substitution.')

    resp = extract_json(do_request([
        '# :a = barf',
        'GET http://httpbin.org/get?data=:a'
    ], []))
    test(resp['args']['data'] == 'barf', 'GET data is passed with variable substitution.')

    resp = extract_json(do_request([
        'POST http://httpbin.org/post',
        'some data'
    ], []))
    test(resp['data'] == 'some data', 'POST data is passed with variable substitution.')

    resp = extract_json(do_request([
        'POST http://httpbin.org/post',
        'forma=a',
        'formb=b',
    ], []))
    test(resp['form']['forma'] == 'a', 'POST form data is passed.')

    resp = extract_json(do_request([
        'POST http://$global/post',
        'forma=a',
        'formb=b',
    ], [ '# $global = httpbin.org']))
    test(resp['form']['forma'] == 'a', 'Global variables are substituted.')

    import os
    from tempfile import NamedTemporaryFile

    SAMPLE_FILE_CONTENT = 'sample file content'

    temp_file = NamedTemporaryFile(delete = False)
    temp_file.write(SAMPLE_FILE_CONTENT)
    temp_file.close()
    resp = extract_json(do_request([
        'POST http://httpbin.org/post',
        'forma=a',
        'formb=b',
        "formc=!file(%s)" % temp_file.name,
    ], []))
    test(resp['files']['formc'] == SAMPLE_FILE_CONTENT, 'Files given as path are sent properly.')
    test(not 'formc' in resp['form'], 'File not included in form data.')
    os.unlink(temp_file.name)

    resp = extract_json(do_request([
        'POST http://httpbin.org/post',
        'forma=a',
        'formb=b',
        "formc=!content(%s)" % SAMPLE_FILE_CONTENT,
    ], []))
    test(resp['files']['formc'] == SAMPLE_FILE_CONTENT, 'Files given as content are sent properly.')

    resp = extract_json(do_request([
        'POST http://httpbin.org/post',
        "c=!content(foo \\(bar\\))",
    ], []))
    test(resp['files']['c'] == 'foo (bar)', 'Escaped parenthesis should be unescaped during request')


if from_cmdline:
    run_tests()