From 1af38ba3738a48959a48b803bd1c9a245917d9e6 Mon Sep 17 00:00:00 2001 From: Herbert Buurman Date: Tue, 9 Apr 2019 16:18:04 +0200 Subject: [PATCH 1/6] Add camel_to_snake_case and snake_to_camel_case conversion functions (with Jinja decorators). --- salt/utils/stringutils.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/salt/utils/stringutils.py b/salt/utils/stringutils.py index 4e9a4ca1fef1..e7fe1fa404c4 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(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 input: The camelcase or CamelCase string to convert to snake_case + + :return str + ''' + r = input[0].lower() + for i, letter in enumerate(input[1:], 1): + if letter.isupper(): + if input[i-1].islower() or (i != len(input)-1 and input[i+1].islower()): + r += '_' + r += letter.lower() + return r + + +@jinja_filter('to_camelcase') +def snake_to_camel_case(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 input: The input snake_case string to convert to camelCase + :param bool uppercamel: Whether or not to convert to CamelCase instead + + :return str + ''' + words = input.split('_') + if uppercamel: + words[0] = words[0].capitalize() + return words[0] + ''.join(word.capitalize() for word in words[1:]) From c1116e99916831390eb8c4ea8866c33c89568827 Mon Sep 17 00:00:00 2001 From: Herbert Buurman Date: Tue, 9 Apr 2019 16:19:02 +0200 Subject: [PATCH 2/6] Removed private snake_to_pascal and pascal_to_snake conversion functions and replaced them with the added functions in salt.utils.stringutils. --- salt/modules/testinframod.py | 37 ++++-------------------------------- salt/states/testinframod.py | 19 +++--------------- 2 files changed, 7 insertions(+), 49 deletions(-) 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..25f568dd2ead 100644 --- a/salt/states/testinframod.py +++ b/salt/states/testinframod.py @@ -4,6 +4,8 @@ import re import logging +from salt.utils.stringutils import camel_to_snake_case + log = logging.getLogger(__name__) try: @@ -36,24 +38,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] From dbeedee5019851f5f4c6185a798241636a2a0bdb Mon Sep 17 00:00:00 2001 From: Herbert Buurman Date: Tue, 9 Apr 2019 16:27:14 +0200 Subject: [PATCH 3/6] Pylint-inspired changes: Renamed parameters "input" and variable "r". --- salt/utils/stringutils.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/salt/utils/stringutils.py b/salt/utils/stringutils.py index e7fe1fa404c4..acdd70c2305d 100644 --- a/salt/utils/stringutils.py +++ b/salt/utils/stringutils.py @@ -591,36 +591,36 @@ def get_diff(a, b, *args, **kwargs): @jinja_filter('to_snake_case') -def camel_to_snake_case(input): +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 input: The camelcase or CamelCase string to convert to snake_case + :param str camel_input: The camelcase or CamelCase string to convert to snake_case :return str ''' - r = input[0].lower() - for i, letter in enumerate(input[1:], 1): + res = camel_input[0].lower() + for i, letter in enumerate(camel_input[1:], 1): if letter.isupper(): - if input[i-1].islower() or (i != len(input)-1 and input[i+1].islower()): - r += '_' - r += letter.lower() - return r + 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(input, uppercamel=False): +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 input: The input snake_case string to convert 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 = input.split('_') + words = snake_input.split('_') if uppercamel: words[0] = words[0].capitalize() return words[0] + ''.join(word.capitalize() for word in words[1:]) From 65ee13f7a528dfe7933185f48fbf7c7331ed99fc Mon Sep 17 00:00:00 2001 From: Herbert Buurman Date: Tue, 9 Apr 2019 16:36:32 +0200 Subject: [PATCH 4/6] Removed unused import. --- salt/states/testinframod.py | 1 - 1 file changed, 1 deletion(-) diff --git a/salt/states/testinframod.py b/salt/states/testinframod.py index 25f568dd2ead..51d2a209872e 100644 --- a/salt/states/testinframod.py +++ b/salt/states/testinframod.py @@ -1,7 +1,6 @@ # -*- 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 From cd9a96d80b03d4df65526d94af3f2f8e07c0d724 Mon Sep 17 00:00:00 2001 From: Herbert Buurman Date: Wed, 10 Apr 2019 10:16:44 +0200 Subject: [PATCH 5/6] Added unit tests using the Jinja filters. --- tests/unit/utils/test_jinja.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) 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. From 43896e21b9b325d43e5b06610d7c2e291777cea9 Mon Sep 17 00:00:00 2001 From: Herbert Buurman Date: Wed, 10 Apr 2019 10:18:13 +0200 Subject: [PATCH 6/6] Add documentation for the newly introduced Jinja filters. --- doc/topics/jinja/index.rst | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) 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 ------------------