core.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  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 Mention():
  24. def __init__(self, line, objname, quote):
  25. self.line = line
  26. self.objname = objname
  27. self.quote = quote
  28. self.obj = None
  29. class AccessObject():
  30. type_ = "<unknown>"
  31. _valid_file_exts = (".bas")
  32. def __init__(self, name_):
  33. self.name_ = name_
  34. self.functions = []
  35. self.sourcefile = ""
  36. self._sourcecode = ""
  37. self.mentions = []
  38. self.deps = {}
  39. def __repr__(self):
  40. return "<{}: {}>".format(self.type_, self.name_)
  41. @classmethod
  42. def from_file(cls, file):
  43. file = Path(file)
  44. if file.ext not in cls._valid_file_exts:
  45. raise InvalidFileExt("Format de fichier d'entrée non valide ({})".format(file.name))
  46. obj = cls(AccessObject.path_to_name(file))
  47. obj.sourcefile = file
  48. obj._sourcecode = file.text()
  49. obj._sourcecode.replace("\r\n", "\n")
  50. return obj
  51. @property
  52. def sourcecode(self):
  53. if not self._sourcecode:
  54. self._sourcecode = self.sourcefile.text()
  55. return self._sourcecode
  56. SUBSTR = {92: "\\", 47: "/", 58: ":", 42: "*", 63:"?", 34:"\"", 60:"<", 62:">", 124:"|" }
  57. @staticmethod
  58. def path_to_name(path):
  59. name_ = path.name.stripext()
  60. for ascii_code, char in AccessObject.SUBSTR.items():
  61. name_ = name_.replace("[{}]".format(ascii_code), char)
  62. return name_
  63. class TableObject(AccessObject):
  64. type_ = "Table"
  65. _valid_file_exts = (".xml", ".lnkd")
  66. class QueryObject(AccessObject):
  67. type_ = "Query"
  68. class FormObject(AccessObject):
  69. type_ = "Form"
  70. class ReportObject(AccessObject):
  71. type_ = "Report"
  72. class MacroObject(AccessObject):
  73. type_ = "Macro"
  74. class ModuleObject(AccessObject):
  75. type_ = "Module"
  76. @classmethod
  77. def from_file(cls, file):
  78. obj = super(ModuleObject, cls).from_file(file)
  79. rx = re.compile(r"Sub|Function ([^(]+)\(")
  80. obj.functions = [fname for fname in rx.findall(file.text()) if fname]
  81. return obj
  82. class RelationObject(AccessObject):
  83. type_ = "Relation"
  84. _valid_file_exts = (".txt")
  85. class Analyse():
  86. objects = []
  87. duplicated_names = []
  88. @classmethod
  89. def report(cls, current, total, msg=""):
  90. pass
  91. @classmethod
  92. def ended(cls):
  93. pass
  94. @classmethod
  95. def load_objects(cls, source_dir):
  96. source_dir = Path(source_dir)
  97. cls.objects = []
  98. cls.duplicated_names = []
  99. sourcemap = {
  100. "forms": FormObject,
  101. "reports": ReportObject,
  102. "relations": RelationObject,
  103. "scripts": MacroObject,
  104. "queries": QueryObject,
  105. "tables": TableObject,
  106. "modules": ModuleObject,
  107. }
  108. for dirname, accobj in sourcemap.items():
  109. for file in Path(source_dir / dirname).files():
  110. try:
  111. obj = accobj.from_file(file)
  112. if obj.name_ in [other.name_ for other in cls.objects] and not obj.name_ in cls.duplicated_names:
  113. cls.duplicated_names.append(obj.name_)
  114. cls.objects.append(obj)
  115. except InvalidFileExt:
  116. print("Ignored unrecognized file: {}".format(file))
  117. @classmethod
  118. def find_deps(cls, subject):
  119. # On cherche le nom de chaque autre objet, ainsi que le nom des fonctions issues des modules
  120. look_for = [obj.name_ for obj in cls.objects if obj is not subject] + sum([obj.functions for obj in cls.objects if obj is not subject])
  121. names = "|".join(list(set(look_for)))
  122. rx = re.compile("""(.*(?:^|\t| |\[|\]|&|\(|\)|\.|!|"|')({})(?:$|\t| |\[|\]|&|\(|\)|\.|!|"|').*)""".format(names), MULTILINE)
  123. mentions = []
  124. # Indexe la position des lignes
  125. line_ends = [m.end() for m in re.finditer('.*\n', subject.sourcecode)]
  126. for match in rx.finditer(subject.sourcecode):
  127. line = next(i for i in range(len(line_ends)) if line_ends[i] > match.start(1)) + 1
  128. quote = match.group(1).strip()
  129. objname = match.group(2)
  130. mentions.append(Mention(line, objname, quote))
  131. subject.mentions = mentions
  132. # if mentions:
  133. # subject.deps[candidate] = mentions
  134. @classmethod
  135. def analyse_all(cls):
  136. # Mise à jour des dépendances:
  137. # # parcourt les objets, et recherche dans le code source de chacun des mentions du nom des autres objets.
  138. total = len(cls.objects)
  139. for index, subject in enumerate(cls.objects):
  140. cls.report(index, total, "* {}: {}".format(subject.type_, subject.name_))
  141. cls.find_deps(subject)
  142. @classmethod
  143. def run(cls, source_dir):
  144. # Liste les objets à partir de l'arborescence du repertoire des sources
  145. cls.report(0, 100, "Analyse du répertoire")
  146. cls.load_objects(source_dir)
  147. cls.report(0, 100, "> {} objets trouvés".format(len(cls.objects)))
  148. cls.analyse_all()
  149. cls.report(100, 100, "Analyse terminée")
  150. cls.ended()
  151. return cls.objects
  152. if __name__ == "__main__":
  153. here = Path(__file__).parent.abspath()
  154. source_dir = here / r"test\source"
  155. resultfile = here / r"test\analyse.txt"
  156. resultfile.remove_p()
  157. def print_(i, total, msg):
  158. print("({}/{}) {}".format(i, total, msg))
  159. Analyse.report = print_
  160. Analyse.run(source_dir)
  161. with open(resultfile, "w+") as f:
  162. for obj in Analyse.objects:
  163. msg = "# L'objet [{}] '{}' mentionne dans son code-source:".format(obj.type_, obj.name_)
  164. for mention in obj.mentions:
  165. msg += "\n\t\t'{}'\t\tLine: {}\t>>\t{}".format(mention.objname, mention.line, mention.quote)
  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é")