From 0aee02eb970c46332799edd755e7b74938598cf5 Mon Sep 17 00:00:00 2001 From: Federico Chiacchiaretta Date: Fri, 4 Nov 2022 19:13:13 +0100 Subject: [PATCH] Welcome to support for indentation with spaces in to_xml plugin (#192) * Welcome to support for indentation with spaces in to_xml plugin * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Changelog fragment * Unit tests for: * Indent with spaces and explicit indent_width (4) * Invalid indent I also slightly modified VALID_DATA to produce output with indentation, so existing test_valid_data and OUTPUT has been updated accordingly. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix changelog section name to minor_changes Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../fragments/to_xml_indent_with_spaces.yaml | 5 ++ docs/ansible.utils.to_xml_filter.rst | 73 +++++++++++++++++++ plugins/filter/to_xml.py | 46 +++++++++++- plugins/plugin_utils/to_xml.py | 12 ++- tests/unit/plugins/filter/test_to_xml.py | 38 +++++++++- 5 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/to_xml_indent_with_spaces.yaml diff --git a/changelogs/fragments/to_xml_indent_with_spaces.yaml b/changelogs/fragments/to_xml_indent_with_spaces.yaml new file mode 100644 index 0000000..c06a3cb --- /dev/null +++ b/changelogs/fragments/to_xml_indent_with_spaces.yaml @@ -0,0 +1,5 @@ +--- +minor_changes: + - >- + to_xml - Added support for using spaces to indent an XML doc via a new + `indent` parameter. diff --git a/docs/ansible.utils.to_xml_filter.rst b/docs/ansible.utils.to_xml_filter.rst index 40d3490..022fcb2 100644 --- a/docs/ansible.utils.to_xml_filter.rst +++ b/docs/ansible.utils.to_xml_filter.rst @@ -73,6 +73,47 @@ Parameters
Conversion library to use within the filter plugin.
+ + +
+ indent + +
+ string +
+ + + + + + + +
The character used for indentation (defaults to tabs).
+ + + + +
+ indent_width + +
+ integer +
+ + + Default:
4
+ + + + +
The number of spaces to use to indent output data.
+
This option is only used when indent="spaces", otherwise it is ignored.
+
When indent="tabs", a single tab is always used for indentation.
+ +
@@ -149,6 +190,38 @@ Examples # Cisco-IOS-XR-ifmgr-cfg\">\n\t\n" # } + #### example3 with indent='spaces' and indent_width=2 + + - name: Define JSON data + ansible.builtin.set_fact: + data: + "interface-configurations": + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" + "interface-configuration": + - debug: + msg: "{{ data|ansible.utils.to_xml(indent='spaces', indent_width=2) }}" + + # TASK [Define JSON data ] ************************************************************************* + # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 + # ok: [localhost] => { + # "ansible_facts": { + # "data": { + # "interface-configurations": { + # "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + # "interface-configuration": null + # } + # } + # }, + # "changed": false + # } + # TASK [debug] *********************************************************************************************************** + # task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 + # Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils + # ok: [localhost] => { + # "\n\n \n" + # } + diff --git a/plugins/filter/to_xml.py b/plugins/filter/to_xml.py index 555ba62..d8281e8 100644 --- a/plugins/filter/to_xml.py +++ b/plugins/filter/to_xml.py @@ -35,6 +35,19 @@ DOCUMENTATION = """ - Conversion library to use within the filter plugin. type: str default: xmltodict + indent: + description: + - The character used for indentation (defaults to tabs). + type: str + default: tabs + choices: ["tabs", "spaces"] + indent_width: + description: + - The number of spaces to use to indent output data. + - This option is only used when indent="spaces", otherwise it is ignored. + - When indent="tabs", a single tab is always used for indentation. + type: int + default: 4 """ EXAMPLES = r""" @@ -104,6 +117,37 @@ EXAMPLES = r""" # Cisco-IOS-XR-ifmgr-cfg\">\n\t\n" # } +#### example3 with indent='spaces' and indent_width=2 + +- name: Define JSON data + ansible.builtin.set_fact: + data: + "interface-configurations": + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg" + "interface-configuration": +- debug: + msg: "{{ data|ansible.utils.to_xml(indent='spaces', indent_width=2) }}" + +# TASK [Define JSON data ] ************************************************************************* +# task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:5 +# ok: [localhost] => { +# "ansible_facts": { +# "data": { +# "interface-configurations": { +# "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", +# "interface-configuration": null +# } +# } +# }, +# "changed": false +# } +# TASK [debug] *********************************************************************************************************** +# task path: /Users/amhatre/ansible-collections/playbooks/test_utils_json_to_xml.yaml:13 +# Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +# ok: [localhost] => { +# "\n\n \n" +# } """ from ansible.errors import AnsibleFilterError @@ -123,7 +167,7 @@ except ImportError: @pass_environment def _to_xml(*args, **kwargs): """Convert the given data from json to xml.""" - keys = ["data", "engine"] + keys = ["data", "engine", "indent", "indent_width"] data = dict(zip(keys, args[1:])) data.update(kwargs) aav = AnsibleArgSpecValidator(data=data, schema=DOCUMENTATION, name="to_xml") diff --git a/plugins/plugin_utils/to_xml.py b/plugins/plugin_utils/to_xml.py index 6004fa6..1c3f882 100644 --- a/plugins/plugin_utils/to_xml.py +++ b/plugins/plugin_utils/to_xml.py @@ -35,18 +35,26 @@ def _raise_error(msg): raise AnsibleFilterError(error) -def to_xml(data, engine): +def to_xml(data, engine, indent, indent_width): """Convert data which is in json to xml" :param data: The data passed in (data|to_xml(...)) :type data: xml :param engine: Conversion library default=xmltodict + :param indent: Indent char default='tabs' + :param indent_width: Indent char multiplier default=4 """ + + indent_char = "\t" + + if indent == "spaces": + indent_char = " " * indent_width + if engine == "xmltodict": if not HAS_XMLTODICT: _raise_error("Missing required library xmltodict") try: - res = xmltodict.unparse(data, pretty=True) + res = xmltodict.unparse(data, pretty=True, indent=indent_char) except Exception: _raise_error("Input json is not valid") return res diff --git a/tests/unit/plugins/filter/test_to_xml.py b/tests/unit/plugins/filter/test_to_xml.py index 81fd6a3..f1ce6fb 100644 --- a/tests/unit/plugins/filter/test_to_xml.py +++ b/tests/unit/plugins/filter/test_to_xml.py @@ -18,11 +18,21 @@ from ansible_collections.ansible.utils.plugins.filter.to_xml import _to_xml INVALID_DATA = '' VALID_DATA = { - "interface-configurations": {"@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg"}, + "interface-configurations": { + "@xmlns": "http://cisco.com/ns/yang/Cisco-IOS-XR-ifmgr-cfg", + "key1": "value1", + }, } -OUTPUT = """ -""" +OUTPUT_TABS = """ + +\tvalue1 +""" + +OUTPUT_SPACES = """ + + value1 +""" class TestToXml(unittest.TestCase): @@ -44,7 +54,7 @@ class TestToXml(unittest.TestCase): self.maxDiff = None args = ["", VALID_DATA, "xmltodict"] result = _to_xml(*args) - self.assertEqual(result, OUTPUT) + self.assertEqual(result, OUTPUT_TABS) def test_args(self): """Check passing invalid argspec""" @@ -65,3 +75,23 @@ class TestToXml(unittest.TestCase): with self.assertRaises(AnsibleError) as error: _to_xml(*args, **kwargs) self.assertIn("engine: test is not supported", str(error.exception)) + + def test_indent_with_spaces(self): + """Check passing indent with spaces and default indent_width""" + self.maxDiff = None + args = ["", VALID_DATA, "xmltodict", "spaces", 4] + result = _to_xml(*args) + self.assertEqual(result, OUTPUT_SPACES) + + def test_invalid_indent(self): + """Check passing invalid indent value""" + + # missing required arguments + args = ["", VALID_DATA, "xmltodict", "test"] + kwargs = {} + with self.assertRaises(AnsibleError) as error: + _to_xml(*args, **kwargs) + self.assertIn( + "value of indent must be one of: tabs, spaces, got: test", + str(error.exception), + )