core.py 6.4 KB

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