diff --git a/doc/topics/jinja/index.rst b/doc/topics/jinja/index.rst index cd197acd1f75..bd4a1e79c4a9 100644 --- a/doc/topics/jinja/index.rst +++ b/doc/topics/jinja/index.rst @@ -1309,6 +1309,49 @@ Returns: .. _`JMESPath language`: http://jmespath.org/ .. _`jmespath`: https://github.com/jmespath/jmespath.py + +.. jinja_ref:: to_snake_case + +``to_snake_case`` +----------------- + +.. versionadded:: Neon + +Converts a string from camelCase (or CamelCase) to snake_case. + +.. code-block:: jinja + + Example: {{ camelsWillLoveThis | to_snake_case }} + +Returns: + +.. code-block:: text + + Example: camels_will_love_this + + +.. jinja_ref:: to_camelcase + +``to_camelcase`` +---------------- + +.. versionadded:: Neon + +Converts a string from snake_case to camelCase (or UpperCamelCase if so indicated). + +.. code-block:: jinja + + Example 1: {{ snake_case_for_the_win | to_camelcase }} + + Example 2: {{ snake_case_for_the_win | to_camelcase(uppercamel=True) }} + +Returns: + +.. code-block:: text + + Example 1: snakeCaseForTheWin + Example 2: SnakeCaseForTheWin + Networking Filters ------------------ diff --git a/salt/modules/testinframod.py b/salt/modules/testinframod.py index da9121787312..6620cc52e41e 100644 --- a/salt/modules/testinframod.py +++ b/salt/modules/testinframod.py @@ -14,6 +14,8 @@ import re import types +from salt.utils.stringutils import camel_to_snake_case, snake_to_camel_case + log = logging.getLogger(__name__) try: @@ -51,38 +53,7 @@ def _get_module(module_name, backend=default_backend): """ backend_instance = testinfra.get_backend(backend) - return backend_instance.get_module(_to_pascal_case(module_name)) - - -def _to_pascal_case(snake_case): - """Convert a snake_case string to its PascalCase equivalent. - - :param snake_case: snake_cased string to be converted - :returns: PascalCase string - :rtype: str - - """ - space_case = re.sub('_', ' ', snake_case) - wordlist = [] - for word in space_case.split(): - wordlist.append(word[0].upper()) - wordlist.append(word[1:]) - return ''.join(wordlist) - - -def _to_snake_case(pascal_case): - """Convert a PascalCase string to its snake_case equivalent. - - :param pascal_case: PascalCased string to be converted - :returns: snake_case string - :rtype: str - - """ - snake_case = re.sub('(^|[a-z])([A-Z])', - lambda match: '{0}_{1}'.format(match.group(1).lower(), - match.group(2).lower()), - pascal_case) - return snake_case.lower().strip('_') + return backend_instance.get_module(snake_to_camel_case(module_name, uppercamel=True)) def _get_method_result(module_, module_instance, method_name, method_arg=None): @@ -297,7 +268,7 @@ def _register_functions(): can be called via salt. """ try: - modules_ = [_to_snake_case(module_) for module_ in modules.__all__] + modules_ = [camel_to_snake_case(module_) for module_ in modules.__all__] except AttributeError: modules_ = [module_ for module_ in modules.modules] diff --git a/salt/states/testinframod.py b/salt/states/testinframod.py index 7f62dbb901eb..51d2a209872e 100644 --- a/salt/states/testinframod.py +++ b/salt/states/testinframod.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, unicode_literals, print_function -import re import logging +from salt.utils.stringutils import camel_to_snake_case + log = logging.getLogger(__name__) try: @@ -36,24 +37,9 @@ def _module_function_wrapper(name, **methods): return _module_function_wrapper -def _to_snake_case(pascal_case): - """Convert a PascalCase string to its snake_case equivalent. - - :param pascal_case: PascalCased string to be converted - :returns: snake_case string - :rtype: str - - """ - snake_case = re.sub('(^|[a-z])([A-Z])', - lambda match: '{0}_{1}'.format(match.group(1).lower(), - match.group(2).lower()), - pascal_case) - return snake_case.lower().strip('_') - - def _generate_functions(): try: - modules_ = [_to_snake_case(module_) for module_ in modules.__all__] + modules_ = [camel_to_snake_case(module_) for module_ in modules.__all__] except AttributeError: modules_ = [module_ for module_ in modules.modules] diff --git a/salt/utils/stringutils.py b/salt/utils/stringutils.py index 4e9a4ca1fef1..acdd70c2305d 100644 --- a/salt/utils/stringutils.py +++ b/salt/utils/stringutils.py @@ -588,3 +588,39 @@ def get_diff(a, b, *args, **kwargs): *args, **kwargs ) ) + + +@jinja_filter('to_snake_case') +def camel_to_snake_case(camel_input): + ''' + Converts camelCase (or CamelCase) to snake_case. + From https://codereview.stackexchange.com/questions/185966/functions-to-convert-camelcase-strings-to-snake-case + + :param str camel_input: The camelcase or CamelCase string to convert to snake_case + + :return str + ''' + res = camel_input[0].lower() + for i, letter in enumerate(camel_input[1:], 1): + if letter.isupper(): + if camel_input[i-1].islower() or (i != len(camel_input)-1 and camel_input[i+1].islower()): + res += '_' + res += letter.lower() + return res + + +@jinja_filter('to_camelcase') +def snake_to_camel_case(snake_input, uppercamel=False): + ''' + Converts snake_case to camelCase (or CamelCase if uppercamel is ``True``). + Inspired by https://codereview.stackexchange.com/questions/85311/transform-snake-case-to-camelcase + + :param str snake_input: The input snake_case string to convert to camelCase + :param bool uppercamel: Whether or not to convert to CamelCase instead + + :return str + ''' + words = snake_input.split('_') + if uppercamel: + words[0] = words[0].capitalize() + return words[0] + ''.join(word.capitalize() for word in words[1:]) diff --git a/tests/unit/utils/test_jinja.py b/tests/unit/utils/test_jinja.py index f54e3bc8523d..63c3631a0685 100644 --- a/tests/unit/utils/test_jinja.py +++ b/tests/unit/utils/test_jinja.py @@ -943,6 +943,26 @@ def test_sequence(self): .render(data={'foo': 'bar'}) self.assertEqual(rendered, '1') + def test_camel_to_snake_case(self): + ''' + Test the `to_snake_case` Jinja filter. + ''' + rendered = render_jinja_tmpl('{{ \'abcdEfghhIjkLmnoP\' | to_snake_case }}', + dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)) + self.assertEqual(rendered, 'abcd_efghh_ijk_lmno_p') + + def test_snake_to_camel_case(self): + ''' + Test the `to_camelcase` Jinja filter. + ''' + rendered = render_jinja_tmpl('{{ \'the_fox_jumped_over_the_lazy_dog\' | to_camelcase }}', + dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)) + self.assertEqual(rendered, 'theFoxJumpedOverTheLazyDog') + + rendered = render_jinja_tmpl('{{ \'the_fox_jumped_over_the_lazy_dog\' | to_camelcase(uppercamel=True) }}', + dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)) + self.assertEqual(rendered, 'TheFoxJumpedOverTheLazyDog') + def test_is_ip(self): ''' Test the `is_ip` Jinja filter.