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.

176 lines
6.0 KiB

  1. # Copyright (C) 2019-Today: GRAP (<http://www.grap.coop/>)
  2. # @author: Sylvain LE GAL (https://twitter.com/legalsylvain)
  3. # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
  4. import os
  5. # import json
  6. import pygount
  7. from pathlib import Path
  8. import logging
  9. # import os
  10. # import subprocess
  11. from odoo import api, fields, models
  12. from odoo.tools.safe_eval import safe_eval
  13. from odoo.modules.module import get_module_path
  14. _logger = logging.getLogger(__name__)
  15. class IrModuleModule(models.Model):
  16. _inherit = 'ir.module.module'
  17. author_ids = fields.Many2many(
  18. string='Authors', comodel_name='ir.module.author', readonly=True,
  19. relation='ir_module_module_author_rel')
  20. module_type_id = fields.Many2one(
  21. string='Module Type', comodel_name='ir.module.type', readonly=True)
  22. python_code_qty = fields.Integer(string='Python Lines', readonly=True)
  23. xml_code_qty = fields.Integer(string='XML Lines', readonly=True)
  24. js_code_qty = fields.Integer(string='JS Lines', readonly=True)
  25. css_code_qty = fields.Integer(string='CSS Lines', readonly=True)
  26. # Overloadable Section
  27. @api.model
  28. def _get_analyse_settings(self):
  29. """Return a dictionnary of data analysed
  30. Overload this function if you want to analyse other data
  31. {
  32. 'extension': {
  33. 'data_to_analyse': 'field_name',
  34. },
  35. }, [...]
  36. extension: extension of the file, with the '.'
  37. data_to_analyse : possible value : code, documentation, empty, string
  38. field_name: Odoo field name to store the analysis
  39. """
  40. return {
  41. '.py': {
  42. 'code': 'python_code_qty',
  43. },
  44. '.xml': {
  45. 'code': 'xml_code_qty',
  46. },
  47. '.js': {
  48. 'code': 'js_code_qty',
  49. },
  50. '.css': {
  51. 'code': 'css_code_qty',
  52. },
  53. }
  54. @api.model
  55. def _get_clean_analyse_values(self):
  56. """List of fields to unset when a module is uninstalled"""
  57. return {
  58. 'author_ids': [6, 0, []],
  59. 'module_type_id': False,
  60. 'python_code_qty': False,
  61. 'xml_code_qty': 0,
  62. 'js_code_qty': 0,
  63. 'css_code_qty': 0,
  64. }
  65. # Overload Section
  66. @api.model
  67. def update_list(self):
  68. res = super(IrModuleModule, self).update_list()
  69. if self.env.context.get('analyze_installed_modules', False):
  70. self.search([('state', '=', 'installed')]).button_analyze_code()
  71. return res
  72. @api.multi
  73. def write(self, vals):
  74. res = super(IrModuleModule, self).write(vals)
  75. if vals.get('state', False) == 'installed':
  76. self.button_analyze_code()
  77. elif vals.get('state', False) == 'uninstalled'\
  78. and 'module_analysis' not in [x.name for x in self]:
  79. self.write(self._get_clean_analyse_values())
  80. return res
  81. # Public Section
  82. @api.multi
  83. def button_analyze_code(self):
  84. IrModuleAuthor = self.env['ir.module.author']
  85. IrModuleTypeRule = self.env['ir.module.type.rule']
  86. rules = IrModuleTypeRule.search([])
  87. exclude_directories = ['description', 'lib', 'tests']
  88. exclude_files = ['__openerp__.py', '__manifest__.py']
  89. for module in self:
  90. _logger.info("Analyzing Code for module %s" % (module.name))
  91. # Update Authors, based on manifest key
  92. authors = []
  93. if module.author and module.author[0] == '[':
  94. author_txt_list = safe_eval(module.author)
  95. else:
  96. author_txt_list = module.author.split(',')
  97. author_txt_list = [x.strip() for x in author_txt_list]
  98. author_txt_list = [x for x in author_txt_list if x]
  99. for author_txt in author_txt_list:
  100. authors.append(IrModuleAuthor._get_or_create(author_txt))
  101. author_ids = [x.id for x in authors]
  102. module.author_ids = author_ids
  103. # Update Module Type, based on rules
  104. module_type_id = rules._get_module_type_id_from_module(module)
  105. module.module_type_id = module_type_id
  106. # Get Path of module folder and parse the code
  107. module_path = get_module_path(module.name)
  108. # Get Files
  109. analysed_datas = self._get_analyse_data_dict()
  110. file_extensions = analysed_datas.keys()
  111. file_list = self._get_files_to_analyse(
  112. module_path, file_extensions, exclude_directories,
  113. exclude_files)
  114. for file_path, file_ext in file_list:
  115. file_res = pygount.source_analysis(file_path, '')
  116. for k, v in analysed_datas.get(file_ext).items():
  117. v['value'] += getattr(file_res, k)
  118. # Update the module with the datas
  119. values = {}
  120. for file_ext, analyses in analysed_datas.items():
  121. for k, v in analyses.items():
  122. values[v['field']] = v['value']
  123. module.write(values)
  124. # Custom Section
  125. @api.model
  126. def _get_files_to_analyse(
  127. self, path, file_extensions, exclude_directories, exclude_files):
  128. res = []
  129. for root, dirs, files in os.walk(path, followlinks=True):
  130. if set(Path(root).parts) & set(exclude_directories):
  131. continue
  132. for name in files:
  133. if name in exclude_files:
  134. continue
  135. filename, file_extension = os.path.splitext(name)
  136. if file_extension in file_extensions:
  137. res.append((os.path.join(root, name), file_extension))
  138. return res
  139. @api.model
  140. def _get_analyse_data_dict(self):
  141. res_dict = self._get_analyse_settings().copy()
  142. for file_ext, analyse_dict in res_dict.items():
  143. for analyse_type, v in analyse_dict.items():
  144. analyse_dict[analyse_type] = {
  145. 'field': v,
  146. 'value': 0
  147. }
  148. return res_dict