No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

scripts.py 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. #
  2. # This file is part of Lodel 2 (https://github.com/OpenEdition)
  3. #
  4. # Copyright (C) 2015-2017 Cléo UMS-3287
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as published
  8. # by the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program 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 Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import argparse
  20. import sys
  21. from lodel.context import LodelContext
  22. LodelContext.expose_modules(globals(), {
  23. 'lodel.logger': 'logger',
  24. 'lodel.exceptions': ['LodelException', 'LodelExceptions',
  25. 'LodelFatalError', 'DataNoneValid', 'FieldValidationError']})
  26. ## @defgroup lodel2_script Administration scripts
  27. # @ingroup lodel2_plugins
  28. ## @package lodel.plugin.scripts Lodel2 utility for writting administration scripts
  29. # @ingroup lodel2_plugins
  30. # @ingroup lodel2_script
  31. ## @brief Stores registered scripts
  32. # @todo store it in MetaLodelScript
  33. __registered_scripts = dict()
  34. ## @brief LodelScript metaclass that allows to "catch" child class declaration
  35. # @ingroup lodel2_script
  36. # @ingroup lodel2_plugins
  37. #
  38. # Automatic action registration on child class declaration
  39. class MetaLodelScript(type):
  40. ##
  41. # @param name str : action's name
  42. # @param bases list
  43. # @param attrs list
  44. def __init__(self, name, bases, attrs):
  45. # Here we can store all child classes of LodelScript
  46. super().__init__(name, bases, attrs)
  47. if len(bases) == 1 and bases[0] == object:
  48. return
  49. self.__register_script(name)
  50. #_action initialization
  51. if self._action is None:
  52. logger.warning("%s._action is None. Trying to use class name as \
  53. action identifier" % name)
  54. self._action = name
  55. self._action = self._action.lower()
  56. if self._description is None:
  57. self._description = self._default_description()
  58. self._parser = argparse.ArgumentParser(
  59. prog = self._prog_name(),
  60. description = self._description)
  61. self.argparser_config(self._parser)
  62. ## @brief Handles script registration
  63. # @note Script list is maitained in lodel.plugin.admin_script.__registered_scripts
  64. # @param name str
  65. def __register_script(self, name):
  66. if self._action is None:
  67. logger.warning("%s._action is None. Trying to use class name as \
  68. action identifier" % name)
  69. self._action = name
  70. self._action = self._action.lower()
  71. script_registration(self._action, self)
  72. def __str__(self):
  73. return '%s : %s' % (self._action, self._description)
  74. ## @brief Class designed to facilitate custom script writting
  75. # @ingroup lodel2_plugins
  76. # @ingroup lodel2_script
  77. class LodelScript(object, metaclass=MetaLodelScript):
  78. ## @brief A string to identify the action
  79. _action = None
  80. ## @brief Script descripiton (argparse argument)
  81. _description = None
  82. ## @brief argparse.ArgumentParser instance
  83. _parser = None
  84. ## @brief No instanciation
  85. def __init__(self):
  86. raise NotImplementedError("Static class")
  87. ## @brief Virtual method. Designed to initialize arguement parser.
  88. # @param parser ArgumentParser : Child class argument parser instance
  89. # @return MUST return the argument parser (NOT SURE ABOUT THAT !! Maybe it works by reference)
  90. # @throw LodelScriptError in case it is not implemented in the child class
  91. @classmethod
  92. def argparser_config(cls, parser):
  93. raise LodelScriptError("LodelScript.argparser_config() is a pure \
  94. virtual method! MUST be implemented by ALL child classes")
  95. ## @brief Virtual method. Runs the script
  96. # @param args list
  97. # @return None or an integer that will be the script return code
  98. # @throw LodelScriptError in case it is not implemented in the child class
  99. @classmethod
  100. def run(cls, args):
  101. raise LodelScriptError("LodelScript.run() is a pure virtual method. \
  102. MUST be implemented by ALL child classes")
  103. ## @brief Executes a script
  104. #
  105. # Called by main_run()
  106. #
  107. # Handles argument parsing and then call LodelScript.run()
  108. @classmethod
  109. def _run(cls):
  110. args = cls._parser.parse_args()
  111. return cls.run(args)
  112. ## @brief Append action name to the prog name
  113. # @note See argparse.ArgumentParser() prog argument
  114. # @return str
  115. @classmethod
  116. def _prog_name(cls):
  117. return '%s %s' % (sys.argv[0], cls._action)
  118. ## @brief Return the default description for an action
  119. # @return str
  120. @classmethod
  121. def _default_description(cls):
  122. return "Lodel2 script : %s" % cls._action
  123. ## @brief handles the help message of an action
  124. # @param msg str
  125. # @param return_code int : the default return code is 1
  126. # @param exit_after bool : default value is True, so that there is an exit after the message printing
  127. @classmethod
  128. def help_exit(cls,msg = None, return_code = 1, exit_after = True):
  129. if not (msg is None):
  130. print(msg, file=sys.stderr)
  131. cls._parser.print_help()
  132. if exit_after:
  133. exit(1)
  134. ## @brief Registers the script class for an action
  135. # @param action_name str
  136. # @param cls LodelScript
  137. def script_registration(action_name, cls):
  138. __registered_scripts[action_name] = cls
  139. logger.info("New script registered : %s" % action_name)
  140. ## @brief Returns a list containing all available actions
  141. # @return list
  142. def _available_actions():
  143. return [ act for act in __registered_scripts ]
  144. ## @brief Returns default runner's argument parser
  145. # @param ArgumentParser
  146. def _default_parser():
  147. action_list = _available_actions()
  148. if len(action_list) > 0:
  149. action_list = ', '.join(sorted(action_list))
  150. else:
  151. action_list = 'NO SCRIPT FOUND !'
  152. parser = argparse.ArgumentParser(description = "Lodel2 script runner")
  153. parser.add_argument('-L', '--list-actions', action='store_true',
  154. default=False, help="List available actions")
  155. parser.add_argument('action', metavar="ACTION", type=str,
  156. help="One of the following actions : %s" % action_list, nargs='?')
  157. parser.add_argument('option', metavar="OPTIONS", type=str, nargs='*',
  158. help="Action options. Use %s ACTION -h to have help on a specific \
  159. action" % sys.argv[0])
  160. return parser
  161. ## @brief Main function of lodel_admin.py script
  162. #
  163. # This function take care to run the good plugins and clean sys.argv from
  164. # action name before running script
  165. #
  166. # @return DO NOT RETURN BUT exit() ONCE SCRIPT EXECUTED !!
  167. def main_run():
  168. default_parser = _default_parser()
  169. if len(sys.argv) == 1:
  170. default_parser.print_help()
  171. exit(1)
  172. #preparing sys.argv (deleting action)
  173. action = sys.argv[1].lower()
  174. if action not in __registered_scripts:
  175. #Trying to parse argument with default parser
  176. args = default_parser.parse_args()
  177. if args.list_actions:
  178. print("Available actions :")
  179. for sname in sorted(__registered_scripts.keys()):
  180. print("\t- %s" % __registered_scripts[sname])
  181. exit(0)
  182. print("Unknow action '%s'\n" % action, file=sys.stderr)
  183. default_parser.print_help()
  184. exit(1)
  185. #OK action is known, preparing argv to pass it to the action script
  186. del(sys.argv[1])
  187. script = __registered_scripts[action]
  188. ret = script._run()
  189. ret = 0 if ret is None else ret
  190. exit(ret)
  191. ## @page lodel2_script_doc Lodel2 scripting
  192. # @ingroup lodel2_script
  193. #
  194. # @section lodel2_script_adm Lodel2 instance administration scripts
  195. #
  196. # In Lodel2, it is possible to administrate instances using either Makefiles
  197. # or lodel_admin.py script ( see @ref lodel2_instance_admin ).
  198. #
  199. # The lodel_admin.py script takes an action as first argument. Each action
  200. # corresponds to a sub-script with its own options etc. To get a list
  201. # of all available action run <code>python3 lodel_admin.py -L</code>.
  202. #
  203. # @section lodel2_script_action lodel_admin.py actions
  204. #
  205. # Action implementation is done by class inheritance. To create a new action,
  206. # one has to write a @ref lodel.plugin.scripts.LodelScript "LodelScript"
  207. # derived class ( see @ref lodel.plugin.core_scripts "core_scripts.py" file
  208. # as example )
  209. #
  210. # @subsection lodel2_script_inheritance LodelScript inheritance
  211. #
  212. # In order to implement properly a new action you have to write a new
  213. # @ref lodel.plugin.scripts.LodelScript "LodelScript" derivated class.
  214. # Some methods and attributes are mandatory to write a fully functionnal
  215. # derivated class. Here is a list :
  216. #
  217. # - mandatory methods
  218. # - @ref plugin.scripts.LodelScript.argparser_config() "argparser_config()" :
  219. #(classmethod) initialize argparse.Parser
  220. # - @ref plugin.scripts.LodelScript.run() "run()" : (classmethod) contains the
  221. #code that runs to perform the action
  222. # - mandatory attributes
  223. # - @ref plugin.scripts.LodelScript::_action "_action" : (class attribute)
  224. #stores action name
  225. # - @ref plugin.scripts.LodelScript::_description "_description" : (class
  226. #attribute) sotres a short action description
  227. #
  228. # @note On script's action registration : once child class is written you only
  229. # need to import it to trigger script's action registration (see
  230. # @ref plugin.scripts.MetaLodelScript )
  231. #