# # This file is part of Lodel 2 (https://github.com/OpenEdition) # # Copyright (C) 2015-2017 Cléo UMS-3287 # # 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 . # import argparse import sys from lodel.context import LodelContext LodelContext.expose_modules(globals(), { 'lodel.logger': 'logger', 'lodel.exceptions': ['LodelException', 'LodelExceptions', 'LodelFatalError', 'DataNoneValid', 'FieldValidationError']}) ## @defgroup lodel2_script Administration scripts # @ingroup lodel2_plugins ## @package lodel.plugin.scripts Lodel2 utility for writting administration scripts # @ingroup lodel2_plugins # @ingroup lodel2_script ## @brief Stores registered scripts # @todo store it in MetaLodelScript __registered_scripts = dict() ## @brief LodelScript metaclass that allows to "catch" child class declaration # @ingroup lodel2_script # @ingroup lodel2_plugins # # Automatic action registration on child class declaration class MetaLodelScript(type): ## # @param name str : action's name # @param bases list # @param attrs list def __init__(self, name, bases, attrs): # Here we can store all child classes of LodelScript super().__init__(name, bases, attrs) if len(bases) == 1 and bases[0] == object: return self.__register_script(name) #_action initialization if self._action is None: logger.warning("%s._action is None. Trying to use class name as \ action identifier" % name) self._action = name self._action = self._action.lower() if self._description is None: self._description = self._default_description() self._parser = argparse.ArgumentParser( prog = self._prog_name(), description = self._description) self.argparser_config(self._parser) ## @brief Handles script registration # @note Script list is maitained in lodel.plugin.admin_script.__registered_scripts # @param name str def __register_script(self, name): if self._action is None: logger.warning("%s._action is None. Trying to use class name as \ action identifier" % name) self._action = name self._action = self._action.lower() script_registration(self._action, self) def __str__(self): return '%s : %s' % (self._action, self._description) ## @brief Class designed to facilitate custom script writting # @ingroup lodel2_plugins # @ingroup lodel2_script class LodelScript(object, metaclass=MetaLodelScript): ## @brief A string to identify the action _action = None ## @brief Script descripiton (argparse argument) _description = None ## @brief argparse.ArgumentParser instance _parser = None ## @brief No instanciation def __init__(self): raise NotImplementedError("Static class") ## @brief Virtual method. Designed to initialize arguement parser. # @param parser ArgumentParser : Child class argument parser instance # @return MUST return the argument parser (NOT SURE ABOUT THAT !! Maybe it works by reference) # @throw LodelScriptError in case it is not implemented in the child class @classmethod def argparser_config(cls, parser): raise LodelScriptError("LodelScript.argparser_config() is a pure \ virtual method! MUST be implemented by ALL child classes") ## @brief Virtual method. Runs the script # @param args list # @return None or an integer that will be the script return code # @throw LodelScriptError in case it is not implemented in the child class @classmethod def run(cls, args): raise LodelScriptError("LodelScript.run() is a pure virtual method. \ MUST be implemented by ALL child classes") ## @brief Executes a script # # Called by main_run() # # Handles argument parsing and then call LodelScript.run() @classmethod def _run(cls): args = cls._parser.parse_args() return cls.run(args) ## @brief Append action name to the prog name # @note See argparse.ArgumentParser() prog argument # @return str @classmethod def _prog_name(cls): return '%s %s' % (sys.argv[0], cls._action) ## @brief Return the default description for an action # @return str @classmethod def _default_description(cls): return "Lodel2 script : %s" % cls._action ## @brief handles the help message of an action # @param msg str # @param return_code int : the default return code is 1 # @param exit_after bool : default value is True, so that there is an exit after the message printing @classmethod def help_exit(cls,msg = None, return_code = 1, exit_after = True): if not (msg is None): print(msg, file=sys.stderr) cls._parser.print_help() if exit_after: exit(1) ## @brief Registers the script class for an action # @param action_name str # @param cls LodelScript def script_registration(action_name, cls): __registered_scripts[action_name] = cls logger.info("New script registered : %s" % action_name) ## @brief Returns a list containing all available actions # @return list def _available_actions(): return [ act for act in __registered_scripts ] ## @brief Returns default runner's argument parser # @param ArgumentParser def _default_parser(): action_list = _available_actions() if len(action_list) > 0: action_list = ', '.join(sorted(action_list)) else: action_list = 'NO SCRIPT FOUND !' parser = argparse.ArgumentParser(description = "Lodel2 script runner") parser.add_argument('-L', '--list-actions', action='store_true', default=False, help="List available actions") parser.add_argument('action', metavar="ACTION", type=str, help="One of the following actions : %s" % action_list, nargs='?') parser.add_argument('option', metavar="OPTIONS", type=str, nargs='*', help="Action options. Use %s ACTION -h to have help on a specific \ action" % sys.argv[0]) return parser ## @brief Main function of lodel_admin.py script # # This function take care to run the good plugins and clean sys.argv from # action name before running script # # @return DO NOT RETURN BUT exit() ONCE SCRIPT EXECUTED !! def main_run(): default_parser = _default_parser() if len(sys.argv) == 1: default_parser.print_help() exit(1) #preparing sys.argv (deleting action) action = sys.argv[1].lower() if action not in __registered_scripts: #Trying to parse argument with default parser args = default_parser.parse_args() if args.list_actions: print("Available actions :") for sname in sorted(__registered_scripts.keys()): print("\t- %s" % __registered_scripts[sname]) exit(0) print("Unknow action '%s'\n" % action, file=sys.stderr) default_parser.print_help() exit(1) #OK action is known, preparing argv to pass it to the action script del(sys.argv[1]) script = __registered_scripts[action] ret = script._run() ret = 0 if ret is None else ret exit(ret) ## @page lodel2_script_doc Lodel2 scripting # @ingroup lodel2_script # # @section lodel2_script_adm Lodel2 instance administration scripts # # In Lodel2, it is possible to administrate instances using either Makefiles # or lodel_admin.py script ( see @ref lodel2_instance_admin ). # # The lodel_admin.py script takes an action as first argument. Each action # corresponds to a sub-script with its own options etc. To get a list # of all available action run python3 lodel_admin.py -L. # # @section lodel2_script_action lodel_admin.py actions # # Action implementation is done by class inheritance. To create a new action, # one has to write a @ref lodel.plugin.scripts.LodelScript "LodelScript" # derived class ( see @ref lodel.plugin.core_scripts "core_scripts.py" file # as example ) # # @subsection lodel2_script_inheritance LodelScript inheritance # # In order to implement properly a new action you have to write a new # @ref lodel.plugin.scripts.LodelScript "LodelScript" derivated class. # Some methods and attributes are mandatory to write a fully functionnal # derivated class. Here is a list : # # - mandatory methods # - @ref plugin.scripts.LodelScript.argparser_config() "argparser_config()" : #(classmethod) initialize argparse.Parser # - @ref plugin.scripts.LodelScript.run() "run()" : (classmethod) contains the #code that runs to perform the action # - mandatory attributes # - @ref plugin.scripts.LodelScript::_action "_action" : (class attribute) #stores action name # - @ref plugin.scripts.LodelScript::_description "_description" : (class #attribute) sotres a short action description # # @note On script's action registration : once child class is written you only # need to import it to trigger script's action registration (see # @ref plugin.scripts.MetaLodelScript ) #