| # Copyright 2005-2015 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| |
| from __future__ import print_function |
| |
| from portage import os |
| from portage.exception import PortageException |
| from portage.cache.mappings import ProtectedDict |
| from portage.localization import _ |
| from portage.util import writemsg |
| |
| |
| class InvalidModuleName(PortageException): |
| """An invalid or unknown module name.""" |
| |
| |
| class ModuleVersionError(PortageException): |
| '''An incompatible module version''' |
| |
| |
| class Module(object): |
| """Class to define and hold our plug-in module |
| |
| @type name: string |
| @param name: the module name |
| @type path: the path to the new module |
| """ |
| |
| def __init__(self, name, namepath): |
| """Some variables initialization""" |
| self.name = name |
| self._namepath = namepath |
| self.kids_names = [] |
| self.kids = {} |
| self.initialized = self._initialize() |
| |
| def _initialize(self): |
| """Initialize the plug-in module |
| |
| @rtype: boolean |
| """ |
| self.valid = False |
| try: |
| mod_name = ".".join([self._namepath, self.name]) |
| self._module = __import__(mod_name, [], [], ["not empty"]) |
| self.valid = True |
| except ImportError as e: |
| print("MODULE; failed import", mod_name, " error was:", e) |
| return False |
| self.module_spec = self._module.module_spec |
| for submodule in self.module_spec['provides']: |
| kid = self.module_spec['provides'][submodule] |
| kidname = kid['name'] |
| try: |
| kid['module_name'] = '.'.join([mod_name, kid['sourcefile']]) |
| except KeyError: |
| kid['module_name'] = '.'.join([mod_name, self.name]) |
| msg = ("%s module's module_spec is old, missing attribute: " |
| "'sourcefile'. Backward compatibility may be " |
| "removed in the future.\nFile: %s\n") |
| writemsg(_(msg) % (self.name, self._module.__file__)) |
| kid['is_imported'] = False |
| self.kids[kidname] = kid |
| self.kids_names.append(kidname) |
| return True |
| |
| def get_class(self, name): |
| if not name or name not in self.kids_names: |
| raise InvalidModuleName("Module name '%s' is invalid or not" |
| %name + "part of the module '%s'" %self.name) |
| kid = self.kids[name] |
| if kid['is_imported']: |
| module = kid['instance'] |
| else: |
| try: |
| module = __import__(kid['module_name'], [], [], ["not empty"]) |
| kid['instance'] = module |
| kid['is_imported'] = True |
| except ImportError: |
| raise |
| mod_class = getattr(module, kid['class']) |
| return mod_class |
| |
| |
| class Modules(object): |
| """Dynamic modules system for loading and retrieving any of the |
| installed emaint modules and/or provided class's |
| |
| @param path: Path to the "modules" directory |
| @param namepath: Python import path to the "modules" directory |
| """ |
| |
| def __init__(self, path, namepath, compat_versions=None): |
| self._module_path = path |
| self._namepath = namepath |
| self.compat_versions = compat_versions |
| self.parents = [] |
| self._modules = self._get_all_modules() |
| self.modules = ProtectedDict(self._modules) |
| self.module_names = sorted(self._modules) |
| |
| def _get_all_modules(self): |
| """scans the _module_path dir for loadable modules |
| |
| @rtype: dictionary of module_plugins |
| """ |
| module_dir = self._module_path |
| importables = [] |
| names = os.listdir(module_dir) |
| for entry in names: |
| # skip any __init__ or __pycache__ files or directories |
| if entry.startswith('__'): |
| continue |
| try: |
| # test for statinfo to ensure it should a real module |
| # it will bail if it errors |
| os.lstat(os.path.join(module_dir, entry, '__init__.py')) |
| importables.append(entry) |
| except EnvironmentError: |
| pass |
| kids = {} |
| for entry in importables: |
| new_module = Module(entry, self._namepath) |
| self._check_compat(new_module) |
| for module_name in new_module.kids: |
| kid = new_module.kids[module_name] |
| kid['parent'] = new_module |
| kids[kid['name']] = kid |
| self.parents.append(entry) |
| return kids |
| |
| def get_module_names(self): |
| """Convenience function to return the list of installed modules |
| available |
| |
| @rtype: list |
| @return: the installed module names available |
| """ |
| return self.module_names |
| |
| def get_class(self, modname): |
| """Retrieves a module class desired |
| |
| @type modname: string |
| @param modname: the module class name |
| """ |
| if modname and modname in self.module_names: |
| mod = self._modules[modname]['parent'].get_class(modname) |
| else: |
| raise InvalidModuleName("Module name '%s' is invalid or not" |
| %modname + "found") |
| return mod |
| |
| def get_description(self, modname): |
| """Retrieves the module class decription |
| |
| @type modname: string |
| @param modname: the module class name |
| @type string |
| @return: the modules class decription |
| """ |
| if modname and modname in self.module_names: |
| mod = self._modules[modname]['description'] |
| else: |
| raise InvalidModuleName("Module name '%s' is invalid or not" |
| %modname + "found") |
| return mod |
| |
| def get_functions(self, modname): |
| """Retrieves the module class exported function names |
| |
| @type modname: string |
| @param modname: the module class name |
| @type list |
| @return: the modules class exported function names |
| """ |
| if modname and modname in self.module_names: |
| mod = self._modules[modname]['functions'] |
| else: |
| raise InvalidModuleName("Module name '%s' is invalid or not" |
| %modname + "found") |
| return mod |
| |
| def get_func_descriptions(self, modname): |
| """Retrieves the module class exported functions descriptions |
| |
| @type modname: string |
| @param modname: the module class name |
| @type dictionary |
| @return: the modules class exported functions descriptions |
| """ |
| if modname and modname in self.module_names: |
| desc = self._modules[modname]['func_desc'] |
| else: |
| raise InvalidModuleName("Module name '%s' is invalid or not" |
| %modname + "found") |
| return desc |
| |
| def get_opt_descriptions(self, modname): |
| """Retrieves the module class exported options descriptions |
| |
| @type modname: string |
| @param modname: the module class name |
| @type dictionary |
| @return: the modules class exported options descriptions |
| """ |
| if modname and modname in self.module_names: |
| desc = self._modules[modname].get('opt_desc') |
| else: |
| raise InvalidModuleName( |
| "Module name '%s' is invalid or not found" % modname) |
| return desc |
| |
| def get_spec(self, modname, var): |
| """Retrieves the module class exported spec variable |
| |
| @type modname: string |
| @param modname: the module class name |
| @type var: string |
| @param var: the base level variable to return |
| @type dictionary |
| @return: the modules class exported options descriptions |
| """ |
| if modname and modname in self.module_names: |
| value = self._modules[modname].get(var, None) |
| else: |
| raise InvalidModuleName( |
| "Module name '%s' is invalid or not found" % modname) |
| return value |
| |
| def _check_compat(self, module): |
| if self.compat_versions: |
| if not module.module_spec['version'] in self.compat_versions: |
| raise ModuleVersionError( |
| "Error loading '%s' plugin module: %s, version: %s\n" |
| "Module is not compatible with the current application version\n" |
| "Compatible module API versions are: %s" |
| % (self._namepath, module.module_spec['name'], |
| module.module_spec['version'], self.compat_versions)) |