Skip to content

Commit

Permalink
Merge pull request #788 from danielgtaylor/square-brackets
Browse files Browse the repository at this point in the history
Add support for square bracket shorthand lists. Fixes #788.
  • Loading branch information
danielgtaylor committed May 23, 2014
2 parents 2c8f038 + ca4d130 commit 8485e91
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 15 deletions.
2 changes: 1 addition & 1 deletion awscli/argprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def _list_scalar_list_parse(self, param, value):
current_value = unpack_scalar_cli_arg(args[current_key],
current[1].strip())
if args[current_key].type == 'list':
current_parsed[current_key] = [current_value]
current_parsed[current_key] = current_value.split(',')
else:
current_parsed[current_key] = current_value
elif current_key is not None:
Expand Down
49 changes: 35 additions & 14 deletions awscli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@


def split_on_commas(value):
if '"' not in value and '\\' not in value and "'" not in value:
if not any(char in value for char in ['"', '\\', "'", ']', '[']):
# No quotes or escaping, just use a simple split.
return value.split(',')
elif '"' not in value and "'" not in value:
elif not any(char in value for char in ['"', "'", '[', ']']):
# Simple escaping, let the csv module handle it.
return list(csv.reader(six.StringIO(value), escapechar='\\'))[0]
else:
Expand All @@ -36,8 +36,21 @@ def _split_with_quotes(value):
iter_parts = iter(parts)
new_parts = []
for part in iter_parts:
# Find the first quote
quote_char = _find_quote_char_in_part(part)
if quote_char is None:

# Find an opening list bracket
list_start = part.find('=[')

if list_start >= 0 and value.find(']') != -1 and \
(quote_char is None or part.find(quote_char) > list_start):
# This is a list, eat all the items until the end
new_chunk = _eat_items(value, iter_parts, part, ']')
list_items = _split_with_quotes(new_chunk[list_start + 2:-1])
new_chunk = new_chunk[:list_start + 1] + ','.join(list_items)
new_parts.append(new_chunk)
continue
elif quote_char is None:
new_parts.append(part)
continue
elif part.count(quote_char) == 2:
Expand All @@ -49,21 +62,29 @@ def _split_with_quotes(value):
continue
# Now that we've found a starting quote char, we
# need to combine the parts until we encounter an end quote.
current = part
chunks = [current.replace(quote_char, '')]
while True:
try:
current = six.advance_iterator(iter_parts)
except StopIteration:
raise ValueError(value)
chunks.append(current.replace(quote_char, ''))
if quote_char in current:
break
new_chunk = ','.join(chunks)
new_chunk = _eat_items(value, iter_parts, part, quote_char, quote_char)
new_parts.append(new_chunk)
return new_parts


def _eat_items(value, iter_parts, part, end_char, replace_char=''):
"""
Eat items from an iterator, optionally replacing characters with
a blank and stopping when the end_char has been reached.
"""
current = part
chunks = [current.replace(replace_char, '')]
while True:
try:
current = six.advance_iterator(iter_parts)
except StopIteration:
raise ValueError(value)
chunks.append(current.replace(replace_char, ''))
if end_char in current:
break
return ','.join(chunks)


def _find_quote_char_in_part(part):
if '"' not in part and "'" not in part:
return
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/test_argprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from awscli.argprocess import ParamError
from awscli.argprocess import ParamUnknownKeyError
from awscli.argprocess import uri_param
from awscli.arguments import CustomArgument


MAPHELP = """--attributes key_name=string,key_name2=string
Expand Down Expand Up @@ -194,6 +195,41 @@ def test_list_structure_list_scalar_2(self):

self.assertEqual(simplified, expected)

def test_list_structure_list_scalar_3(self):
arg = CustomArgument('foo', schema={
'type': 'array',
'items': {
'type': 'object',
'properties': {
'Name': {
'type': 'string'
},
'Args': {
'type': 'array',
'items': {
'type': 'string'
}
}
}
}
})
arg.create_argument_object()
p = arg.argument_object

expected = [
{"Name": "foo",
"Args": ["a", "k1=v1", "b"]},
{"Name": "bar",
"Args": ["baz"]}
]

simplified = self.simplify(p, [
"Name=foo,Args=[a,k1=v1,b]",
"Name=bar,Args=baz"
])

self.assertEqual(simplified, expected)

def test_list_structure_list_multiple_scalar(self):
p = self.get_param_object('elastictranscoder.CreateJob.Playlists')
returned = self.simplify(
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,23 @@ def test_trailing_commas(self):
def test_escape_backslash(self):
self.assertEqual(split_on_commas('foo,bar\\\\,baz\\\\,qux'),
['foo', 'bar\\', 'baz\\', 'qux'])

def test_square_brackets(self):
self.assertEqual(split_on_commas('foo,bar=["a=b",\'2\',c=d],baz'),
['foo', 'bar=a=b,2,c=d', 'baz'])

def test_quoted_square_brackets(self):
self.assertEqual(split_on_commas('foo,bar="[blah]",c=d],baz'),
['foo', 'bar=[blah]', 'c=d]', 'baz'])

def test_missing_bracket(self):
self.assertEqual(split_on_commas('foo,bar=[a,baz'),
['foo', 'bar=[a', 'baz'])

def test_missing_bracket2(self):
self.assertEqual(split_on_commas('foo,bar=a],baz'),
['foo', 'bar=a]', 'baz'])

def test_bracket_in_middle(self):
self.assertEqual(split_on_commas('foo,bar=a[b][c],baz'),
['foo', 'bar=a[b][c]', 'baz'])

0 comments on commit 8485e91

Please sign in to comment.