loader.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #
  2. # Metrix++, Copyright 2009-2013, Metrix++ Project
  3. # Link: http://metrixplusplus.sourceforge.net
  4. #
  5. # This file is a part of Metrix++ Tool.
  6. #
  7. # Metrix++ is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, version 3 of the License.
  10. #
  11. # Metrix++ is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Metrix++. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import mpp.api
  20. import os
  21. import sys
  22. import ConfigParser
  23. import re
  24. import optparse
  25. import logging
  26. class MultiOptionParser(optparse.OptionParser):
  27. class MultipleOption(optparse.Option):
  28. ACTIONS = optparse.Option.ACTIONS + ("multiopt",)
  29. STORE_ACTIONS = optparse.Option.STORE_ACTIONS + ("multiopt",)
  30. TYPED_ACTIONS = optparse.Option.TYPED_ACTIONS + ("multiopt",)
  31. ALWAYS_TYPED_ACTIONS = optparse.Option.ALWAYS_TYPED_ACTIONS + ("multiopt",)
  32. def take_action(self, action, dest, opt, value, values, parser):
  33. if action == "multiopt":
  34. values.ensure_value(dest, []).append(value)
  35. else:
  36. optparse.Option.take_action(self, action, dest, opt, value, values, parser)
  37. def __init__(self, *args, **kwargs):
  38. optparse.OptionParser.__init__(self, *args, option_class=self.MultipleOption, **kwargs)
  39. class Loader(object):
  40. def __init__(self):
  41. self.plugins = []
  42. self.hash = {}
  43. self.action = None
  44. def get_action(self):
  45. return self.action
  46. def get_plugin(self, name):
  47. return self.hash[name]['instance']
  48. def iterate_plugins(self, is_reversed = False):
  49. if is_reversed == False:
  50. for item in self.plugins:
  51. yield item['instance']
  52. else:
  53. for item in reversed(self.plugins):
  54. yield item['instance']
  55. def load(self, command, directories, args):
  56. class IniContainer(object):
  57. def __init__(self):
  58. self.plugins = []
  59. self.actions = []
  60. self.hash = {}
  61. def load_recursively(inicontainer, directory):
  62. active_plugins = []
  63. if os.path.exists(directory) == False or os.path.isdir(directory) == False:
  64. return active_plugins
  65. pattern = re.compile(r'.*[.]ini$', flags=re.IGNORECASE)
  66. dirList = os.listdir(directory)
  67. for fname in dirList:
  68. fname = os.path.join(directory, fname)
  69. if os.path.isdir(fname):
  70. active_plugins += load_recursively(inicontainer, fname)
  71. elif re.match(pattern, fname):
  72. config = ConfigParser.ConfigParser()
  73. config.read(fname)
  74. item = {'package': config.get('Plugin', 'package'),
  75. 'module': config.get('Plugin', 'module'),
  76. 'class': config.get('Plugin', 'class'),
  77. 'version': config.get('Plugin', 'version'),
  78. 'depends': config.get('Plugin', 'depends'),
  79. 'actions': config.get('Plugin', 'actions'),
  80. 'enabled': config.getboolean('Plugin', 'enabled'),
  81. 'instance': None}
  82. if item['enabled']:
  83. item['actions'] = item['actions'].split(',')
  84. for (ind, action) in enumerate(item['actions']):
  85. action = action.strip()
  86. item['actions'][ind] = action
  87. if action not in inicontainer.actions + ['*', 'None', 'none', 'False', 'false']:
  88. inicontainer.actions.append(action)
  89. if action == '*' or action == command:
  90. active_plugins.append(item['package'] + '.' + item['module'])
  91. inicontainer.plugins.append(item)
  92. inicontainer.hash[item['package'] + '.' + item['module']] = item
  93. return active_plugins
  94. def list_dependants_recursively(inicontainer, required_plugin_name):
  95. assert required_plugin_name in inicontainer.hash.keys(), \
  96. "depends section requires unknown plugin: " + required_plugin_name
  97. item = inicontainer.hash[required_plugin_name]
  98. if item['depends'] in ('None', 'none', 'False', 'false'):
  99. return []
  100. result = []
  101. for child in item['depends'].split(','):
  102. child = child.strip()
  103. result += list_dependants_recursively(inicontainer, child)
  104. result.append(child)
  105. return result
  106. logging.debug("Additional plugin loading locations: " + str(directories))
  107. # configure python path for loading
  108. std_ext_dir = os.path.join(os.environ['METRIXPLUSPLUS_INSTALL_DIR'], 'ext')
  109. std_ext_priority_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
  110. for each in [std_ext_dir] + directories:
  111. sys.path.append(each)
  112. inicontainer = IniContainer()
  113. # load available plugin ini files
  114. required_plugins = []
  115. for each in ([std_ext_priority_dir, std_ext_dir] + directories):
  116. required_plugins += load_recursively(inicontainer, each)
  117. # upgrade the list of required plugins
  118. required_and_dependant_plugins = []
  119. for name in required_plugins:
  120. for each in list_dependants_recursively(inicontainer, name):
  121. if each not in required_and_dependant_plugins:
  122. required_and_dependant_plugins.append(each)
  123. required_and_dependant_plugins.append(name)
  124. # load
  125. for plugin_name in required_and_dependant_plugins:
  126. item = inicontainer.hash[plugin_name]
  127. logging.debug("Loading plugin: " + str(item['package']) + ' ' + str(item['module']))
  128. plugin = __import__(item['package'], globals(), locals(), [item['module']], -1)
  129. module_attr = plugin.__getattribute__(item['module'])
  130. class_attr = module_attr.__getattribute__(item['class'])
  131. item['instance'] = class_attr.__new__(class_attr)
  132. item['instance'].__init__()
  133. item['instance'].set_name(item['package'] + "." + item['module'])
  134. item['instance'].set_version(item['version'])
  135. item['instance']._set_plugin_loader(self)
  136. self.plugins.append(item)
  137. self.hash[plugin_name] = item
  138. optparser = MultiOptionParser(
  139. usage = "Usage: python %prog --help\n" +
  140. " python %prog <action> --help\n" +
  141. " python %prog <action> [options] -- [path 1] ... [path N]\n" +
  142. "\n" +
  143. "Actions: \n " + "\n ".join(inicontainer.actions))
  144. if command in ['--help', '--h', '-h']:
  145. optparser.print_help()
  146. exit(0)
  147. if command.strip() == "":
  148. optparser.error("Mandatory action argument required")
  149. if command not in inicontainer.actions:
  150. optparser.error("action {action}: Unknown command".format(action=command))
  151. self.action = command
  152. optparser = MultiOptionParser(
  153. usage="Usage: python %prog --help\n"
  154. " python %prog {command} --help\n"
  155. " python %prog {command} [options] -- [path 1] ... [path N]".format(command=command))
  156. for item in self.iterate_plugins():
  157. if (isinstance(item, mpp.api.IConfigurable)):
  158. logging.debug("declaring options for " + item.get_name())
  159. item.declare_configuration(optparser)
  160. logging.debug("after has option std.code.lines.code " + str(optparser.has_option('--std.code.lines.code')))
  161. (options, args) = optparser.parse_args(args)
  162. for item in self.iterate_plugins():
  163. if (isinstance(item, mpp.api.IConfigurable)):
  164. item.configure(options)
  165. for item in self.iterate_plugins():
  166. item.initialize()
  167. return args
  168. def unload(self):
  169. for item in self.iterate_plugins(is_reversed = True):
  170. item.terminate()
  171. def run(self, args):
  172. exit_code = 0
  173. for item in self.iterate_plugins():
  174. if (isinstance(item, mpp.api.IRunable)):
  175. exit_code += item.run(args)
  176. return exit_code
  177. def __repr__(self):
  178. result = object.__repr__(self) + ' with loaded:'
  179. for item in self.iterate_plugins():
  180. result += '\n\t' + item.__repr__()
  181. if isinstance(item, mpp.api.Parent):
  182. result += ' with subscribed:'
  183. for child in item.iterate_children():
  184. result += '\n\t\t' + child.__repr__()
  185. return result