From 5f73bdc3bf3821c957cddbf81febfc13b3de9af9 Mon Sep 17 00:00:00 2001 From: Will Thames Date: Wed, 2 Aug 2017 01:09:22 +1000 Subject: [PATCH] [cloud] Improve Camel to Snake conversion in EC2 module_utils (#25015) * Make camel_to_snake work on capitalized plurals `TargetGroupARNs` should become `target_group_arns`, not `target_group_ar_ns` Promote `camel_to_snake` to top layer function but prefix it with an underscore. Add tests for improved `_camel_to_snake` function. Reduce use of `re.compile` as it makes no sense when the compilation result is not reused. * Remove unused LooseVersion check * Fix PLURALs case for camel_to_snake Also renamed EXPECTED_CAMELIZATION to EXPECTED_SNAKIFICATION --- lib/ansible/module_utils/ec2.py | 42 ++++++++++--------- .../module_utils/ec2/test_camel_to_snake.py | 37 ++++++++++++++++ 2 files changed, 60 insertions(+), 19 deletions(-) create mode 100644 test/units/module_utils/ec2/test_camel_to_snake.py diff --git a/lib/ansible/module_utils/ec2.py b/lib/ansible/module_utils/ec2.py index 4186128579..6506fc6d9b 100644 --- a/lib/ansible/module_utils/ec2.py +++ b/lib/ansible/module_utils/ec2.py @@ -47,12 +47,6 @@ try: except: HAS_BOTO3 = False -try: - from distutils.version import LooseVersion - HAS_LOOSE_VERSION = True -except: - HAS_LOOSE_VERSION = False - from ansible.module_utils.six import string_types, binary_type, text_type @@ -345,18 +339,28 @@ def paging(pause=0, marker_property='marker'): return wrapper +def _camel_to_snake(name): + + def prepend_underscore_and_lower(m): + return '_' + m.group(0).lower() + + import re + # Cope with pluralized abbreviations such as TargetGroupARNs + # that would otherwise be rendered target_group_ar_ns + plural_pattern = r'[A-Z]{3,}s$' + s1 = re.sub(plural_pattern, prepend_underscore_and_lower, name) + # Handle when there was nothing before the plural_pattern + if s1.startswith("_") and not name.startswith("_"): + s1 = s1[1:] + # Remainder of solution seems to be https://stackoverflow.com/a/1176023 + first_cap_pattern = r'(.)([A-Z][a-z]+)' + all_cap_pattern = r'([a-z0-9])([A-Z]+)' + s2 = re.sub(first_cap_pattern, r'\1_\2', s1) + return re.sub(all_cap_pattern, r'\1_\2', s2).lower() + + def camel_dict_to_snake_dict(camel_dict): - def camel_to_snake(name): - - import re - - first_cap_re = re.compile('(.)([A-Z][a-z]+)') - all_cap_re = re.compile('([a-z0-9])([A-Z])') - s1 = first_cap_re.sub(r'\1_\2', name) - - return all_cap_re.sub(r'\1_\2', s1).lower() - def value_is_list(camel_list): checked_list = [] @@ -373,11 +377,11 @@ def camel_dict_to_snake_dict(camel_dict): snake_dict = {} for k, v in camel_dict.items(): if isinstance(v, dict): - snake_dict[camel_to_snake(k)] = camel_dict_to_snake_dict(v) + snake_dict[_camel_to_snake(k)] = camel_dict_to_snake_dict(v) elif isinstance(v, list): - snake_dict[camel_to_snake(k)] = value_is_list(v) + snake_dict[_camel_to_snake(k)] = value_is_list(v) else: - snake_dict[camel_to_snake(k)] = v + snake_dict[_camel_to_snake(k)] = v return snake_dict diff --git a/test/units/module_utils/ec2/test_camel_to_snake.py b/test/units/module_utils/ec2/test_camel_to_snake.py new file mode 100644 index 0000000000..0bb5d6829e --- /dev/null +++ b/test/units/module_utils/ec2/test_camel_to_snake.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# (c) 2017, Will Thames +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +from ansible.compat.tests import unittest +from ansible.module_utils.ec2 import _camel_to_snake + +EXPECTED_SNAKIFICATION = { + 'alllower': 'alllower', + 'TwoWords': 'two_words', + 'AllUpperAtEND': 'all_upper_at_end', + 'AllUpperButPLURALs': 'all_upper_but_plurals', + 'TargetGroupARNs': 'target_group_arns', + 'HTTPEndpoints': 'http_endpoints', + 'PLURALs': 'plurals' +} + + +class CamelToSnakeTestCase(unittest.TestCase): + + def test_camel_to_snake(self): + for (k, v) in EXPECTED_SNAKIFICATION.items(): + self.assertEqual(_camel_to_snake(k), v)