forked from ansible/ansible
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move camel2snake and snake2camel to common/dict_transformations.py (a…
…nsible#39498) Since it will be used outside just AWS modules, this commit moves `camel_dict_to_snake_dict` and `snake_dict_to_camel_dict` functions into a new module_utils file under common/ to match their wider usage.
- Loading branch information
Showing
2 changed files
with
111 additions
and
97 deletions.
There are no files selected for viewing
107 changes: 107 additions & 0 deletions
107
lib/ansible/module_utils/common/dict_transformations.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright: (c) 2018, Ansible Project | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
from __future__ import absolute_import, division, print_function | ||
__metaclass__ = type | ||
|
||
|
||
import re | ||
|
||
|
||
def camel_dict_to_snake_dict(camel_dict, reversible=False, ignore_list=()): | ||
""" | ||
reversible allows two way conversion of a camelized dict | ||
such that snake_dict_to_camel_dict(camel_dict_to_snake_dict(x)) == x | ||
This is achieved through mapping e.g. HTTPEndpoint to h_t_t_p_endpoint | ||
where the default would be simply http_endpoint, which gets turned into | ||
HttpEndpoint if recamelized. | ||
ignore_list is used to avoid converting a sub-tree of a dict. This is | ||
particularly important for tags, where keys are case-sensitive. We convert | ||
the 'Tags' key but nothing below. | ||
""" | ||
|
||
def value_is_list(camel_list): | ||
|
||
checked_list = [] | ||
for item in camel_list: | ||
if isinstance(item, dict): | ||
checked_list.append(camel_dict_to_snake_dict(item, reversible)) | ||
elif isinstance(item, list): | ||
checked_list.append(value_is_list(item)) | ||
else: | ||
checked_list.append(item) | ||
|
||
return checked_list | ||
|
||
snake_dict = {} | ||
for k, v in camel_dict.items(): | ||
if isinstance(v, dict) and k not in ignore_list: | ||
snake_dict[_camel_to_snake(k, reversible=reversible)] = camel_dict_to_snake_dict(v, reversible) | ||
elif isinstance(v, list) and k not in ignore_list: | ||
snake_dict[_camel_to_snake(k, reversible=reversible)] = value_is_list(v) | ||
else: | ||
snake_dict[_camel_to_snake(k, reversible=reversible)] = v | ||
|
||
return snake_dict | ||
|
||
|
||
def snake_dict_to_camel_dict(snake_dict, capitalize_first=False): | ||
""" | ||
Perhaps unexpectedly, snake_dict_to_camel_dict returns dromedaryCase | ||
rather than true CamelCase. Passing capitalize_first=True returns | ||
CamelCase. The default remains False as that was the original implementation | ||
""" | ||
|
||
def camelize(complex_type, capitalize_first=False): | ||
if complex_type is None: | ||
return | ||
new_type = type(complex_type)() | ||
if isinstance(complex_type, dict): | ||
for key in complex_type: | ||
new_type[_snake_to_camel(key, capitalize_first)] = camelize(complex_type[key], capitalize_first) | ||
elif isinstance(complex_type, list): | ||
for i in range(len(complex_type)): | ||
new_type.append(camelize(complex_type[i], capitalize_first)) | ||
else: | ||
return complex_type | ||
return new_type | ||
|
||
return camelize(snake_dict, capitalize_first) | ||
|
||
|
||
def _snake_to_camel(snake, capitalize_first=False): | ||
if capitalize_first: | ||
return ''.join(x.capitalize() or '_' for x in snake.split('_')) | ||
else: | ||
return snake.split('_')[0] + ''.join(x.capitalize() or '_' for x in snake.split('_')[1:]) | ||
|
||
|
||
def _camel_to_snake(name, reversible=False): | ||
|
||
def prepend_underscore_and_lower(m): | ||
return '_' + m.group(0).lower() | ||
|
||
import re | ||
if reversible: | ||
upper_pattern = r'[A-Z]' | ||
else: | ||
# Cope with pluralized abbreviations such as TargetGroupARNs | ||
# that would otherwise be rendered target_group_ar_ns | ||
upper_pattern = r'[A-Z]{3,}s$' | ||
|
||
s1 = re.sub(upper_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:] | ||
if reversible: | ||
return s1 | ||
|
||
# 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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters