mirror of
				https://github.com/yweber/lodel2.git
				synced 2025-10-31 19:49:02 +01:00 
			
		
		
		
	Lodel2 script helper class implementation (#122)
- Allow to extend easily lodel_admin.py script. - Writtent first lodel_admin.py action : discover-plugins
This commit is contained in:
		
					parent
					
						
							
								d14a7377a2
							
						
					
				
			
			
				commit
				
					
						cddf221988
					
				
			
		
					 6 changed files with 207 additions and 13 deletions
				
			
		|  | @ -32,10 +32,11 @@ if 'LODEL2_NO_SETTINGS_LOAD' not in os.environ: | |||
|     if not settings.started(): | ||||
|         settings('conf.d') | ||||
|     from lodel.settings import Settings | ||||
| 
 | ||||
|      | ||||
|     #Starts hooks | ||||
|     from lodel.plugin import LodelHook | ||||
|     from lodel.plugin import core_hooks | ||||
|     from lodel.plugin import core_scripts | ||||
| 
 | ||||
| def start(): | ||||
|     #Load plugins | ||||
|  |  | |||
|  | @ -4,14 +4,20 @@ import sys | |||
| import os, os.path | ||||
| import argparse | ||||
| 
 | ||||
| """ | ||||
| #Dirty hack to solve symlinks problems : | ||||
| #   When loader was imported the original one (LODEL_LIBDIR/install/loader) | ||||
| #   because lodel_admin.py is a symlink from this folder | ||||
| #Another solution can be delete loader from install folder | ||||
| sys.path[0] = os.getcwd() | ||||
| import loader | ||||
| """ | ||||
| ##@brief Dirty hack to avoid problems with simlink to lodel2 lib folder | ||||
| # | ||||
| #In instance folder we got a loader.py (the one we want to import here when | ||||
| #writing "import loader". The problem is that lodel_admin.py is a simlink to | ||||
| #LODEL2LIB_FOLDER/install/lodel_admin.py . In this folder there is the  | ||||
| #generic loader.py template. And when writing "import loader" its  | ||||
| #LODEL2LIB_FOLDER/install/loader.py that gets imported. | ||||
| # | ||||
| #In order to solve this problem the _simlink_hack() function delete the | ||||
| #LODEL2LIB_FOLDER/install entry from sys.path | ||||
| #@note This problem will be solved once lodel lib dir will be in  | ||||
| #/usr/lib/python3/lodel | ||||
| def _simlink_hack(): | ||||
|     sys.path[0] = os.getcwd() | ||||
| 
 | ||||
| ## @brief Utility method to generate python code given an emfile and a | ||||
| # translator | ||||
|  | @ -141,4 +147,10 @@ def update_plugin_discover_cache(path_list = None): | |||
|     for pname, pinfos in res['plugins'].items(): | ||||
|         print("\t- %s %s -> %s" % ( | ||||
|             pname, pinfos['version'], pinfos['path'])) | ||||
|      | ||||
| 
 | ||||
| if __name__ == '__main__': | ||||
|     _simlink_hack() | ||||
|     import loader | ||||
|     loader.start() | ||||
|     from lodel.plugin.scripts import main_run | ||||
|     main_run() | ||||
|  |  | |||
							
								
								
									
										29
									
								
								lodel/plugin/core_scripts.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								lodel/plugin/core_scripts.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| import lodel.plugin.scripts as lodel_script | ||||
| 
 | ||||
| class DiscoverPlugin(lodel_script.LodelScript): | ||||
|     _action = 'discover-plugin' | ||||
|     _description = 'Walk through given folders looking for plugins' | ||||
|      | ||||
|     @classmethod | ||||
|     def argparser_config(cls, parser): | ||||
|         parser.add_argument('-d', '--directory', | ||||
|             help="Directory to walk through looking for lodel2 plugins", | ||||
|             nargs='+') | ||||
|         parser.add_argument('-l', '--list-only', default=False, | ||||
|             action = 'store_true', | ||||
|             help="Use this option to print a list of discovered plugins \ | ||||
| without modifying existing cache") | ||||
| 
 | ||||
|     @classmethod | ||||
|     def run(cls, args): | ||||
|         from lodel.plugin.plugins import Plugin | ||||
|         if args.directory is None or len(args.directory) == 0: | ||||
|             cls.help_exit("Specify a least one directory") | ||||
|         no_cache = args.list_only | ||||
|         res = Plugin.discover(args.directory, no_cache) | ||||
|         print("Found plugins in : %s" % ', '.join(args.directory)) | ||||
|         for pname, pinfos in res['plugins'].items(): | ||||
|             print("\t- %s(%s) in %s" % ( | ||||
|                 pname, pinfos['version'], pinfos['path'])) | ||||
|              | ||||
| 
 | ||||
|  | @ -1,2 +1,5 @@ | |||
| class PluginError(Exception): | ||||
|     pass | ||||
| 
 | ||||
| class LodelScriptError(Exception): | ||||
|     pass | ||||
|  |  | |||
|  | @ -512,9 +512,12 @@ name differ from the one found in plugin's init file" | |||
|      | ||||
|     ##@brief Reccursively walk throught paths to find plugin, then stores | ||||
|     #found plugin in a file... | ||||
|     #@param paths list : list of directory paths | ||||
|     #@param no_cache bool : if true only return a list of found plugins  | ||||
|     #without modifying the cache file | ||||
|     #@return a dict {'path_list': [...], 'plugins': { see @ref _discover }} | ||||
|     @classmethod | ||||
|     def discover(cls, paths = None): | ||||
|     def discover(cls, paths = None, no_cache = False): | ||||
|         logger.info("Running plugin discover") | ||||
|         if paths is None: | ||||
|             paths = DEFAULT_PLUGINS_PATH_LIST | ||||
|  | @ -535,8 +538,9 @@ name differ from the one found in plugin's init file" | |||
|                 pass | ||||
|         result = {'path_list': paths, 'plugins': result} | ||||
|         #Writing to cache | ||||
|         with open(DISCOVER_CACHE_FILENAME, 'w+') as pdcache: | ||||
|             pdcache.write(json.dumps(result)) | ||||
|         if not no_cache: | ||||
|             with open(DISCOVER_CACHE_FILENAME, 'w+') as pdcache: | ||||
|                 pdcache.write(json.dumps(result)) | ||||
|         return result | ||||
|      | ||||
|     ##@brief Return discover result | ||||
|  |  | |||
							
								
								
									
										145
									
								
								lodel/plugin/scripts.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										145
									
								
								lodel/plugin/scripts.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,145 @@ | |||
| import argparse | ||||
| import sys | ||||
| 
 | ||||
| from lodel import logger | ||||
| from lodel.exceptions import * | ||||
| 
 | ||||
| ##@brief Stores registered scripts | ||||
| __registered_scripts = dict() | ||||
| 
 | ||||
| ##@brief LodelScript metaclass that allows to "catch" child class | ||||
| #declaration | ||||
| # | ||||
| #Automatic script registration on child class declaration | ||||
| class MetaLodelScript(type): | ||||
|      | ||||
|     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: | ||||
|             print("Dropped : ", name, bases) | ||||
|             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 | ||||
|     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) | ||||
| 
 | ||||
| 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 argparser ArgumentParser : Child class argument parser instance | ||||
|     #@return MUST return the argument parser (NOT SURE ABOUT THAT !! Maybe it \ | ||||
|     #works by reference) | ||||
|     @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. Run the script | ||||
|     #@return None or an integer that will be the script return code | ||||
|     @classmethod | ||||
|     def run(cls, args): | ||||
|         raise LodelScriptError("LodelScript.run() is a pure virtual method. \ | ||||
| MUST be implemented by ALL child classes") | ||||
|      | ||||
|     ##@brief Called by main_run() to execute a script. | ||||
|     # | ||||
|     #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 | ||||
|     @classmethod | ||||
|     def _prog_name(cls): | ||||
|         return '%s %s' % (sys.argv[0], self._action) | ||||
|      | ||||
|     ##@brief Return the default description for an action | ||||
|     @classmethod | ||||
|     def _default_description(cls): | ||||
|         return "Lodel2 script : %s" % cls._action | ||||
|      | ||||
|     @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) | ||||
| 
 | ||||
| def script_registration(action_name, cls): | ||||
|     __registered_scripts[action_name] = cls | ||||
|     logger.info("New script registered : %s" % action_name) | ||||
| 
 | ||||
| ##@brief Return a list containing all available actions | ||||
| def _available_actions(): | ||||
|     return [ act for act in __registered_scripts ] | ||||
| 
 | ||||
| ##@brief Returns default runner argument parser | ||||
| 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('action', metavar="ACTION", type=str, | ||||
|         help="One of the following actions : %s" % action_list) | ||||
|     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 | ||||
| 
 | ||||
| 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() | ||||
|     del(sys.argv[1]) | ||||
|     if action not in __registered_scripts: | ||||
|         print("Unknow action '%s'\n" % action, file=sys.stderr) | ||||
|         default_parser.print_help() | ||||
|         exit(1) | ||||
|     script = __registered_scripts[action] | ||||
|     ret = script._run() | ||||
|     ret = 0 if ret is None else ret | ||||
|     exit(ret) | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Yann
				Yann