Skip to content

Commit

Permalink
Move the parent_key function args to a class variable.
Browse files Browse the repository at this point in the history
  • Loading branch information
aemous committed Oct 31, 2024
1 parent e4ccd66 commit 88e1f9c
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 58 deletions.
87 changes: 39 additions & 48 deletions awscli/shorthand.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ def parse(self, value):
self._input_value = value
self._index = 0
self._resolve_paramfiles = False
self._parent_key = None
return self._parameter()

def _parameter(self):
Expand All @@ -201,13 +202,14 @@ def _keyval(self):
# file-optional-values = file://value / fileb://value / value
key = self._key()
self._resolve_paramfiles = False
self._parent_key = key
try:
self._expect('@', consume_whitespace=True)
self._resolve_paramfiles = True
except ShorthandParseSyntaxError:
pass
self._expect('=', consume_whitespace=True)
values = self._values(key)
values = self._values()
return key, values

def _key(self):
Expand All @@ -220,23 +222,23 @@ def _key(self):
self._index += 1
return self._input_value[start:self._index]

def _values(self, parent_key):
def _values(self):
if self._at_eof():
return ''
elif self._current() == '[':
return self._explicit_list(parent_key)
return self._explicit_list()
elif self._current() == '{':
return self._hash_literal()
else:
return self._csv_value(parent_key)
return self._csv_value()

def _csv_value(self, parent_key):
def _csv_value(self):
# Supports either:
# foo=bar -> 'bar'
# ^
# foo=bar,baz -> ['bar', 'baz']
# ^
first_value = self._first_value(parent_key)
first_value = self._first_value()
self._consume_whitespace()
if self._at_eof() or self._input_value[self._index] != ',':
return first_value
Expand All @@ -252,7 +254,7 @@ def _csv_value(self, parent_key):

while True:
try:
current = self._second_value(parent_key)
current = self._second_value()
self._consume_whitespace()
if self._at_eof():
csv_list.append(current)
Expand All @@ -279,22 +281,19 @@ def _csv_value(self, parent_key):
return first_value
return csv_list

def _value(self, parent_key):
def _value(self):
result = self._FIRST_VALUE.match(self._input_value[self._index:])
if result is not None:
consumed = self._consume_matched_regex(result)
return self._resolve_paramfile(
consumed.replace('\\,', ',').rstrip(),
parent_key
)
return self._resolve_paramfile_with_warnings(consumed.replace('\\,', ',').rstrip())
return ''

def _explicit_list(self, parent_key):
def _explicit_list(self):
# explicit-list = "[" [value *(",' value)] "]"
self._expect('[', consume_whitespace=True)
values = []
while self._current() != ']':
val = self._explicit_values(parent_key)
val = self._explicit_values()
values.append(val)
self._consume_whitespace()
if self._current() != ']':
Expand All @@ -303,28 +302,29 @@ def _explicit_list(self, parent_key):
self._expect(']')
return values

def _explicit_values(self, parent_key):
def _explicit_values(self):
# values = csv-list / explicit-list / hash-literal
if self._current() == '[':
return self._explicit_list(parent_key)
return self._explicit_list()
elif self._current() == '{':
return self._hash_literal()
else:
return self._first_value(parent_key)
return self._first_value()

def _hash_literal(self):
self._expect('{', consume_whitespace=True)
keyvals = {}
while self._current() != '}':
key = self._key()
self._resolve_paramfiles = False
self._parent_key = key
try:
self._expect('@', consume_whitespace=True)
self._resolve_paramfiles = True
except ShorthandParseSyntaxError:
pass
self._expect('=', consume_whitespace=True)
v = self._explicit_values(key)
v = self._explicit_values()
self._consume_whitespace()
if self._current() != '}':
self._expect(',')
Expand All @@ -333,22 +333,19 @@ def _hash_literal(self):
self._expect('}')
return keyvals

def _first_value(self, parent_key):
def _first_value(self):
# first-value = value / single-quoted-val / double-quoted-val
if self._current() == "'":
return self._single_quoted_value(parent_key)
return self._single_quoted_value()
elif self._current() == '"':
return self._double_quoted_value(parent_key)
return self._value(parent_key)
return self._double_quoted_value()
return self._value()

def _single_quoted_value(self, parent_key=None):
def _single_quoted_value(self):
# single-quoted-value = %x27 *(val-escaped-single) %x27
# val-escaped-single = %x20-26 / %x28-7F / escaped-escape /
# (escape single-quote)
return self._resolve_paramfile(
self._consume_quoted(self._SINGLE_QUOTED, escaped_char="'"),
parent_key
)
return self._resolve_paramfile_with_warnings(self._consume_quoted(self._SINGLE_QUOTED, escaped_char="'"))

def _consume_quoted(self, regex, escaped_char=None):
value = self._must_consume_regex(regex)[1:-1]
Expand All @@ -357,48 +354,42 @@ def _consume_quoted(self, regex, escaped_char=None):
value = value.replace("\\\\", "\\")
return value

def _double_quoted_value(self, parent_key=None):
return self._resolve_paramfile(
self._consume_quoted(self._DOUBLE_QUOTED, escaped_char='"'),
parent_key
)
def _double_quoted_value(self):
return self._resolve_paramfile_with_warnings(self._consume_quoted(self._DOUBLE_QUOTED, escaped_char='"'))

def _second_value(self, parent_key):
def _second_value(self):
if self._current() == "'":
return self._single_quoted_value(parent_key)
return self._single_quoted_value()
elif self._current() == '"':
return self._double_quoted_value(parent_key)
return self._double_quoted_value()
else:
consumed = self._must_consume_regex(self._SECOND_VALUE)
return self._resolve_paramfile(
consumed.replace('\\,', ',').rstrip(),
parent_key
)
return self._resolve_paramfile_with_warnings(consumed.replace('\\,', ',').rstrip())

def _resolve_paramfile(self, val, parent_key=None):
# If resolve_param_files is True, this function tries to resolve val to a
def _resolve_paramfile_with_warnings(self, val):
# If self._resolve_paramfiles is True, this function tries to resolve val to a
# paramfile (i.e. a file path prefixed with a key of LOCAL_PREFIX_MAP).
# If val is a paramfile, returns the contents of the file (retrieved
# according to the spec of get_paramfile).
# If val is not a paramfile, returns val.
# If resolve_param_files is False, returns val.
# if a parent_key is supplied, then a warning will be printed if resolve_param_files=False
# If self._resolve_paramfiles is False, returns val.
# A warning will be printed if self._resolve_paramfiles=False
# and the value is parsed to a string that starts with a file prefix
if (self._resolve_paramfiles and
(paramfile := get_paramfile(val, LOCAL_PREFIX_MAP)) is not None):
return paramfile
if parent_key is not None and not self._resolve_paramfiles:
self._print_file_warnings_if_prefixed(parent_key, val)
if self._parent_key is not None and not self._resolve_paramfiles:
self._print_file_warnings_if_prefixed(val)
return val

def _print_file_warnings_if_prefixed(self, key, val):
def _print_file_warnings_if_prefixed(self,val):
for prefix in LOCAL_PREFIX_MAP.keys():
if val.startswith(prefix):
_LOGGER.warning(f'Usage of the {prefix} prefix was detected '
f'without the file assignment operator in parameter {key}. '
f'without the file assignment operator in parameter {self._parent_key}. '
f'To load nested parameters from a file, you must use the file '
f'assignment operator \'{_FILE_ASSIGNMENT}\'. For example, '
f'{key}{_FILE_ASSIGNMENT}<...>.')
f'{self._parent_key}{_FILE_ASSIGNMENT}<...>.')
break

def _expect(self, char, consume_whitespace=False):
Expand Down
10 changes: 0 additions & 10 deletions tests/unit/test_shorthand.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,20 +74,16 @@
# Single quoted strings.
("foo='bar'", {"foo": "bar"}),
("foo='bar,baz'", {"foo": "bar,baz"}),
("'foo'=bar", {"foo": "bar"}),
("'foo,bar'='bar,bas'", {"foo,bar": "bar,bas"}),
# Single quoted strings for each value in a CSV list.
("foo='bar','baz'", {"foo": ['bar', 'baz']}),
# Can mix single quoted and non quoted values.
("foo=bar,'baz'", {"foo": ['bar', 'baz']}),
# Quoted strings can include chars not allowed in unquoted strings.
("foo=bar,'baz=qux'", {"foo": ['bar', 'baz=qux']}),
("foo=bar,'--option=bar space'", {"foo": ['bar', '--option=bar space']}),
("'foo@=key'=bar", {"foo@=key": "bar"}),
# Can escape the single quote.
("foo='bar\\'baz'", {"foo": "bar'baz"}),
("foo='bar\\\\baz'", {"foo": "bar\\baz"}),
("'foo\\'bar'=bar", {"foo'bar": "bar"}),
# Double quoted strings.
('foo="bar"', {'foo': 'bar'}),
('foo="bar,baz"', {'foo': 'bar,baz'}),
Expand All @@ -96,21 +92,18 @@
('foo=bar,"--option=bar space"', {'foo': ['bar', '--option=bar space']}),
('foo="bar\\"baz"', {'foo': 'bar"baz'}),
('foo="bar\\\\baz"', {'foo': 'bar\\baz'}),
("\"foo\\\"bar\"='bar,bas'", {"foo\"bar": "bar,bas"}),
# Can escape comma in CSV list.
('foo=a\\,b', {"foo": "a,b"}),
('foo=a\\,b', {"foo": "a,b"}),
('foo=a\\,', {"foo": "a,"}),
('foo=\\,', {"foo": ","}),
('foo=a,b\\,c', {"foo": ['a', 'b,c']}),
('"foo,"=a,b\\,c', {"foo,": ['a', 'b,c']}),
('foo=a,b\\,', {"foo": ['a', 'b,']}),
('foo=a,\\,bc', {"foo": ['a', ',bc']}),
# Ignores whitespace around '=' and ','
('foo= bar', {'foo': 'bar'}),
('foo =bar', {'foo': 'bar'}),
('foo = bar', {'foo': 'bar'}),
('"foo = key" = bar', {'foo = key': 'bar'}),
('foo = bar', {'foo': 'bar'}),
('foo = bar,baz = qux', {'foo': 'bar', 'baz': 'qux'}),
('a = b, c = d , e = f', {'a': 'b', 'c': 'd', 'e': 'f'}),
Expand All @@ -123,7 +116,6 @@
('A=b,\nC=d,\nE=f\n', {'A': 'b', 'C': 'd', 'E': 'f'}),
# Hashes
('Name={foo=bar,baz=qux}', {'Name': {'foo': 'bar', 'baz': 'qux'}}),
('Name={\'foo=\'=bar,baz=qux}', {'Name': {'foo=': 'bar', 'baz': 'qux'}}),
('Name={foo=[a,b,c],bar=baz}', {'Name': {'foo': ['a', 'b', 'c'], 'bar': 'baz'}}),
('Name={foo=bar},Bar=baz', {'Name': {'foo': 'bar'}, 'Bar': 'baz'}),
('Bar=baz,Name={foo=bar}', {'Bar': 'baz', 'Name': {'foo': 'bar'}}),
Expand All @@ -145,11 +137,9 @@
('foo=a,b@=with space', {'foo': 'a', 'b': 'with space'}),
('foo=a,b@=with trailing space ', {'foo': 'a', 'b': 'with trailing space'}),
('aws:service:region:124:foo/bar@=baz', {'aws:service:region:124:foo/bar': 'baz'}),
('"aws:service:region:124:foo/bar@=baz"@=baz', {'aws:service:region:124:foo/bar@=baz': 'baz'}),
('foo=[a,b],bar@=[c,d]', {'foo': ['a', 'b'], 'bar': ['c', 'd']}),
('foo @= [ a , b , c ]', {'foo': ['a', 'b', 'c']}),
('A=b,\nC@=d,\nE@=f\n', {'A': 'b', 'C': 'd', 'E': 'f'}),
('A=b,"\nC"@=d,\nE@=f\n', {'A': 'b', '\nC': 'd', 'E': 'f'}),
('Bar@=baz,Name={foo@=bar}', {'Bar': 'baz', 'Name': {'foo': 'bar'}}),
('Name=[{foo@=bar}, {baz=qux}]', {'Name': [{'foo': 'bar'}, {'baz': 'qux'}]}),
(
Expand Down

0 comments on commit 88e1f9c

Please sign in to comment.