Browse Source

Lodel2 script helper class implementation (#122)

- Allow to extend easily lodel_admin.py script.
- Writtent first lodel_admin.py action : discover-plugins
Yann Weber 8 years ago
parent
commit
cddf221988

+ 2
- 1
install/loader.py View File

@@ -32,10 +32,11 @@ if 'LODEL2_NO_SETTINGS_LOAD' not in os.environ:
32 32
     if not settings.started():
33 33
         settings('conf.d')
34 34
     from lodel.settings import Settings
35
-
35
+    
36 36
     #Starts hooks
37 37
     from lodel.plugin import LodelHook
38 38
     from lodel.plugin import core_hooks
39
+    from lodel.plugin import core_scripts
39 40
 
40 41
 def start():
41 42
     #Load plugins

+ 21
- 9
install/lodel_admin.py View File

@@ -4,14 +4,20 @@ import sys
4 4
 import os, os.path
5 5
 import argparse
6 6
 
7
-"""
8
-#Dirty hack to solve symlinks problems :
9
-#   When loader was imported the original one (LODEL_LIBDIR/install/loader)
10
-#   because lodel_admin.py is a symlink from this folder
11
-#Another solution can be delete loader from install folder
12
-sys.path[0] = os.getcwd()
13
-import loader
14
-"""
7
+##@brief Dirty hack to avoid problems with simlink to lodel2 lib folder
8
+#
9
+#In instance folder we got a loader.py (the one we want to import here when
10
+#writing "import loader". The problem is that lodel_admin.py is a simlink to
11
+#LODEL2LIB_FOLDER/install/lodel_admin.py . In this folder there is the 
12
+#generic loader.py template. And when writing "import loader" its 
13
+#LODEL2LIB_FOLDER/install/loader.py that gets imported.
14
+#
15
+#In order to solve this problem the _simlink_hack() function delete the
16
+#LODEL2LIB_FOLDER/install entry from sys.path
17
+#@note This problem will be solved once lodel lib dir will be in 
18
+#/usr/lib/python3/lodel
19
+def _simlink_hack():
20
+    sys.path[0] = os.getcwd()
15 21
 
16 22
 ## @brief Utility method to generate python code given an emfile and a
17 23
 # translator
@@ -141,4 +147,10 @@ def update_plugin_discover_cache(path_list = None):
141 147
     for pname, pinfos in res['plugins'].items():
142 148
         print("\t- %s %s -> %s" % (
143 149
             pname, pinfos['version'], pinfos['path']))
144
-    
150
+
151
+if __name__ == '__main__':
152
+    _simlink_hack()
153
+    import loader
154
+    loader.start()
155
+    from lodel.plugin.scripts import main_run
156
+    main_run()

+ 29
- 0
lodel/plugin/core_scripts.py View File

@@ -0,0 +1,29 @@
1
+import lodel.plugin.scripts as lodel_script
2
+
3
+class DiscoverPlugin(lodel_script.LodelScript):
4
+    _action = 'discover-plugin'
5
+    _description = 'Walk through given folders looking for plugins'
6
+    
7
+    @classmethod
8
+    def argparser_config(cls, parser):
9
+        parser.add_argument('-d', '--directory',
10
+            help="Directory to walk through looking for lodel2 plugins",
11
+            nargs='+')
12
+        parser.add_argument('-l', '--list-only', default=False,
13
+            action = 'store_true',
14
+            help="Use this option to print a list of discovered plugins \
15
+without modifying existing cache")
16
+
17
+    @classmethod
18
+    def run(cls, args):
19
+        from lodel.plugin.plugins import Plugin
20
+        if args.directory is None or len(args.directory) == 0:
21
+            cls.help_exit("Specify a least one directory")
22
+        no_cache = args.list_only
23
+        res = Plugin.discover(args.directory, no_cache)
24
+        print("Found plugins in : %s" % ', '.join(args.directory))
25
+        for pname, pinfos in res['plugins'].items():
26
+            print("\t- %s(%s) in %s" % (
27
+                pname, pinfos['version'], pinfos['path']))
28
+            
29
+

+ 3
- 0
lodel/plugin/exceptions.py View File

@@ -1,2 +1,5 @@
1 1
 class PluginError(Exception):
2 2
     pass
3
+
4
+class LodelScriptError(Exception):
5
+    pass

+ 7
- 3
lodel/plugin/plugins.py View File

@@ -512,9 +512,12 @@ name differ from the one found in plugin's init file"
512 512
     
513 513
     ##@brief Reccursively walk throught paths to find plugin, then stores
514 514
     #found plugin in a file...
515
+    #@param paths list : list of directory paths
516
+    #@param no_cache bool : if true only return a list of found plugins 
517
+    #without modifying the cache file
515 518
     #@return a dict {'path_list': [...], 'plugins': { see @ref _discover }}
516 519
     @classmethod
517
-    def discover(cls, paths = None):
520
+    def discover(cls, paths = None, no_cache = False):
518 521
         logger.info("Running plugin discover")
519 522
         if paths is None:
520 523
             paths = DEFAULT_PLUGINS_PATH_LIST
@@ -535,8 +538,9 @@ name differ from the one found in plugin's init file"
535 538
                 pass
536 539
         result = {'path_list': paths, 'plugins': result}
537 540
         #Writing to cache
538
-        with open(DISCOVER_CACHE_FILENAME, 'w+') as pdcache:
539
-            pdcache.write(json.dumps(result))
541
+        if not no_cache:
542
+            with open(DISCOVER_CACHE_FILENAME, 'w+') as pdcache:
543
+                pdcache.write(json.dumps(result))
540 544
         return result
541 545
     
542 546
     ##@brief Return discover result

+ 145
- 0
lodel/plugin/scripts.py View File

@@ -0,0 +1,145 @@
1
+import argparse
2
+import sys
3
+
4
+from lodel import logger
5
+from lodel.exceptions import *
6
+
7
+##@brief Stores registered scripts
8
+__registered_scripts = dict()
9
+
10
+##@brief LodelScript metaclass that allows to "catch" child class
11
+#declaration
12
+#
13
+#Automatic script registration on child class declaration
14
+class MetaLodelScript(type):
15
+    
16
+    def __init__(self, name, bases, attrs):
17
+        #Here we can store all child classes of LodelScript
18
+        super().__init__(name, bases, attrs)
19
+        if len(bases) == 1 and bases[0] == object:
20
+            print("Dropped : ", name, bases)
21
+            return
22
+
23
+        self.__register_script(name)
24
+        #_action initialization
25
+        if self._action is None:
26
+            logger.warning("%s._action is None. Trying to use class name as \
27
+action identifier" % name)
28
+            self._action = name
29
+        self._action = self._action.lower()
30
+        if self._description is None:
31
+            self._description = self._default_description()
32
+        self._parser = argparse.ArgumentParser(
33
+            prog = self._prog_name,
34
+            description = self._description)
35
+        self.argparser_config(self._parser)
36
+            
37
+    
38
+    ##@brief Handles script registration
39
+    #@note Script list is maitained in 
40
+    #lodel.plugin.admin_script.__registered_scripts
41
+    def __register_script(self, name):
42
+        if self._action is None:
43
+            logger.warning("%s._action is None. Trying to use class name as \
44
+action identifier" % name)
45
+            self._action = name
46
+        self._action = self._action.lower()
47
+        script_registration(self._action, self)
48
+
49
+class LodelScript(object, metaclass=MetaLodelScript):
50
+    
51
+    ##@brief A string to identify the action
52
+    _action = None
53
+    ##@brief Script descripiton (argparse argument)
54
+    _description = None
55
+    ##@brief argparse.ArgumentParser instance
56
+    _parser = None
57
+    
58
+    ##@brief No instanciation
59
+    def __init__(self):
60
+        raise NotImplementedError("Static class")
61
+    
62
+    ##@brief Virtual method. Designed to initialize arguement parser.
63
+    #@param argparser ArgumentParser : Child class argument parser instance
64
+    #@return MUST return the argument parser (NOT SURE ABOUT THAT !! Maybe it \
65
+    #works by reference)
66
+    @classmethod
67
+    def argparser_config(cls, parser):
68
+        raise LodelScriptError("LodelScript.argparser_config() is a pure \
69
+virtual method! MUST be implemented by ALL child classes")
70
+    
71
+    ##@brief Virtual method. Run the script
72
+    #@return None or an integer that will be the script return code
73
+    @classmethod
74
+    def run(cls, args):
75
+        raise LodelScriptError("LodelScript.run() is a pure virtual method. \
76
+MUST be implemented by ALL child classes")
77
+    
78
+    ##@brief Called by main_run() to execute a script.
79
+    #
80
+    #Handles argument parsing and then call LodelScript.run()
81
+    @classmethod
82
+    def _run(cls):
83
+        args = cls._parser.parse_args()
84
+        return cls.run(args)
85
+
86
+    ##@brief Append action name to the prog name
87
+    #@note See argparse.ArgumentParser() prog argument
88
+    @classmethod
89
+    def _prog_name(cls):
90
+        return '%s %s' % (sys.argv[0], self._action)
91
+    
92
+    ##@brief Return the default description for an action
93
+    @classmethod
94
+    def _default_description(cls):
95
+        return "Lodel2 script : %s" % cls._action
96
+    
97
+    @classmethod
98
+    def help_exit(cls,msg = None, return_code = 1, exit_after = True):
99
+        if not (msg is None):
100
+            print(msg, file=sys.stderr)
101
+        cls._parser.print_help()
102
+        if exit_after:
103
+            exit(1)
104
+
105
+def script_registration(action_name, cls):
106
+    __registered_scripts[action_name] = cls
107
+    logger.info("New script registered : %s" % action_name)
108
+
109
+##@brief Return a list containing all available actions
110
+def _available_actions():
111
+    return [ act for act in __registered_scripts ]
112
+
113
+##@brief Returns default runner argument parser
114
+def _default_parser():
115
+
116
+    action_list = _available_actions()
117
+    if len(action_list) > 0:
118
+        action_list = ', '.join(sorted(action_list))
119
+    else:
120
+        action_list = 'NO SCRIPT FOUND !'
121
+
122
+    parser = argparse.ArgumentParser(description = "Lodel2 script runner")
123
+    parser.add_argument('action', metavar="ACTION", type=str,
124
+        help="One of the following actions : %s" % action_list)
125
+    parser.add_argument('option', metavar="OPTIONS", type=str, nargs='*',
126
+        help="Action options. Use %s ACTION -h to have help on a specific \
127
+action" % sys.argv[0])
128
+    return parser
129
+
130
+def main_run():
131
+    default_parser = _default_parser()
132
+    if len(sys.argv) == 1:
133
+        default_parser.print_help()
134
+        exit(1)
135
+    #preparing sys.argv (deleting action)
136
+    action = sys.argv[1].lower()
137
+    del(sys.argv[1])
138
+    if action not in __registered_scripts:
139
+        print("Unknow action '%s'\n" % action, file=sys.stderr)
140
+        default_parser.print_help()
141
+        exit(1)
142
+    script = __registered_scripts[action]
143
+    ret = script._run()
144
+    ret = 0 if ret is None else ret
145
+    exit(ret)

Loading…
Cancel
Save