core.py 6.5 KB

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