From 9a3dfacc314da9136a4829d0bcab762f6c14d862 Mon Sep 17 00:00:00 2001 From: Fabien BOURGEOIS Date: Mon, 22 Feb 2021 00:43:17 +0100 Subject: [PATCH] [REF][IMP][WIP]Pull improvements from purepy yaltik_dsl --- hy_odoo/__init__.py | 2 +- hy_odoo/macros.hy | 26 +++++++ hy_odoo/odoo.hy | 164 ++++++++++++++++++++++++-------------------- hy_odoo/test.hy | 3 + hy_odoo/xml.hy | 71 ------------------- hy_odoo/xml_base.hy | 73 ++++++++++++++++++++ 6 files changed, 193 insertions(+), 146 deletions(-) create mode 100644 hy_odoo/macros.hy delete mode 100644 hy_odoo/xml.hy create mode 100644 hy_odoo/xml_base.hy diff --git a/hy_odoo/__init__.py b/hy_odoo/__init__.py index 87761c6..4ad51cc 100644 --- a/hy_odoo/__init__.py +++ b/hy_odoo/__init__.py @@ -16,4 +16,4 @@ # along with this program. If not, see . import hy -from . import odoo, xml +from . import macros,odoo, xml_base, test diff --git a/hy_odoo/macros.hy b/hy_odoo/macros.hy new file mode 100644 index 0000000..9913cc4 --- /dev/null +++ b/hy_odoo/macros.hy @@ -0,0 +1,26 @@ +;; -*- coding: utf-8 -*- +;; +;; Copyright 2021 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 . + +" Hy General Macros " + + +(defmacro if-python2 [python2-form python3-form] + "If running on python2, execute python2-form, else, execute python3-form" + (import sys) + (if (< (get sys.version_info 0) 3) + python2-form + python3-form)) diff --git a/hy_odoo/odoo.hy b/hy_odoo/odoo.hy index 3a9fafe..5095dfd 100644 --- a/hy_odoo/odoo.hy +++ b/hy_odoo/odoo.hy @@ -17,9 +17,9 @@ " Odoo macros and helpers " -(require [hy-odoo.xml [if-python2]]) -(import [os [path]]) -(import [hy-odoo.xml [xmlroot xmln]]) +(require [hy-odoo.macros [if-python2]]) +(import [os [path]] + [hy-odoo.xml_base [xmlroot xmln]]) ; Global helpers @@ -57,85 +57,32 @@ ; XML helpers functions and macros -(defn odoo [&rest args] (xmlroot (xmln "odoo" {} #*args))) +(defn odoo [children] (xmlroot (xmln "odoo" {} children))) + (defn data [&rest args] - (if (= (len args) 1) - (xmln "data" {} #*args) - (xmln "data" #*args))) + "Special data node, allow optional args on data tag" + (when (= (len args) 1) (setv args (.insert (list args) 0 {}))) + (xmln "data" #*args)) + +; Aliases (defn function [&rest args] (xmln "function" #*args)) (defn record [&rest args] (xmln "record" #*args)) (defn form [&rest args] (xmln "form" #*args)) (defn tree [&rest args] (xmln "tree" #*args)) (defn search [&rest args] (xmln "search" #*args)) + +; Actions (defn act-window [&rest args] (xmln "act_window" #*args)) + (defn act-window-model [model attrs] " Build new act_window from model and args" - (setv model_und (.replace model "." "_") - model_cap (.join " " (lfor w (.split model ".") (.capitalize w))) - xmlid f"{model_und}_view_action" - name f"{model_cap} Action") - (.update attrs {"id" xmlid "name" name "res_model" model}) - (act-window attrs)) -(defn menuitem [&rest args] (xmln "menuitem" #*args)) -(defn menuitem-model [model attrs] - " Build new menuitem from model and attrs" - (setv model_und (.replace model "." "_") - actionid f"{model_und}_view_action" - xmlid f"{model_und}_menu") - (.update attrs {"id" xmlid "action" actionid}) - (menuitem attrs)) -(defn group [&rest args] (xmln "group" #*args)) -(defn header [&rest args] (xmln "header" #*args)) -(defn footer [&rest args] (xmln "footer" #*args)) -(defn sheet [&rest args] (xmln "sheet" #*args)) -(defn button [&rest args] (xmln "button" #*args)) -(defn p [&rest args] (xmln "p" #*args)) -(defn xpath [&rest args] (xmln "xpath" #*args)) -(defn attribute [name value] (xmln "attribute" {"name" name} [value])) - -(defn field [&rest args] - "Special field allowing mangling name attribute" - (setv attrs (nth args 0)) - (when (and (instance? dict attrs) (in "name" attrs)) - (assoc attrs "name" (mangle (get attrs "name"))) - (setv args (list args)) - (assoc args 0 attrs) - (setv args (tuple args))) - (xmln "field" #*args)) - -(defn field-name [name] (field {"name" "name"} [name])) -(defn field-model [model] (field {"name" "model"} [model])) -(defn field-inherit [xmlid] (field {"name" "inherit_id" "ref" xmlid} [])) -(defn field-arch [&rest args] (field {"name" "arch" "type" "xml"} #*args)) - - -(defn view [xmlid children] (record {"id" xmlid "model" "ir.ui.view"} children)) -(defn view-def [xmlid name model arch] - "View and first fields simplification with record xmlid, name, targeted model" - (view xmlid - [(field-name name) - (field-model model) - (field-arch arch)])) -(defn view-new[view_type model arch] - "View : new view definition, based on type (form, tree, ...) and model ID" - (setv model_und (.replace model "." "_") - model_cap (.join " " (lfor w (.split model ".") (.capitalize w))) - xmlid f"{model_und}_view_{view_type}" - name f"{model_cap} {(.capitalize view_type)}") - (view-def :xmlid xmlid :name name :model model :arch arch)) -(defn view-inherit [filename model inherit arch] - "Inherited View simplification with name of the record, xmlid for model and - inherited view" - (setv module (get (.split filename ".") 2) - inherited (get (.split inherit ".") 1) - xmlid f"{inherited}_inherit_{module}" - model_cap (.join " " (lfor w (.split model ".") (.capitalize w))) - name f"{model_cap} Adaptations") - (view xmlid - [(field-name name) - (field-model model) - (field-inherit inherit) - (field-arch arch)])) + (setv model-und (.replace model "." "_") + model-cap (.join " " (lfor w (.split model ".") (.capitalize w))) + xmlid f"{model-und}_view_action" + name f"{model-cap} Action" + cloned-attrs (.copy attrs)) ; Avoid side-effect + (.update cloned-attrs {"id" xmlid "name" name "res_model" model}) + (act-window cloned-attrs)) (defn actions-server-code [xmlid name modelref code] "Server actions of type code" @@ -152,9 +99,78 @@ (record {"id" xmlid "model" "ir.values"} [(field-name name) (field {"name" "key2" "eval" "'client_action_multi'"} []) - (field {"name" "model" "eval" (+ "'" model "'")} []) + (field {"name" "model" "eval" (% "'%s'" model)} []) (field {"name" "value" "eval" action})])) +; Menus +(defn menuitem [&rest args] (xmln "menuitem" #*args)) + +(defn menuitem-model [model attrs] + " Build new menuitem from model and attrs" + (setv model-und (.replace model "." "_") + actionid f"{model-und}_view_action" + xmlid f"{model-und}_menu" + cloned-attrs (.copy attrs)) + (.update cloned-attrs {"id" xmlid "action" actionid}) + (menuitem cloned-attrs)) + +; Form aliases +(defn group [&rest args] (xmln "group" #*args)) +(defn header [&rest args] (xmln "header" #*args)) +(defn footer [&rest args] (xmln "footer" #*args)) +(defn sheet [&rest args] (xmln "sheet" #*args)) +(defn button [&rest args] (xmln "button" #*args)) +(defn p [&rest args] (xmln "p" #*args)) +(defn xpath [&rest args] (xmln "xpath" #*args)) +(defn attribute [name value] (xmln "attribute" {"name" name} [value])) + +; Fields +(defn field [&rest args] + "Special field allowing mangling name attribute" + (setv attrs (nth args 0)) + (when (and (instance? dict attrs) (in "name" attrs)) + (assoc attrs "name" (mangle (get attrs "name"))) + (setv args (list args)) + (assoc args 0 attrs) + (setv args (tuple args))) + (xmln "field" #*args)) + +(defn field-name [name] (field {"name" "name"} [name])) +(defn field-model [model] (field {"name" "model"} [model])) +(defn field-inherit [xmlid] (field {"name" "inherit_id" "ref" xmlid} [])) +(defn field-arch [&rest args] (field {"name" "arch" "type" "xml"} #*args)) + +; Search +(defn filter [&rest args] (xmln "filter" #*args)) + +; Views +(defn view [xmlid children] (record {"id" xmlid "model" "ir.ui.view"} children)) + +(defn view-def [xmlid name model arch] + "View and first fields simplification with record xmlid, name, targeted model" + (view xmlid [(field-name name) (field-model model) (field-arch arch)])) + +(defn view-new[view_type model arch] + "View : new view definition, based on type (form, tree, ...) and model ID" + (setv model-und (.replace model "." "_") + model-cap (.join " " (lfor w (.split model ".") (.capitalize w))) + xmlid f"{model-und}_view_{view_type}" + name f"{model-cap} {(.capitalize view_type)}") + (view-def :xmlid xmlid :name name :model model :arch arch)) + +(defn view-inherit [filename model inherit arch] + "Inherited View simplification with name of the record, xmlid for model and + inherited view" + (setv module (get (.split filename ".") 2) + inherited (get (.split inherit ".") 1) + xmlid f"{inherited}_inherit_{module}" + model-cap (.join " " (lfor w (.split model ".") (.capitalize w))) + name f"{model-cap} Adaptations") + (view xmlid [(field-name name) + (field-model model) + (field-inherit inherit) + (field-arch arch)])) + ; Odoo ORM macros (defmacro/g! compute-fn [field dependencies body] diff --git a/hy_odoo/test.hy b/hy_odoo/test.hy index e9ccfe4..24fc703 100644 --- a/hy_odoo/test.hy +++ b/hy_odoo/test.hy @@ -18,6 +18,8 @@ " Hy Odoo Tests Helpers and Macros " (defmacro o-assert-equal [left right] `(.assertEqual self ~left ~right)) +(defmacro o-assert-list-equal [left right] `(.assertListEqual self ~left ~right)) +(defmacro o-assert-dict-equal [left right] `(.assertDictEqual self ~left ~right)) (defmacro o-assert-not-equal [left right] `(.assertNotEqual self ~left ~right)) (defmacro o-assert-true [val] `(.assertTrue self ~val)) (defmacro o-assert-false [val] `(.assertFalse self ~val)) @@ -30,6 +32,7 @@ (defmacro o-assert-is-instance [left right] `(.assertIsInstance self ~left ~right)) (defmacro o-assert-not-is-instance [left right] `(.assertNotIsInstance self ~left ~right)) (defmacro o-assert-raises [Error] `(.assertRaises self ~Error)) +(defmacro o-assert-raises-regex [Error regexp] `(.assertRaisesRegexp self ~Error ~regexp)) (defmacro odo-assert-raises [Error body] "Macro to test Error with self.assertRaises and do block" diff --git a/hy_odoo/xml.hy b/hy_odoo/xml.hy deleted file mode 100644 index 20f7310..0000000 --- a/hy_odoo/xml.hy +++ /dev/null @@ -1,71 +0,0 @@ -;; -*- coding: utf-8 -*- -;; -;; Copyright 2019 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 " - -(import [xml.etree.ElementTree :as ET]) - -;; Helpers - -(defn xmlroot [tree] - "Special process for root XML Node" - (setv rootel (.Element ET (get tree "tag") (get tree "attrs")) - children (get tree "children")) - (if children (xmlchild rootel children)) - (return rootel)) - -(defn xmlchild [parent children] - "Handling of children (ie non root) XML Nodes with/o text and subchildren - (recursive)" - (for [child children] - (if (string? child) - (setv (. parent text) child) - (do - (setv attrs (dfor [k v] (.items (get child "attrs")) [(str k) (str v)])) - (setv new_parent (.SubElement ET parent (get child "tag") attrs)) - (setv subchildren (get child "children")) - (if subchildren (xmlchild new_parent subchildren)))))) - -(defn xmln [tag &optional attrs children] - "XMLNode with optional attributes or children, defaults according to second - argument type or empty dict and list" - (if (and attrs (not children)) - (if (instance? list attrs) - (do (setv children attrs) (setv attrs {})))) - {"tag" tag "attrs" (or attrs {}) "children" (or children [])}) - -;; Macros - -(defmacro if-python2 [python2-form python3-form] - "If running on python2, execute python2-form, else, execute python3-form" - (import sys) - (if (< (get sys.version_info 0) 3) - python2-form - python3-form)) - -(defmacro/g! xml-write [filename tree] - "Write XML file according to filename and given tree" - `(do - (import [os [path]] - [xml.etree.ElementTree :as ET] - [xml.dom [minidom]]) - (if-python2 - (setv ~g!output-xml (.toprettyxml :indent " " (.parseString minidom (.tostring ET ~tree)))) - (setv ~g!output-xml (.decode (.tostring ET ~tree) "utf-8"))) - (setv ~g!output-path (.dirname path (.abspath path __file__)) - ~g!fpath (+ ~g!output-path "/" ~filename)) - (with [f (open ~g!fpath "w")] (.write f ~g!output-xml)))) diff --git a/hy_odoo/xml_base.hy b/hy_odoo/xml_base.hy new file mode 100644 index 0000000..e4bc641 --- /dev/null +++ b/hy_odoo/xml_base.hy @@ -0,0 +1,73 @@ +;; -*- coding: utf-8 -*- +;; +;; Copyright 2019-2021 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 " + +(require [hy-odoo.macros [if-python2]]) +(import [collections [namedtuple]] + [functools [partial]] + [os [path]] + [xml.etree.ElementTree :as ET]) + +;; Types + +(setv XMLDictElement (namedtuple "XMLDictElement" ["tag" "attrs" "children"])) + +;; Helpers + +(defn xmlroot [tree] + "Special process for root XML Node" + (setv rootel (.Element ET (get tree "tag") (get tree "attrs"))) + (when (in "children" tree) + (xmlchild rootel (get tree "children"))) + (return rootel)) + +(defn xmlchild [parent children] + "Handling of children (ie non root) XML Nodes with/o text and subchildren + (recursive)" + (cond [(string? children) (setv (. parent text) children)] + [(instance? XMLDictElement children + (do + (setv attrs (dfor [k v] (.items (. children attrs)) [(unicode k) (unicode v)]) + new-parent (.SubElement ET parent (. children tag) attrs) + subchildren (.children children)) + (when subchildren) (xmlchild new-parent subchildren)))] + [(instance? list children) (list( map (partial xmlchild parent) children))] + [True (raise (TypeError "Invalid arguments for xmlchild"))])) + +(defn xmln [&optional [tag ""] [attrs {}] [children []]] + "XMLDictElement building from dict object, with defaults" + (when (instance? list attrs) (setv children attrs attrs {})) + (setv xmldictel (partial XMLDictElement tag attrs)) + (when (instance? unicode children) (return (xmldictel [children]))) + (when (instance? list children) (return (xmldictel children))) + (raise (TypeError "Invalid arguments for xmln"))) + +(defn xml-write [filepath tree] + "Write XML file according to filepath and given tree" + (when (.endswith filepath ".py") + (if-python2 + (do + (import [xml.etree.ElementTree :as ET] + [xml.dom [minidom]]) + (setv output-xml (.toprettyxml :indent " " + (.parseString minidom (.tostring ET tree))))) + (setv output-xml (.decode (.tostring ET tree) "utf-8"))) + (setv output-path (.split (.abspath path filepath) "/") + (cut output-path -1) (.replace (last output-path) ".py" "_views.xml") + output-path (.join "/" output-path)) + (with [output-file (open output-path "w")] (.write output-file output-xml))))