Merge pull request #72 from jakebasile/issue-61

Added Query String Parameters (param=:value).
This commit is contained in:
Jakub Roztocil 2012-07-19 03:12:32 -07:00
commit 1d6fcfff73
5 changed files with 48 additions and 12 deletions

View File

@ -54,7 +54,7 @@ Synopsis::
http [flags] [METHOD] URL [items] http [flags] [METHOD] URL [items]
There are four types of key-value pair items available: There are five types of key-value pair items available:
Headers (``Name:Value``) Headers (``Name:Value``)
Arbitrary HTTP headers. The ``:`` character is used to separate a header's Arbitrary HTTP headers. The ``:`` character is used to separate a header's
@ -77,6 +77,9 @@ File fields (``field@/path/to/file``)
``screenshot@/path/to/file.png``. The presence of a file field results into ``screenshot@/path/to/file.png``. The presence of a file field results into
a ``multipart/form-data`` request. a ``multipart/form-data`` request.
Query String Parameters (``name=:value``)
Appends the given name/value pair as a query string to the URL.
Examples Examples
^^^^^^^^ ^^^^^^^^
@ -118,6 +121,12 @@ The above will send the same request as if the following HTML form were submitte
<input type="file" name="cv" /> <input type="file" name="cv" />
</form> </form>
Query string parameters can be added to any request::
http GET example.com/ search=:donuts
Will GET the URL "example.com/?search=donuts".
A whole request body can be passed in via **``stdin``** instead, in which A whole request body can be passed in via **``stdin``** instead, in which
case it will be used with no further processing:: case it will be used with no further processing::
@ -172,8 +181,9 @@ See ``http -h`` for more details::
include one. include one.
ITEM A key-value pair whose type is defined by the ITEM A key-value pair whose type is defined by the
separator used. It can be an HTTP header separator used. It can be an HTTP header
(header:value), a data field to be used in the request (header:value), a query parameter (name=:value),
body (field_name=value), a raw JSON data field a data field to be used in the request body
(field_name=value), a raw JSON data field
(field_name:=value), or a file field (field_name:=value), or a file field
(field_name@/path/to/file). You can use a backslash to (field_name@/path/to/file). You can use a backslash to
escape a colliding separator in the field name. escape a colliding separator in the field name.

View File

@ -41,6 +41,7 @@ def _get_response(args):
# the `Content-Type` for us. # the `Content-Type` for us.
args.headers['Content-Type'] = TYPE_FORM args.headers['Content-Type'] = TYPE_FORM
# Fire the request. # Fire the request.
try: try:
credentials = None credentials = None
@ -61,6 +62,7 @@ def _get_response(args):
proxies=dict((p.key, p.value) for p in args.proxy), proxies=dict((p.key, p.value) for p in args.proxy),
files=args.files, files=args.files,
allow_redirects=args.allow_redirects, allow_redirects=args.allow_redirects,
params=args.queries,
) )
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):

View File

@ -203,6 +203,7 @@ parser.add_argument(
metavar='ITEM', metavar='ITEM',
type=cliparse.KeyValueType( type=cliparse.KeyValueType(
cliparse.SEP_COMMON, cliparse.SEP_COMMON,
cliparse.SEP_QUERY,
cliparse.SEP_DATA, cliparse.SEP_DATA,
cliparse.SEP_DATA_RAW_JSON, cliparse.SEP_DATA_RAW_JSON,
cliparse.SEP_FILES cliparse.SEP_FILES
@ -211,7 +212,8 @@ parser.add_argument(
A key-value pair whose type is defined by the A key-value pair whose type is defined by the
separator used. It can be an HTTP header (header:value), separator used. It can be an HTTP header (header:value),
a data field to be used in the request body (field_name=value), a data field to be used in the request body (field_name=value),
a raw JSON data field (field_name:=value), a raw JSON data field (field_name:=value),
a query parameter (name=:value),
or a file field (field_name@/path/to/file). or a file field (field_name@/path/to/file).
You can use a backslash to escape a colliding You can use a backslash to escape a colliding
separator in the field name. separator in the field name.

View File

@ -25,6 +25,7 @@ SEP_HEADERS = SEP_COMMON
SEP_DATA = '=' SEP_DATA = '='
SEP_DATA_RAW_JSON = ':=' SEP_DATA_RAW_JSON = ':='
SEP_FILES = '@' SEP_FILES = '@'
SEP_QUERY = '=:'
DATA_ITEM_SEPARATORS = [ DATA_ITEM_SEPARATORS = [
SEP_DATA, SEP_DATA,
SEP_DATA_RAW_JSON, SEP_DATA_RAW_JSON,
@ -102,6 +103,7 @@ class Parser(argparse.ArgumentParser):
item = KeyValueType( item = KeyValueType(
SEP_COMMON, SEP_COMMON,
SEP_QUERY,
SEP_DATA, SEP_DATA,
SEP_DATA_RAW_JSON, SEP_DATA_RAW_JSON,
SEP_FILES).__call__(args.url) SEP_FILES).__call__(args.url)
@ -118,16 +120,17 @@ class Parser(argparse.ArgumentParser):
def _parse_items(self, args): def _parse_items(self, args):
""" """
Parse `args.items` into `args.headers`, `args.data` and `args.files`. Parse `args.items` into `args.headers`, `args.data`, `args.queries`, and `args.files`.
""" """
args.headers = CaseInsensitiveDict() args.headers = CaseInsensitiveDict()
args.headers['User-Agent'] = DEFAULT_UA args.headers['User-Agent'] = DEFAULT_UA
args.data = OrderedDict() args.data = OrderedDict()
args.files = OrderedDict() args.files = OrderedDict()
args.queries = CaseInsensitiveDict()
try: try:
parse_items(items=args.items, headers=args.headers, parse_items(items=args.items, headers=args.headers,
data=args.data, files=args.files) data=args.data, files=args.files, queries=args.queries)
except ParseError as e: except ParseError as e:
if args.traceback: if args.traceback:
raise raise
@ -207,6 +210,8 @@ class KeyValueType(object):
if start >= estart and end <= eend: if start >= estart and end <= eend:
inside_escape = True inside_escape = True
break break
if start in found and len(found[start]) > len(sep):
break
if not inside_escape: if not inside_escape:
found[start] = sep found[start] = sep
@ -264,19 +269,23 @@ class AuthCredentialsType(KeyValueType):
) )
def parse_items(items, data=None, headers=None, files=None): def parse_items(items, data=None, headers=None, files=None, queries=None):
"""Parse `KeyValueType` `items` into `data`, `headers` and `files`.""" """Parse `KeyValueType` `items` into `data`, `headers`, `files`, and `queries`."""
if headers is None: if headers is None:
headers = {} headers = {}
if data is None: if data is None:
data = {} data = {}
if files is None: if files is None:
files = {} files = {}
if queries is None:
queries = {}
for item in items: for item in items:
value = item.value value = item.value
key = item.key key = item.key
if item.sep == SEP_HEADERS: if item.sep == SEP_HEADERS:
target = headers target = headers
elif item.sep == SEP_QUERY:
target = queries
elif item.sep == SEP_FILES: elif item.sep == SEP_FILES:
try: try:
value = open(os.path.expanduser(item.value), 'r') value = open(os.path.expanduser(item.value), 'r')
@ -301,4 +310,4 @@ def parse_items(items, data=None, headers=None, files=None):
target[key] = value target[key] = value
return headers, data, files return headers, data, files, queries

View File

@ -341,6 +341,7 @@ class ItemParsingTest(BaseTestCase):
def setUp(self): def setUp(self):
self.key_value_type = cliparse.KeyValueType( self.key_value_type = cliparse.KeyValueType(
cliparse.SEP_HEADERS, cliparse.SEP_HEADERS,
cliparse.SEP_QUERY,
cliparse.SEP_DATA, cliparse.SEP_DATA,
cliparse.SEP_DATA_RAW_JSON, cliparse.SEP_DATA_RAW_JSON,
cliparse.SEP_FILES, cliparse.SEP_FILES,
@ -353,7 +354,7 @@ class ItemParsingTest(BaseTestCase):
lambda: self.key_value_type(item)) lambda: self.key_value_type(item))
def test_escape(self): def test_escape(self):
headers, data, files = cliparse.parse_items([ headers, data, files, queries = cliparse.parse_items([
# headers # headers
self.key_value_type('foo\\:bar:baz'), self.key_value_type('foo\\:bar:baz'),
self.key_value_type('jack\\@jill:hill'), self.key_value_type('jack\\@jill:hill'),
@ -372,7 +373,7 @@ class ItemParsingTest(BaseTestCase):
self.assertIn('bar@baz', files) self.assertIn('bar@baz', files)
def test_escape_longsep(self): def test_escape_longsep(self):
headers, data, files = cliparse.parse_items([ headers, data, files, queries = cliparse.parse_items([
self.key_value_type('bob\\:==foo'), self.key_value_type('bob\\:==foo'),
]) ])
self.assertDictEqual(data, { self.assertDictEqual(data, {
@ -380,7 +381,7 @@ class ItemParsingTest(BaseTestCase):
}) })
def test_valid_items(self): def test_valid_items(self):
headers, data, files = cliparse.parse_items([ headers, data, files, queries = cliparse.parse_items([
self.key_value_type('string=value'), self.key_value_type('string=value'),
self.key_value_type('header:value'), self.key_value_type('header:value'),
self.key_value_type('list:=["a", 1, {}, false]'), self.key_value_type('list:=["a", 1, {}, false]'),
@ -389,6 +390,7 @@ class ItemParsingTest(BaseTestCase):
self.key_value_type('ed='), self.key_value_type('ed='),
self.key_value_type('bool:=true'), self.key_value_type('bool:=true'),
self.key_value_type('test-file@%s' % TEST_FILE_PATH), self.key_value_type('test-file@%s' % TEST_FILE_PATH),
self.key_value_type('query=:value'),
]) ])
self.assertDictEqual(headers, { self.assertDictEqual(headers, {
'header': 'value', 'header': 'value',
@ -401,8 +403,19 @@ class ItemParsingTest(BaseTestCase):
"list": ["a", 1, {}, False], "list": ["a", 1, {}, False],
"obj": {"a": "b"} "obj": {"a": "b"}
}) })
self.assertDictEqual(queries, {
'query': 'value',
})
self.assertIn('test-file', files) self.assertIn('test-file', files)
def test_query_string(self):
headers, data, files, queries = cliparse.parse_items([
self.key_value_type('query=:value'),
])
self.assertDictEqual(queries, {
'query': 'value',
})
class ArgumentParserTestCase(unittest.TestCase): class ArgumentParserTestCase(unittest.TestCase):