From d1a5c99708fa5e2fc0387e31ccb57f004d74f2fc Mon Sep 17 00:00:00 2001 From: necoro <> Date: Fri, 20 Jul 2007 07:19:27 +0000 Subject: new Plugin Scheme --- plugin.xsd | 89 +++++++++++++++++++++++++ plugins/etc_proposals.xml | 28 ++++---- plugins/noroot.xml | 18 +++--- plugins/resume_loop.xml | 25 ++++--- plugins/shutdown.xml | 19 +++--- portato.py | 13 +++- portato/constants.py | 19 ++++-- portato/plugin.py | 161 +++++++++++++++------------------------------- setup.py | 4 +- 9 files changed, 214 insertions(+), 162 deletions(-) create mode 100644 plugin.xsd diff --git a/plugin.xsd b/plugin.xsd new file mode 100644 index 0000000..7f9975b --- /dev/null +++ b/plugin.xsd @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/etc_proposals.xml b/plugins/etc_proposals.xml index 8686072..410ce4b 100644 --- a/plugins/etc_proposals.xml +++ b/plugins/etc_proposals.xml @@ -1,17 +1,19 @@ - - + + + + René 'Necoro' Neumann + Etc-proposals plugin + portato.plugins.etc_proposals - - - + + + + + + + + Et_c-Proposals + - diff --git a/plugins/noroot.xml b/plugins/noroot.xml index 7e744ef..850a039 100644 --- a/plugins/noroot.xml +++ b/plugins/noroot.xml @@ -1,12 +1,12 @@ - - + + + René 'Necoro' Neumann + No Root portato.plugins.noroot - - - + + + + + diff --git a/plugins/resume_loop.xml b/plugins/resume_loop.xml index 572ccfa..14575cd 100644 --- a/plugins/resume_loop.xml +++ b/plugins/resume_loop.xml @@ -1,22 +1,21 @@ - + + René 'Necoro' Neumann + Emerge Resume Loop portato.plugins.resume_loop - - - + + + + - - * - + + * + + + diff --git a/plugins/shutdown.xml b/plugins/shutdown.xml index 586b57d..75323b1 100644 --- a/plugins/shutdown.xml +++ b/plugins/shutdown.xml @@ -1,17 +1,16 @@ - - + + + René 'Necoro' Neumann + Shutdown portato.plugins.shutdown - - * - + + + * + + - diff --git a/portato.py b/portato.py index e4210bb..5e8b30a 100755 --- a/portato.py +++ b/portato.py @@ -12,7 +12,7 @@ # # Written by René 'Necoro' Neumann -from portato.constants import VERSION, FRONTENDS, STD_FRONTEND +from portato.constants import VERSION, FRONTENDS, STD_FRONTEND, XSD_LOCATION from optparse import OptionParser import sys @@ -37,6 +37,9 @@ def main (): parser.add_option("-e", "--ebuild", action = "store", dest = "ebuild", help = "opens the ebuild viewer instead of launching Portato") + parser.add_option("-x", "--validate", action = "store", dest = "validate", metavar="PLUGIN", + help = "validates the given plugin xml instead of launching Portato") + # run parser (options, args) = parser.parse_args() @@ -62,6 +65,14 @@ def main (): if options.ebuild: show_ebuild(options.ebuild) + elif options.validate: + from lxml import etree + if etree.XMLSchema(file = XSD_LOCATION).validate(etree.parse(options.validate)): + print "Passed validation." + return + else: + print "Verification failed." + sys.exit(3) else: run() diff --git a/portato/constants.py b/portato/constants.py index f8da545..c17ee24 100644 --- a/portato/constants.py +++ b/portato/constants.py @@ -14,6 +14,8 @@ Constants used through out the program. Mainly different pathes. These should be set during the installation. +@var VERSION: the current version +@type VERSION: string @var CONFIG_DIR: The configuration directory. @type CONFIG_DIR: string @var CONFIG_LOCATION: L{CONFIG_DIR} plus name of the config file. @@ -22,8 +24,10 @@ These should be set during the installation. @type DATA_DIR: string @var PLUGIN_DIR: Directory containing the plugin xmls. @type PLUGIN_DIR: string -@var VERSION: the current version -@type VERSION: string +@var XSD_DIR: Directory containing the plugin-xml schema. +@type XSD_DIR: string +@var XSD_LOCATION: Path of the plugin schema. +@type XSD_LOCATION: string @var ICON_DIR: directory containing the icons @type ICON_DIR: string @var APP_ICON: the path of the application icon @@ -33,16 +37,21 @@ These should be set during the installation. @var STD_FRONTEND: the frontend uses as the default, i.e. if no other one is given on the cmdline @type STD_FRONTEND: string """ +from os.path import join as pjoin + +VERSION = "9999" CONFIG_DIR = "/etc/portato/" -CONFIG_LOCATION = CONFIG_DIR+"portato.cfg" +CONFIG_LOCATION = pjoin(CONFIG_DIR, "portato.cfg") DATA_DIR = "portato/gui/templates/" PLUGIN_DIR = "plugins/" -VERSION = "9999" + +XSD_DIR = "./" +XSD_LOCATION = pjoin(XSD_DIR, "plugin.xsd") ICON_DIR = "icons/" -APP_ICON = ICON_DIR+"/portato-icon.png" +APP_ICON = pjoin(ICON_DIR, "portato-icon.png") FRONTENDS = ["gtk" ,"qt"] STD_FRONTEND = "gtk" diff --git a/portato/plugin.py b/portato/plugin.py index a833696..06ef135 100644 --- a/portato/plugin.py +++ b/portato/plugin.py @@ -14,11 +14,12 @@ import os, os.path from xml.dom.minidom import parse +from lxml import etree -from constants import PLUGIN_DIR +from constants import PLUGIN_DIR, XSD_LOCATION from helper import * -class ParseException (Exception): +class PluginImportException (ImportError): pass class Options (object): @@ -36,21 +37,10 @@ class Options (object): def parse (self, options): for opt in options: - if opt.hasChildNodes(): - nodes = opt.childNodes - - if len(nodes) > 1: - raise ParseException, "Malformed option" - - if nodes[0].nodeType != nodes[0].TEXT_NODE: - raise ParseException, "Malformed option" - - type = str(nodes[0].nodeValue.strip()) - - if type in self.__options: - self.set(type, True) - else: - raise ParseException, "Malformed option" + nodes = opt.childNodes + type = str(nodes[0].nodeValue.strip()) + if type in self.__options: + self.set(type, True) def get (self, name): return self.__getattribute__(name) @@ -70,13 +60,7 @@ class Menu: @param call: the function to call relative to the import statement @type call: string - @raises ParseException: on parsing errors""" - - if not label: - raise ParseException, "label attribute missing" - - if not call: - raise ParseException, "call attribute missing" + @raises PluginImportException: if the plugin's import could not be imported""" self.label = label self.plugin = plugin @@ -86,17 +70,17 @@ class Menu: try: mod = __import__(imp, globals(), locals(), [call]) except ImportError: - raise ParseException, imp+" cannot be imported" + raise PluginImportException, imp try: self.call = eval("mod."+call) # build function except AttributeError: - raise ParseException, call+" cannot be imported" + raise PluginImportException, imp else: try: self.call = eval(call) except AttributeError: - raise ParseException, call+" cannot be imported" + raise PluginImportException, imp class Connect: """A single -element.""" @@ -109,12 +93,7 @@ class Connect: @param type: the type of the connect ("before", "after", "override") @type type: string @param depend_plugin: a plugin we are dependant on - @type depend_plugin: string or None - - @raises ParseException: on parsing errors""" - - if not type in ["before", "after", "override"]: - raise ParseException, "Unknown connect type %s" % type + @type depend_plugin: string or None""" self.type = type self.hook = hook @@ -140,16 +119,8 @@ class Hook: @param hook: the hook to add to @type hook: string @param call: the call to make - @type call: string - - @raises ParseException: on parsing errors""" - - if not hook: - raise ParseException, "hook attribute missing" + @type call: string""" - if not call: - raise ParseException, "call attribute missing" - self.plugin = plugin self.hook = hook self.call = call @@ -159,13 +130,8 @@ class Hook: """This gets a list of -elements and parses them. @param connects: the list of 's - @type connects: NodeList - - @raises ParseException: on parsing errors""" + @type connects: NodeList""" - if not connects: - raise ParseException, "No connect elements in hook" - for c in connects: type = c.getAttribute("type") if type == '': @@ -175,12 +141,6 @@ class Hook: dep_plugin = None if c.hasChildNodes(): nodes = c.childNodes - if len(nodes) > 1: - raise ParseException, "Malformed connect" - - if nodes[0].nodeType != nodes[0].TEXT_NODE: - raise ParseException, "Malformed connect" - dep_plugin = nodes[0].nodeValue.strip() connect = Connect(self, type, dep_plugin) @@ -195,42 +155,39 @@ class Plugin: @param file: the file name of the plugin.xml @type file: string @param name: the name of the plugin - @type name: string + @type name: Node @param author: the author of the plugin - @type author: string""" + @type author: Node""" self.file = file - self.name = name - self.author = author + self.name = name.firstChild.nodeValue.strip() + self.author = author.firstChild.nodeValue.strip() self._import = None self.hooks = [] self.menus = [] self.options = Options() def parse_hooks (self, hooks): - """Gets a list of -elements and parses them. + """Gets an -elements and parses it. - @param hooks: the list of elements - @type hooks: NodeList - - @raises ParseException: on parsing errors""" + @param hooks: the hooks node + @type hooks: Node""" - for h in hooks: - hook = Hook(self, str(h.getAttribute("hook")), str(h.getAttribute("call"))) + for h in hooks.getElementsByTagName("hook"): + hook = Hook(self, str(h.getAttribute("type")), str(h.getAttribute("call"))) hook.parse_connects(h.getElementsByTagName("connect")) self.hooks.append(hook) def parse_menus (self, menus): - """Gets a list of -elements and parses them. - - @param menus: the list of elements - @type menus: NodeList + """Get a list of -elements and parses them. - @raises ParseException: on parsing errors""" + @param menus: the menu nodelist + @type menus: NodeList""" - for m in menus: - menu = Menu(self, str(m.getAttribute("label")), str(m.getAttribute("call"))) - self.menus.append(menu) + if menus: + for item in menus[0].getElementsByTagName("item"): + menu = Menu(self, item.firstChild.nodeValue.strip(), str(item.getAttribute("call"))) + self.menus.append(menu) def parse_options (self, options): if options: @@ -243,29 +200,16 @@ class Plugin: @param imports: list of imports @type imports: NodeList - @raises ParseException: on parsing errors""" - - if len(imports) > 1: - raise ParseException, "More than one import statement." - - if imports[0].hasChildNodes(): - nodes = imports[0].childNodes - - if len(nodes) > 1: - raise ParseException, "Malformed import" - - if nodes[0].nodeType != nodes[0].TEXT_NODE: - raise ParseException, "Malformed import" + @raises PluginImportException: if the plugin's import could not be imported""" - self._import = str(nodes[0].nodeValue.strip()) + if imports: + self._import = str(imports[0].firstChild.nodeValue.strip()) try: # try loading mod = __import__(self._import) del mod except ImportError: - raise ParseException, self._import+" cannot be imported" - else: - raise ParseException, "Malformed import" + raise PluginImportException, self._import def needs_import (self): """Returns True if an import is required prior to calling the plugin. @@ -401,46 +345,45 @@ class PluginQueue: """Load the plugins.""" plugins = filter(lambda x: x.endswith(".xml"), os.listdir(PLUGIN_DIR)) plugins = map(lambda x: os.path.join(PLUGIN_DIR, x), plugins) + schema = etree.XMLSchema(file = XSD_LOCATION) for p in plugins: + + if not schema.validate(etree.parse(p)): + error("Loading plugin '%s' failed. Plugin does not comply to schema.", p) + continue + doc = parse(p) try: try: list = doc.getElementsByTagName("plugin") - if len(list) != 1: - raise ParseException, "Number of plugin elements unequal to 1." - elem = list[0] frontendOK = None - for f in elem.getElementsByTagName("frontend"): - if f.hasChildNodes(): - nodes = f.childNodes - if len(nodes) > 1: - raise ParseException, "Malformed frontend" - - if nodes[0].nodeType != nodes[0].TEXT_NODE: - raise ParseException, "Malformed frontend" - - fValue = nodes[0].nodeValue.strip() - if fValue == self.frontend: + frontends = elem.getElementsByTagName("frontends") + if frontends: + nodes = f.childNodes + for f in nodes[0].nodeValue.strip().split(): + if f == self.frontend: frontendOK = True # one positive is enough break elif frontendOK is None: # do not make negative if we already have a positive frontendOK = False - if frontendOK is None or frontendOK == True: - plugin = Plugin(p, elem.getAttribute("name"), elem.getAttribute("author")) - plugin.parse_hooks(elem.getElementsByTagName("hook")) + if frontendOK is None or frontendOK is True: + plugin = Plugin(p, elem.getElementsByTagName("name")[0], elem.getElementsByTagName("author")[0]) + plugin.parse_hooks(elem.getElementsByTagName("hook")[0]) plugin.set_import(elem.getElementsByTagName("import")) plugin.parse_menus(elem.getElementsByTagName("menu")) plugin.parse_options(elem.getElementsByTagName("options")) self.list.append(plugin) - except ParseException, e: - error("Malformed plugin \"%s\". Reason: %s", p, e[0]) + except PluginImportException, e: + error("Loading plugin '%s' failed: Could not import %s", p, e[0]) + else: + info("Plugin '%s' loaded.", p) finally: doc.unlink() diff --git a/setup.py b/setup.py index f744194..ca9ae4d 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ import os, os.path from distutils.core import setup, Extension -from portato.constants import VERSION, DATA_DIR, FRONTENDS, ICON_DIR, PLUGIN_DIR +from portato.constants import VERSION, DATA_DIR, FRONTENDS, ICON_DIR, PLUGIN_DIR, XSD_DIR def plugin_list (*args): """Creates a list of correct plugin pathes out of the arguments.""" @@ -26,7 +26,7 @@ def ui_file_list (): packages = ["portato", "portato.gui", "portato.plugins", "portato.backend", "portato.backend.portage"] ext_modules = [] -data_files = [(ICON_DIR, ["icons/portato-icon.png"]), (PLUGIN_DIR, plugin_list("shutdown", "resume_loop"))] +data_files = [(ICON_DIR, ["icons/portato-icon.png"]), (PLUGIN_DIR, plugin_list("shutdown", "resume_loop")), (XSD_DIR, ["plugin.xsd"])] cmdclass = {} if "gtk" in FRONTENDS: -- cgit v1.2.3-54-g00ecf