core.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. '''
  2. @author: olivier.massot
  3. '''
  4. import re
  5. from _regex_core import MULTILINE
  6. from path import Path
  7. # TODO: gérer les cas où des objets de types différents portent le même nom. Ex: une table et un formulaire... Note: une requete et une table ne peuvent pas porter le même nom.
  8. # TODO: ignorer les commentaires dans les modules
  9. # TODO: ignorer les labels dans les formulaires et états
  10. # TODO: Gérer les références circulaires
  11. # TODO: Stocker un aperçu du contexte de la ou des références dans le code source, pour contrôle ultérieur
  12. # TODO: Permettre de supprimer / ajouter des réferences
  13. # TODO: Verifier que la recherche puisse être Case sensitive?
  14. def recurse(acc_obj):
  15. deptree = []
  16. for dep in acc_obj.deps:
  17. deptree.append(dep)
  18. if dep.deps:
  19. deptree += recurse(dep)
  20. return deptree
  21. class InvalidFileExt(IOError):
  22. pass
  23. class AccessObject():
  24. type_ = "<unknown>"
  25. _valid_file_exts = (".bas")
  26. def __init__(self, name_):
  27. self.name_ = name_
  28. self.functions = []
  29. self.sourcefile = ""
  30. self.deps = {}
  31. self._sourcecode = ""
  32. def __repr__(self):
  33. return "<{}: {}>".format(self.type_, self.name_)
  34. @classmethod
  35. def from_file(cls, file):
  36. file = Path(file)
  37. if file.ext not in cls._valid_file_exts:
  38. raise InvalidFileExt("Format de fichier d'entrée non valide ({})".format(file.name))
  39. obj = cls(AccessObject.path_to_name(file))
  40. obj.sourcefile = file
  41. obj._sourcecode = file.text()
  42. return obj
  43. @property
  44. def sourcecode(self):
  45. if not self._sourcecode:
  46. self._sourcecode = self.sourcefile.text()
  47. return self._sourcecode
  48. SUBSTR = {92: "\\", 47: "/", 58: ":", 42: "*", 63:"?", 34:"\"", 60:"<", 62:">", 124:"|" }
  49. @staticmethod
  50. def path_to_name(path):
  51. name_ = path.name.stripext()
  52. for ascii_code, char in AccessObject.SUBSTR.items():
  53. name_ = name_.replace("[{}]".format(ascii_code), char)
  54. return name_
  55. class TableObject(AccessObject):
  56. type_ = "Table"
  57. _valid_file_exts = (".xml", ".lnkd")
  58. class QueryObject(AccessObject):
  59. type_ = "Query"
  60. class FormObject(AccessObject):
  61. type_ = "Form"
  62. class ReportObject(AccessObject):
  63. type_ = "Report"
  64. class MacroObject(AccessObject):
  65. type_ = "Macro"
  66. class ModuleObject(AccessObject):
  67. type_ = "Module"
  68. @classmethod
  69. def from_file(cls, file):
  70. obj = super(ModuleObject, cls).from_file(file)
  71. rx = re.compile(r"Sub|Function ([^(]+)\(")
  72. obj.functions = [fname for fname in rx.findall(file.text()) if fname]
  73. return obj
  74. class RelationObject(AccessObject):
  75. type_ = "Relation"
  76. _valid_file_exts = (".txt")
  77. class Mention():
  78. def __init__(self, line, objname, quote):
  79. self.line = line
  80. self.objname = objname
  81. self.quote = quote
  82. class Analyse():
  83. objects = []
  84. duplicated_names = []
  85. @classmethod
  86. def report(cls, current, total, msg=""):
  87. pass
  88. @classmethod
  89. def ended(cls):
  90. pass
  91. @classmethod
  92. def load_objects(cls, source_dir):
  93. source_dir = Path(source_dir)
  94. cls.objects = []
  95. cls.duplicated_names = []
  96. sourcemap = {
  97. "forms": FormObject,
  98. "reports": ReportObject,
  99. "relations": RelationObject,
  100. "scripts": MacroObject,
  101. "queries": QueryObject,
  102. "tables": TableObject,
  103. "modules": ModuleObject,
  104. }
  105. for dirname, accobj in sourcemap.items():
  106. for file in Path(source_dir / dirname).files():
  107. try:
  108. obj = accobj.from_file(file)
  109. if obj.name_ in [other.name_ for other in cls.objects] and not obj.name_ in cls.duplicated_names:
  110. cls.duplicated_names.append(obj.name_)
  111. cls.objects.append(obj)
  112. except InvalidFileExt:
  113. print("Ignored unrecognized file: {}".format(file))
  114. @classmethod
  115. def find_deps(cls, subject):
  116. for candidate in cls.objects:
  117. if candidate is subject:
  118. continue
  119. mentions = []
  120. if type(candidate) is ModuleObject: # L'objet peut contenir des references aux fonctions de l'objet module
  121. for fname in candidate.functions:
  122. rx = re.compile(r"((?:.*\n)*)(.*(?:^|\W)({})(?:$|\W).*)".format(fname), MULTILINE)
  123. for match in rx.finditer(subject.sourcecode):
  124. line = len(match.group(1).split("\n")) + 2 if match.group(1) is not None else 1
  125. quote = match.group(2)
  126. objname = match.group(3)
  127. mentions.append(Mention(line, objname, quote))
  128. rx = re.compile("""((?:.*\n)*)(.*(?:^|\s|\[|\]|&|\(|\)|\.|!|"|')({})(?:^|\s|\[|\]|&|\(|\)|\.|!|"|').*)""".format(candidate.name_), MULTILINE)
  129. for match in rx.finditer(subject.sourcecode):
  130. line = len(match.group(1).split("\n")) if match.group(1) is not None else 1
  131. quote = match.group(2)
  132. objname = match.group(3)
  133. mentions.append(Mention(line, objname, quote))
  134. if mentions:
  135. subject.deps[candidate] = mentions
  136. @classmethod
  137. def analyse_all(cls):
  138. # Mise à jour des dépendances:
  139. # # parcourt les objets, et recherche dans le code source de chacun des mentions du nom des autres objets.
  140. total = len(cls.objects)
  141. for index, subject in enumerate(cls.objects):
  142. cls.report(index, total, "* {}: {}".format(subject.type_, subject.name_))
  143. cls.find_deps(subject)
  144. @classmethod
  145. def run(cls, source_dir):
  146. # Liste les objets à partir de l'arborescence du repertoire des sources
  147. cls.report(0, 100, "Analyse du répertoire")
  148. cls.load_objects(source_dir)
  149. cls.report(0, 100, "> {} objets trouvés".format(len(cls.objects)))
  150. cls.analyse_all()
  151. cls.report(100, 100, "Analyse terminée")
  152. cls.ended()
  153. return cls.objects
  154. if __name__ == "__main__":
  155. here = Path(__file__).parent.abspath()
  156. source_dir = here / r"test\source"
  157. resultfile = here / r"test\analyse.txt"
  158. resultfile.remove_p()
  159. def print_(i, total, msg):
  160. print("({}/{}) {}".format(i, total, msg))
  161. Analyse.report = print_
  162. Analyse.run(source_dir)
  163. with open(resultfile, "w+") as f:
  164. for obj in Analyse.objects:
  165. msg = "# L'objet [{}] '{}' mentionne dans son code-source:".format(obj.type_, obj.name_)
  166. for dep, mentions in obj.deps.items():
  167. msg += "\n\t* [{}] '{}'".format(dep.type_, dep.name_)
  168. for mention in mentions:
  169. msg += "\n\t\tLine: {} >> {}".format(mention.line, mention.quote)
  170. if not obj.deps:
  171. msg += "\n\t (pas de dépendances)"
  172. msg += "\n"
  173. f.write(msg)
  174. print("# Terminé")