loader.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. #
  2. # Metrix++, Copyright 2009-2019, Metrix++ Project
  3. # Link: https://github.com/metrixplusplus/metrixplusplus
  4. #
  5. # This file is a part of Metrix++ Tool.
  6. #
  7. import mpp.api
  8. import os
  9. import sys
  10. try:
  11. from configparser import ConfigParser
  12. except:
  13. from ConfigParser import ConfigParser
  14. import re
  15. import optparse
  16. class MultiOptionParser(optparse.OptionParser):
  17. class MultipleOption(optparse.Option):
  18. ACTIONS = optparse.Option.ACTIONS + ("multiopt",)
  19. STORE_ACTIONS = optparse.Option.STORE_ACTIONS + ("multiopt",)
  20. TYPED_ACTIONS = optparse.Option.TYPED_ACTIONS + ("multiopt",)
  21. ALWAYS_TYPED_ACTIONS = optparse.Option.ALWAYS_TYPED_ACTIONS + ("multiopt",)
  22. def take_action(self, action, dest, opt, value, values, parser):
  23. if action == "multiopt":
  24. values.ensure_value(dest, []).append(value)
  25. else:
  26. optparse.Option.take_action(self, action, dest, opt, value, values, parser)
  27. def __init__(self, *args, **kwargs):
  28. optparse.OptionParser.__init__(self, *args, option_class=self.MultipleOption, **kwargs)
  29. class Loader(object):
  30. def __init__(self):
  31. self.plugins = []
  32. self.hash = {}
  33. self.action = None
  34. def get_action(self):
  35. return self.action
  36. def get_plugin(self, name):
  37. return self.hash[name]['instance']
  38. def iterate_plugins(self, is_reversed = False):
  39. if is_reversed == False:
  40. for item in self.plugins:
  41. yield item['instance']
  42. else:
  43. for item in reversed(self.plugins):
  44. yield item['instance']
  45. def load(self, command, directories, args):
  46. class IniContainer(object):
  47. def __init__(self):
  48. self.plugins = []
  49. self.actions = []
  50. self.hash = {}
  51. def load_recursively(inicontainer, directory):
  52. active_plugins = []
  53. if os.path.exists(directory) == False or os.path.isdir(directory) == False:
  54. return active_plugins
  55. pattern = re.compile(r'.*[.]ini$', flags=re.IGNORECASE)
  56. dirList = sorted(os.listdir(directory))
  57. for fname in dirList:
  58. fname = os.path.join(directory, fname)
  59. if os.path.isdir(fname):
  60. active_plugins += load_recursively(inicontainer, fname)
  61. elif re.match(pattern, fname):
  62. config = ConfigParser()
  63. config.read(fname)
  64. item = {'package': config.get('Plugin', 'package'),
  65. 'module': config.get('Plugin', 'module'),
  66. 'class': config.get('Plugin', 'class'),
  67. 'version': config.get('Plugin', 'version'),
  68. 'depends': config.get('Plugin', 'depends'),
  69. 'actions': config.get('Plugin', 'actions'),
  70. 'enabled': config.getboolean('Plugin', 'enabled'),
  71. 'instance': None}
  72. if item['enabled']:
  73. item['actions'] = item['actions'].split(',')
  74. for (ind, action) in enumerate(item['actions']):
  75. action = action.strip()
  76. item['actions'][ind] = action
  77. if action not in inicontainer.actions + ['*', 'None', 'none', 'False', 'false']:
  78. inicontainer.actions.append(action)
  79. if action == '*' or action == command:
  80. active_plugins.append(item['package'] + '.' + item['module'])
  81. inicontainer.plugins.append(item)
  82. inicontainer.hash[item['package'] + '.' + item['module']] = item
  83. return active_plugins
  84. def list_dependants_recursively(inicontainer, required_plugin_name):
  85. assert required_plugin_name in list(inicontainer.hash.keys()), \
  86. "depends section requires unknown plugin: " + required_plugin_name
  87. item = inicontainer.hash[required_plugin_name]
  88. if item['depends'] in ('None', 'none', 'False', 'false'):
  89. return []
  90. result = []
  91. for child in item['depends'].split(','):
  92. child = child.strip()
  93. result += list_dependants_recursively(inicontainer, child)
  94. result.append(child)
  95. return result
  96. # configure python path for loading
  97. std_ext_dir = os.path.join(os.environ['METRIXPLUSPLUS_INSTALL_DIR'], 'ext')
  98. std_ext_priority_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  99. for each in [std_ext_dir] + directories:
  100. sys.path.append(each)
  101. inicontainer = IniContainer()
  102. # load available plugin ini files
  103. required_plugins = []
  104. for each in ([std_ext_priority_dir, std_ext_dir] + directories):
  105. required_plugins += load_recursively(inicontainer, each)
  106. required_plugins.sort(); # sort the plugin list to get similar results independant of the os
  107. # upgrade the list of required plugins
  108. required_and_dependant_plugins = []
  109. for name in required_plugins:
  110. for each in list_dependants_recursively(inicontainer, name):
  111. if each not in required_and_dependant_plugins:
  112. required_and_dependant_plugins.append(each)
  113. if name not in required_and_dependant_plugins:
  114. required_and_dependant_plugins.append(name)
  115. # load
  116. for plugin_name in required_and_dependant_plugins:
  117. item = inicontainer.hash[plugin_name]
  118. plugin = __import__(item['package'], globals(), locals(), [str(item['module'])])
  119. module_attr = plugin.__getattribute__(item['module'])
  120. class_attr = module_attr.__getattribute__(item['class'])
  121. item['instance'] = class_attr.__new__(class_attr)
  122. item['instance'].__init__()
  123. item['instance'].set_name(item['package'] + "." + item['module'])
  124. item['instance'].set_version(item['version'])
  125. item['instance']._set_plugin_loader(self)
  126. self.plugins.append(item)
  127. self.hash[plugin_name] = item
  128. optparser = MultiOptionParser(
  129. usage = "Usage: python %prog --help\n" +
  130. " python %prog <action> --help\n" +
  131. " python %prog <action> [options] -- [path 1] ... [path N]\n" +
  132. "\n" +
  133. "Actions: \n " + "\n ".join(sorted(inicontainer.actions)))
  134. if command in ['--help', '--h', '-h']:
  135. optparser.print_help()
  136. exit(0)
  137. if command.strip() == "":
  138. optparser.error("Mandatory action argument required")
  139. if command not in inicontainer.actions:
  140. optparser.error("action {action}: Unknown command".format(action=command))
  141. self.action = command
  142. optparser = MultiOptionParser(
  143. usage="Usage: python %prog --help\n"
  144. " python %prog {command} --help\n"
  145. " python %prog {command} [options] -- [path 1] ... [path N]".format(command=command))
  146. for item in self.iterate_plugins():
  147. if (isinstance(item, mpp.api.IConfigurable)):
  148. item.declare_configuration(optparser)
  149. (options, args) = optparser.parse_args(args)
  150. for item in self.iterate_plugins():
  151. if (isinstance(item, mpp.api.IConfigurable)):
  152. item.configure(options)
  153. for item in self.iterate_plugins():
  154. item.initialize()
  155. return args
  156. def unload(self):
  157. for item in self.iterate_plugins(is_reversed = True):
  158. item.terminate()
  159. def run(self, args):
  160. exit_code = 0
  161. for item in self.iterate_plugins():
  162. if (isinstance(item, mpp.api.IRunable)):
  163. exit_code += item.run(args)
  164. return exit_code
  165. def __repr__(self):
  166. result = object.__repr__(self) + ' with loaded:'
  167. for item in self.iterate_plugins():
  168. result += '\n\t' + item.__repr__()
  169. if isinstance(item, mpp.api.Parent):
  170. result += ' with subscribed:'
  171. for child in item.iterate_children():
  172. result += '\n\t\t' + child.__repr__()
  173. return result