diff --git a/yaltik_dsl/src/odoo_dsl.coco b/yaltik_dsl/src/odoo_dsl.coco deleted file mode 100644 index 1134520..0000000 --- a/yaltik_dsl/src/odoo_dsl.coco +++ /dev/null @@ -1,138 +0,0 @@ -# -*- 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_nval = lambda name, value: field({'name': name}, [value]) -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) -separator = lambda *args: xmln('separator', *args) - -def filter_yes_no(field, str_yes=False, str_no=False): - """ Double filter for boolean : True and False """ - res = [] - if str_yes: - res.append(filter({'name': f'{field}_yes', 'string': str_yes, - 'domain': f"[('{field}', '=', True)]"})) - if str_no: - res.append(filter({'name': f'{field}_no', 'string': str_no, - 'domain': f"[('{field}', '=', False)]"})) - return res - -# 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.coco b/yaltik_dsl/src/xml_base.coco deleted file mode 100644 index 15c0876..0000000 --- a/yaltik_dsl/src/xml_base.coco +++ /dev/null @@ -1,75 +0,0 @@ -# -*- 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, suffix='_views'): - """ 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('/') - output_path[-1] = output_path[-1].replace('.py', '%s.xml' % suffix) - output_path = '/'.join(output_path) - with open(output_path, 'w') as output_file: - output_file.write(output_xml) diff --git a/yaltik_dsl/tests/test_xml_base.coco b/yaltik_dsl/tests/test_xml_base.coco deleted file mode 100644 index e3ff2d0..0000000 --- a/yaltik_dsl/tests/test_xml_base.coco +++ /dev/null @@ -1,129 +0,0 @@ -# -*- 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) - xml_write(__file__, tree, suffix='_data') - filepath = __file__.replace('.py', '_data.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()