Skip to content

Commit

Permalink
Add support for struct with list param shorthand
Browse files Browse the repository at this point in the history
This was already supported within a list via
`list-structure(list-scalar, scalar)` but not in the non-list form of
`structure(list-scalar, scalar)`. This commit adds support and refactors
the code to reuse as much as possible between the two.
  • Loading branch information
danielgtaylor committed Aug 18, 2014
1 parent 09bbb5e commit 53a9d3a
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 37 deletions.
93 changes: 56 additions & 37 deletions awscli/argprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class ParamShorthand(object):
SHORTHAND_SHAPES = {
'structure(scalars)': '_key_value_parse',
'structure(scalar)': '_special_key_value_parse',
'structure(list-scalar, scalar)': '_struct_scalar_list_parse',
'map-scalar': '_key_value_parse',
'list-structure(scalar)': '_list_scalar_parse',
'list-structure(scalars)': '_list_key_value_parse',
Expand Down Expand Up @@ -225,42 +226,51 @@ def add_example_fn(self, arg_name, help_command, **kwargs):
def _list_scalar_list_parse(self, param, value):
# Think something like ec2.DescribeInstances.Filters.
# We're looking for key=val1,val2,val3,key2=val1,val2.
parsed = []

for v in value:
struct = self._struct_scalar_list_parse(param.members, v)
parsed.append(struct)

return parsed

def _struct_scalar_list_parse(self, param, value):
# Create a mapping of argument name -> argument object
args = {}
for arg in param.members.members:
for arg in param.members:
# Arg name -> arg object lookup
args[arg.name] = arg
parsed = []
for v in value:
parts = self._split_on_commas(v)
current_parsed = {}
current_key = None
for part in parts:
current = part.split('=', 1)
if len(current) == 2:
# This is a key/value pair.
current_key = current[0].strip()
if current_key not in args:
raise ParamUnknownKeyError(param, current_key,
args.keys())
current_value = unpack_scalar_cli_arg(args[current_key],
current[1].strip())
if args[current_key].type == 'list':
current_parsed[current_key] = current_value.split(',')
else:
current_parsed[current_key] = current_value
elif current_key is not None:
# This is a value which we associate with the current_key,
# so key1=val1,val2
# ^
# |
# val2 is associated with key1.
current_value = unpack_scalar_cli_arg(args[current_key],
current[0])
current_parsed[current_key].append(current_value)

parts = self._split_on_commas(value)
current_parsed = {}
current_key = None
for part in parts:
current = part.split('=', 1)
if len(current) == 2:
# This is a key/value pair.
current_key = current[0].strip()
if current_key not in args:
raise ParamUnknownKeyError(param, current_key,
args.keys())
current_value = unpack_scalar_cli_arg(args[current_key],
current[1].strip())
if args[current_key].type == 'list':
current_parsed[current_key] = current_value.split(',')
else:
raise ParamSyntaxError(part)
parsed.append(current_parsed)
return parsed
current_parsed[current_key] = current_value
elif current_key is not None:
# This is a value which we associate with the current_key,
# so key1=val1,val2
# ^
# |
# val2 is associated with key1.
current_value = unpack_scalar_cli_arg(args[current_key],
current[0])
current_parsed[current_key].append(current_value)
else:
raise ParamSyntaxError(part)

return current_parsed

def _list_scalar_parse(self, param, value):
single_param = param.members.members[0]
Expand Down Expand Up @@ -325,11 +335,7 @@ def _create_name_to_params(self, param):
elif param.type == 'map' and hasattr(param.keys, 'enum'):
return dict([(v, None) for v in param.keys.enum])

def _docs_list_scalar_list_parse(self, param):
s = ('Key value pairs, where values are separated by commas, '
'and multiple pairs are separated by spaces.\n')
s += '%s ' % param.cli_name
inner_params = param.members.members
def _struct_list_scalar_doc_helper(self, param, inner_params):
scalar_params = [p for p in inner_params if p.type in SCALAR_TYPES]
list_params = [p for p in inner_params if p.type == 'list']
pair = ''
Expand All @@ -341,10 +347,23 @@ def _docs_list_scalar_list_parse(self, param):
last_param = list_params[-1]
param_type = last_param.members.type
pair += '%s=%s1,%s2' % (last_param.name, param_type, param_type)
return pair

def _docs_list_scalar_list_parse(self, param):
s = ('Key value pairs, where values are separated by commas, '
'and multiple pairs are separated by spaces.\n')
s += '%s ' % param.cli_name
pair = self._struct_list_scalar_doc_helper(param, param.members.members)
pair += ' %s' % pair
s += pair
return s

def _docs_struct_scalar_list_parse(self, param):
s = ('Key value pairs, where values are separated by commas.\n')
s += '%s ' % param.cli_name
s += self._struct_list_scalar_doc_helper(param, param.members)
return s

def _docs_list_scalar_parse(self, param):
name = param.members.members[0].name
return '%s %s1 %s2 %s3' % (param.cli_name, name, name, name)
Expand Down
75 changes: 75 additions & 0 deletions tests/unit/test_argprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ def assert_shape_type(self, spec, expected_type):
actual_structure = detect_shape_structure(p)
self.assertEqual(actual_structure, expected_type)

def assert_custom_shape_type(self, schema, expected_type):
argument = CustomArgument('test', schema=schema)
argument.create_argument_object()
actual_structure = detect_shape_structure(argument.argument_object)
self.assertEqual(actual_structure, expected_type)

def test_detect_scalar(self):
self.assert_shape_type('iam.AddRoleToInstanceProfile.RoleName',
'scalar')
Expand Down Expand Up @@ -115,6 +121,22 @@ def test_map_scalar(self):
self.assert_shape_type(
'sqs.SetQueueAttributes.Attributes', 'map-scalar')

def test_struct_list_scalar(self):
self.assert_custom_shape_type({
"type": "object",
"properties": {
"Consistent": {
"type": "boolean",
},
"Args": {
"type": "array",
"items": {
"type": "string"
}
}
}
}, 'structure(list-scalar, scalar)')


class TestParamShorthand(BaseArgProcessTest):
def setUp(self):
Expand Down Expand Up @@ -279,6 +301,30 @@ def test_list_structure_scalars_2(self):
])
self.assertEqual(simplified, expected)

def test_struct_list_scalars(self):
schema = {
"type": "object",
"properties": {
"Consistent": {
"type": "boolean",
},
"Args": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

argument = CustomArgument('test', schema=schema)
argument.create_argument_object()
p = argument.argument_object

returned = self.simplify(p, 'Consistent=true,Args=foo1,foo2')
self.assertEqual(returned, {'Consistent': True,
'Args': ['foo1', 'foo2']})

def test_keyval_with_long_values(self):
p = self.get_param_object(
'dynamodb.UpdateTable.ProvisionedThroughput')
Expand Down Expand Up @@ -440,6 +486,35 @@ def test_gen_list_structure_list_scalar_scalar_docs(self):
self.assertIn('Name=string1,Values=string1,string2 '
'Name=string1,Values=string1,string2', doc_string)

def test_gen_structure_list_scalar_docs(self):
schema = {
"type": "object",
"properties": {
"Consistent": {
"type": "boolean",
},
"Args": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

argument = CustomArgument('test', schema=schema)
argument.create_argument_object()

p = argument.argument_object
help_command = OperationHelpCommand(
self.session, p.operation, None, {p.cli_name: argument},
name='foo', event_class='bar')
help_command.param_shorthand.add_example_fn(p.cli_name, help_command)

doc_string = p.example_fn(p)

self.assertIn('Key value pairs', doc_string)
self.assertIn('Consistent=boolean1,Args=string1,string2', doc_string)

class TestUnpackJSONParams(BaseArgProcessTest):
def setUp(self):
Expand Down

0 comments on commit 53a9d3a

Please sign in to comment.