diff --git a/yaltik_dsl/Makefile b/yaltik_dsl/Makefile new file mode 100644 index 0000000..24bc2cc --- /dev/null +++ b/yaltik_dsl/Makefile @@ -0,0 +1,9 @@ +.PHONY: clean +clean: + find . -name '*.pyc' -delete + find . -name '__pycache__' -delete + +.PHONY: test +test: + python3 tests/test_xml_base.py + python3 tests/test_odoo.py diff --git a/yaltik_dsl/__init__.py b/yaltik_dsl/__init__.py new file mode 100644 index 0000000..7eb5290 --- /dev/null +++ b/yaltik_dsl/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019-2020 Fabien Bourgeois +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from . import src diff --git a/yaltik_dsl/__manifest__.py b/yaltik_dsl/__manifest__.py new file mode 100644 index 0000000..47cc362 --- /dev/null +++ b/yaltik_dsl/__manifest__.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +# Copyright 2019-2020 Fabien Bourgeois +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +{ + 'name': 'Yaltik Odoo DSL base module and fns', + 'summary': 'Yaltik Odoo Domain Specific Language base module and functions', + 'description': """ Yaltik Odoo Domain Specific Language base module and functions """, + 'version': '12.0.0.4.0', + 'category': 'Yaltik', + 'author': 'Fabien Bourgeois', + 'license': 'AGPL-3', + 'application': False, + 'installable': True, + 'depends': ['base'], + "external_dependencies": {'python' : ['function-pattern-matching']} +} diff --git a/yaltik_dsl/src/__init__.py b/yaltik_dsl/src/__init__.py new file mode 100644 index 0000000..b0fa7d4 --- /dev/null +++ b/yaltik_dsl/src/__init__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Fabien Bourgeois +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from . import odoo_dsl diff --git a/yaltik_dsl/src/odoo_dsl.py b/yaltik_dsl/src/odoo_dsl.py new file mode 100644 index 0000000..24b4123 --- /dev/null +++ b/yaltik_dsl/src/odoo_dsl.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2019-2020 Fabien Bourgeois +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" Odoo XML DSL """ + +from .xml_base import xmlroot, xmln + +# XML helpers functions and macros + +# Odoo root XML Node +odoo = lambda children: xmlroot({'tag': 'odoo', 'attrs': {}, 'children': children}) + +# Special data Node +def data(*args): + """ Allow optional args on data tag """ + if len(args) == 1: + args = list(args) + args.insert(0, {}) + return xmln('data', *args) + +# Aliases +function = lambda *args: xmln('function', *args) +record = lambda *args: xmln('record', *args) +form = lambda *args: xmln('form', *args) +tree = lambda *args: xmln('tree', *args) +search = lambda *args: xmln('search', *args) + +# Actions +act_window = lambda *args: xmln('act_window', *args) +def act_window_model(model, attrs): + """ Build new act_window from model and args """ + xmlid = '%s_view_action' % (model.replace('.', '_')) + name = '%s Action' % ' '.join(map(lambda w: w.capitalize(), model.split('.'))) + attrs_clone = attrs.copy() # Avoid side-effect + attrs_clone.update({'id': xmlid, 'name': name, 'res_model': model}) + return act_window(attrs_clone) + +def action_server_code(xmlid, name, modelref, code): + """ Server actions of type code """ + children = [field_name(name), + field({'name': 'model_id', 'ref': modelref}, []), + field({'name': 'state'}, ['code']), + field({'name': 'code'}, [code])] + return record({'id': xmlid, 'model': 'ir.actions.server'}, children) + +def client_action_multi(xmlid, name, model, action): + """ Client action multi (ir.values), with own xmlid, name, targeted model + and action """ + action = "'ir.actions.server,%d'%{}".format(action) + children = [field_name(name), + field({'name': 'key2', 'eval': "'client_action_multi'"}), + field({'name': 'model', 'eval': "'%s'" % model}), + field({'name': 'value', 'eval': action})] + return record({'id': xmlid, 'model': 'ir.values'}, children) + +# Menus +menuitem = lambda *args: xmln('menuitem', *args) +def menuitem_model(model, attrs): + """ Build new menuitem from model and attrs """ + model_und = model.replace('.', '_') + xmlid = '%s_menu' % model_und + actionid = '%s_view_action' % model_und + attrs_clone = attrs.copy() # Avoid side-effect + attrs_clone.update({'id': xmlid, 'action': actionid}) + return menuitem(attrs_clone) + +# Form aliases +group = lambda *args: xmln('group', *args) +header = lambda *args: xmln('header', *args) +footer = lambda *args: xmln('footer', *args) +sheet = lambda *args: xmln('sheet', *args) +button = lambda *args: xmln('button', *args) +p = lambda *args: xmln('p', *args) +xpath = lambda *args: xmln('xpath', *args) +attribute = lambda name, value: xmln('attribute', {'name': name}, [value]) + +# Fields +field = lambda *args: xmln('field', *args) +field_name = lambda name: field({'name': 'name'}, [name]) +field_model = lambda model: field({'name': 'model'}, [model]) +field_inherit = lambda xmlid: field({'name': 'inherit_id', 'ref': xmlid}, []) +field_arch = lambda *args: field({'name': 'arch', 'type': 'xml'}, *args) + +# Search +filter = lambda *args: xmln('filter', *args) + +# Views +view = lambda xmlid, children: record({'id': xmlid, 'model': 'ir.ui.view'}, children) + +def view_def(xmlid, name, model, arch): + """ Shortcut for new view """ + return view(xmlid, [field_name(name), field_model(model), field_arch(arch)]) + +def view_new(view_type, model, arch): + """ View : new view definition, based on type (form, tree, ...) and model ID """ + model_und = model.replace('.', '_') + model_cap = ' '.join(map(lambda w: w.capitalize(), model.split('.'))) + xmlid = "%s_view_%s" % (model_und, view_type) + name = ' '.join([model_cap, view_type.capitalize()]) + return view_def(xmlid, name, model, arch) + +def view_inherit(filename, model, inherit, arch): + """ Inherited View simplification with name of the record, xmlid for model + and inherited view """ + module = filename.split('.')[2] + inherited = inherit.split('.')[1] + xmlid = '%s_inherit_%s' % (inherited, module) + model_cap = ' '.join(map(lambda w: w.capitalize(), model.split('.'))) + name = '%s Adaptations' % model_cap + return view(xmlid, [field_name(name), field_model(model), + field_inherit(inherit), field_arch(arch)]) diff --git a/yaltik_dsl/src/xml_base.py b/yaltik_dsl/src/xml_base.py new file mode 100644 index 0000000..426c927 --- /dev/null +++ b/yaltik_dsl/src/xml_base.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2019-2020 Fabien Bourgeois +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" XML helpers and macros """ + +from os import path +import xml.etree.ElementTree as ET +from xml.dom import minidom +from collections import namedtuple +from functools import partial + +XMLDictElement = namedtuple('XMLDictElement', ['tag', 'attrs', 'children']) + + +def xmlroot(tree): + """ Special process for root XML Node """ + rootel = ET.Element(tree['tag'], tree['attrs']) + if 'children' in tree: + xmlchild(rootel, tree['children']) + return rootel + +def xmlchild(parent, children): + """ Handling of children (ie non root) XML Nodes with/o text and + subchildren (recursive) """ + if isinstance(children, str): + parent.text = children + elif isinstance(children, XMLDictElement): + attrs = {str(k): str(v) for [k, v] in children.attrs.items()} + new_parent = ET.SubElement(parent, children.tag, attrs) + subchildren = children.children + if subchildren: + xmlchild(new_parent, subchildren) + elif isinstance(children, list): + list(map(partial(xmlchild, parent), children)) + else: + raise TypeError('Invalid arguments for xmlchild') + +def xmln(tag='', attrs={}, children=[]): + """ XMLDictElement building from dict object, with defaults """ + if isinstance(attrs, list): + children = attrs + attrs = {} + xmldictel = partial(XMLDictElement, tag, attrs) + if isinstance(children, str): + return xmldictel([children]) + if isinstance(children, list): + return xmldictel(children) + raise TypeError('Invalid arguments for xmln') + + +def xml_write(filepath, tree, pretty=True): + """ Write XML file according to filename and given tree """ + if filepath.endswith('.py'): # if .pyc, no need to generate XML + output_xml = ET.tostring(tree) + if pretty: + output_xml = minidom.parseString(output_xml).toprettyxml(indent=' ') + output_path = path.abspath(filepath).split(u'/') + output_path[-1] = output_path[-1].replace(u'.py', u'_views.xml') + output_path = u'/'.join(output_path) + with open(output_path, 'w') as output_file: + output_file.write(output_xml) diff --git a/yaltik_dsl/tests/test_odoo.py b/yaltik_dsl/tests/test_odoo.py new file mode 100644 index 0000000..cc2ad07 --- /dev/null +++ b/yaltik_dsl/tests/test_odoo.py @@ -0,0 +1,247 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2020 Fabien Bourgeois +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" Odoo Helpers tests """ + +import unittest +import xml.etree.ElementTree as ET +from src.xml_base import XMLDictElement +import src.odoo_dsl as od + +class TestOdooBase(unittest.TestCase): + """ Odoo Helpers tests """ + + def test_odoo(self): + """ Test odoo function """ + element = od.odoo([]) + self.assertIsInstance(element, ET.Element) + self.assertEqual(element.tag, 'odoo') + + def test_data(self): + """ Test data function """ + element = od.data([]) + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'data') + self.assertEqual(element.attrs, {}) + + element = od.data({"one": "attribute"}, []) + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'data') + self.assertEqual(element.attrs, {"one": "attribute"}) + + def test_aliases(self): + """ Test simple aliases to xmln """ + element = od.record({"one": "attribute"}, 'A child') + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'record') + self.assertEqual(element.attrs, {"one": "attribute"}) + self.assertEqual(element.children, ['A child']) + + element = od.tree() + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'tree') + + def test_act_window_model(self): + """ Test act_window function """ + element = od.act_window_model('sample.model', {'view_type': 'form'}) + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'act_window') + self.assertEqual(element.attrs['view_type'], 'form') + self.assertEqual(element.attrs['id'], 'sample_model_view_action') + self.assertEqual(element.attrs['res_model'], 'sample.model') + self.assertEqual(element.attrs['name'], 'Sample Model Action') + + def test_menunitem_model(self): + """ Test menuitem function """ + element = od.menuitem_model('sample.model', {'groups': 'base.user_employee'}) + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'menuitem') + self.assertEqual(element.attrs['groups'], 'base.user_employee') + self.assertEqual(element.attrs['id'], 'sample_model_menu') + self.assertEqual(element.attrs['action'], 'sample_model_view_action') + + def test_attribute(self): + """ Test attribute function """ + element = od.attribute('invisible', "1") + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'attribute') + self.assertEqual(element.attrs['name'], 'invisible') + self.assertEqual(element.children, ["1"]) + + def test_fields(self): + """ Test fields function """ + element = od.field({"one": "attribute"}, 'A child') + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'field') + + element = od.field_name('A name') + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'field') + self.assertEqual(element.attrs['name'], 'name') + self.assertEqual(element.children, ['A name']) + + element = od.field_model('sample.model') + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'field') + self.assertEqual(element.attrs['name'], 'model') + self.assertEqual(element.children, ['sample.model']) + + element = od.field_inherit('module.xml_view') + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'field') + self.assertEqual(element.attrs['name'], 'inherit_id') + self.assertEqual(element.attrs['ref'], 'module.xml_view') + self.assertFalse(element.children) + + element = od.field_arch() + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'field') + self.assertEqual(element.attrs['name'], 'arch') + self.assertEqual(element.attrs['type'], 'xml') + self.assertFalse(element.children) + + def test_view(self): + """ Test view function """ + element = od.view('view_xmlid', []) + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'record') + self.assertEqual(element.attrs['id'], 'view_xmlid') + self.assertEqual(element.attrs['model'], 'ir.ui.view') + self.assertFalse(element.children) + + def test_view_def(self): + """ Test view_def function """ + element = od.view_def('view_xmlid', 'View', 'sample.model', []) + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'record') + self.assertEqual(element.attrs['id'], 'view_xmlid') + self.assertEqual(element.attrs['model'], 'ir.ui.view') + self.assertEqual((len(element.children)), 3) + self.assertIsInstance(element.children[0], XMLDictElement) + self.assertEqual(element.children[0].tag, 'field') + self.assertEqual(element.children[0].attrs['name'], 'name') + self.assertEqual(element.children[0].children, ['View']) + self.assertIsInstance(element.children[1], XMLDictElement) + self.assertEqual(element.children[1].tag, 'field') + self.assertEqual(element.children[1].attrs['name'], 'model') + self.assertEqual(element.children[1].children, ['sample.model']) + self.assertIsInstance(element.children[2], XMLDictElement) + self.assertEqual(element.children[2].tag, 'field') + self.assertEqual(element.children[2].attrs['name'], 'arch') + self.assertEqual(element.children[2].attrs['type'], 'xml') + self.assertFalse(element.children[2].children) + + def test_view_new(self): + """ Test view_new function """ + element = od.view_new('tree', 'sample.model', []) + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'record') + self.assertEqual(element.attrs['id'], 'sample_model_view_tree') + self.assertEqual(element.attrs['model'], 'ir.ui.view') + self.assertEqual((len(element.children)), 3) + self.assertIsInstance(element.children[0], XMLDictElement) + self.assertEqual(element.children[0].tag, 'field') + self.assertEqual(element.children[0].attrs['name'], 'name') + self.assertEqual(element.children[0].children, ['Sample Model Tree']) + self.assertIsInstance(element.children[1], XMLDictElement) + self.assertEqual(element.children[1].tag, 'field') + self.assertEqual(element.children[1].attrs['name'], 'model') + self.assertEqual(element.children[1].children, ['sample.model']) + self.assertIsInstance(element.children[2], XMLDictElement) + self.assertEqual(element.children[2].tag, 'field') + self.assertEqual(element.children[2].attrs['name'], 'arch') + self.assertEqual(element.children[2].attrs['type'], 'xml') + self.assertFalse(element.children[2].children) + + def test_view_inherit(self): + """ Test view_inherit function """ + element = od.view_inherit('odoo.addons.module', 'sample.model', 'parent.view', []) + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'record') + self.assertEqual(element.attrs['id'], 'view_inherit_module') + self.assertEqual(element.attrs['model'], 'ir.ui.view') + self.assertEqual((len(element.children)), 4) + self.assertIsInstance(element.children[0], XMLDictElement) + self.assertEqual(element.children[0].tag, 'field') + self.assertEqual(element.children[0].attrs['name'], 'name') + self.assertEqual(element.children[0].children, ['Sample Model Adaptations']) + self.assertIsInstance(element.children[1], XMLDictElement) + self.assertEqual(element.children[1].tag, 'field') + self.assertEqual(element.children[1].attrs['name'], 'model') + self.assertEqual(element.children[1].children, ['sample.model']) + self.assertIsInstance(element.children[2], XMLDictElement) + self.assertEqual(element.children[2].tag, 'field') + self.assertEqual(element.children[2].attrs['name'], 'inherit_id') + self.assertFalse(element.children[2].children) + self.assertIsInstance(element.children[3], XMLDictElement) + self.assertEqual(element.children[3].tag, 'field') + self.assertEqual(element.children[3].attrs['name'], 'arch') + self.assertEqual(element.children[3].attrs['type'], 'xml') + self.assertFalse(element.children[3].children) + + def test_action_server_code(self): + """ Test action_server_code function """ + element = od.action_server_code('sample.xmlid', 'Code', 'sample.model', + '''record.do_something()''') + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'record') + self.assertEqual(element.attrs['id'], 'sample.xmlid') + self.assertEqual(element.attrs['model'], 'ir.actions.server') + self.assertEqual((len(element.children)), 4) + self.assertIsInstance(element.children[0], XMLDictElement) + self.assertEqual(element.children[0].tag, 'field') + self.assertEqual(element.children[0].attrs['name'], 'name') + self.assertEqual(element.children[0].children, ['Code']) + self.assertIsInstance(element.children[1], XMLDictElement) + self.assertEqual(element.children[1].tag, 'field') + self.assertEqual(element.children[1].attrs['name'], 'model_id') + self.assertEqual(element.children[1].attrs['ref'], 'sample.model') + self.assertFalse(element.children[1].children) + self.assertEqual(element.children[2].tag, 'field') + self.assertEqual(element.children[2].attrs['name'], 'state') + self.assertEqual(element.children[2].children, ['code']) + self.assertEqual(element.children[3].tag, 'field') + self.assertEqual(element.children[3].attrs['name'], 'code') + self.assertEqual(element.children[3].children, ['record.do_something()']) + + def test_client_action_multi(self): + """ Test client_action_multi function """ + element = od.client_action_multi('sample.xmlid', 'Multi', 'sample.model', 'sample.action') + self.assertIsInstance(element, XMLDictElement) + self.assertEqual(element.tag, 'record') + self.assertEqual(element.attrs['id'], 'sample.xmlid') + self.assertEqual(element.attrs['model'], 'ir.values') + self.assertEqual((len(element.children)), 4) + self.assertIsInstance(element.children[0], XMLDictElement) + self.assertEqual(element.children[0].tag, 'field') + self.assertEqual(element.children[0].attrs['name'], 'name') + self.assertEqual(element.children[0].children, ['Multi']) + self.assertIsInstance(element.children[1], XMLDictElement) + self.assertEqual(element.children[1].tag, 'field') + self.assertEqual(element.children[1].attrs['name'], 'key2') + self.assertEqual(element.children[1].attrs['eval'], "'client_action_multi'") + self.assertFalse(element.children[1].children) + self.assertEqual(element.children[2].tag, 'field') + self.assertEqual(element.children[2].attrs['name'], 'model') + self.assertEqual(element.children[2].attrs['eval'], "'sample.model'") + self.assertEqual(element.children[3].tag, 'field') + self.assertEqual(element.children[3].attrs['name'], 'value') + self.assertEqual(element.children[3].attrs['eval'], "'ir.actions.server,%d'%sample.action") + + +if __name__ == '__main__': + unittest.main() diff --git a/yaltik_dsl/tests/test_xml_base.py b/yaltik_dsl/tests/test_xml_base.py new file mode 100644 index 0000000..66a5ad6 --- /dev/null +++ b/yaltik_dsl/tests/test_xml_base.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2020 Fabien Bourgeois +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" XML Helpers tests """ + +from functools import partial +import unittest +import xml.etree.ElementTree as ET +from os import unlink +from src.xml_base import xmln, xmlroot, xmlchild, xml_write + + +class TestXMLBase(unittest.TestCase): + """ XML Helpers tests """ + + def test_xmln(self): + """ Text xmln """ + # Tags + self.assertDictEqual(xmln()._asdict(), {'tag': '', 'attrs': {}, 'children': []}) + self.assertEqual(xmln('a tag').tag, 'a tag') + # Attrs + self.assertDictEqual(xmln(attrs={'a good': 'one'}).attrs, {'a good': 'one'}) + # Childrens + self.assertListEqual(xmln(children=[1, 2, 3]).children, [1, 2, 3]) + self.assertListEqual(xmln(children='Some text').children, ['Some text']) + + with self.assertRaisesRegex(TypeError, 'Invalid arguments'): + xmln(children=False) + + # Ensure that only children after tags is managed + element = xmln('tag', {'something': 'inside'}) + self.assertIsInstance(element.attrs, dict) + self.assertIsInstance(element.children, list) + + element = xmln('tag', ['something', 'inside']) + self.assertIsInstance(element.attrs, dict) + self.assertIsInstance(element.children, list) + + + def test_xmlchild(self): + """ Test xmlchild """ + parent = xmlroot({'tag': 'root', 'attrs': {}, 'children': []}) + xmlc_par = partial(xmlchild, parent) + + # Bad arguments + with self.assertRaisesRegex(TypeError, 'Invalid arguments for xmlchild'): + xmlc_par(False) + # Need XMLDictElement, not dict + with self.assertRaisesRegex(TypeError, 'Invalid arguments for xmlchild'): + xmlc_par([{'tag': 't', 'attrs': {'a': 'b'}, 'children': []}]) + + xmlc_par(['some text']) + self.assertEqual(parent.text, 'some text') + + xmlc_par([xmln('t', {'a': 'b'}, [])]) + child = parent.iter('t').__next__() + self.assertEqual(child.tag, 't') + self.assertDictEqual(child.attrib, {'a': 'b'}) + self.assertListEqual(list(child), []) + + xmlc_par([xmln('t2', {1: 2}, [])]) + child = parent.iter('t2').__next__() + self.assertDictEqual(child.attrib, {'1': '2'}) + + xmlc_par([xmln('tchildren', {}, [xmln('subchild', {}, [])])]) + child = parent.iter('tchildren').__next__() + subchildren = list(child) + self.assertEqual(len(subchildren), 1) + self.assertEqual(subchildren[0].tag, 'subchild') + + + def test_xmlroot(self): + """ Test xmlroot """ + root = xmlroot({'tag': 'root', 'attrs': {}, 'children': []}) + self.assertIsInstance(root, ET.Element) + + with self.assertRaisesRegex(TypeError, 'not subscriptable'): + xmlroot(False) + with self.assertRaisesRegex(KeyError, 'tag'): + xmlroot({}) + with self.assertRaisesRegex(KeyError, 'attrs'): + xmlroot({'tag': 'root'}) + + + def test_xml_write(self): + """ test xml_write """ + children = [xmln('child1', {'attr': 'value'}, []), + xmln('child2', {}, "Some text")] + tree = xmlroot({'tag': 'root', 'attrs': {}, 'children': children}) + xmlw = lambda p: xml_write(p, tree) + + self.assertIsNone(xmlw('/badpath')) + self.assertIsNone(xmlw('/bad.ext')) + + xmlw(__file__) + filepath = __file__.replace('.py', '_views.xml') + with open(filepath, 'r') as output_file: + output_xml = output_file.read() + self.assertIn('', output_xml) + self.assertIn('', output_xml) + self.assertIn('Some text', output_xml) + unlink(filepath) + +if __name__ == '__main__': + unittest.main()