''' @author: olivier.massot ''' import re from path import Path # 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. # TODO: ignorer les commentaires dans le modules # TODO: ignorer les labels dans les formulaires et états # TODO: Gérer les références circulaires # TODO: Stocker un aperçu du contexte de la ou des références dans le code source, pour contrôle ultérieur # TODO: Permettre de supprimer / ajouter des réferences # TODO: Verifier que la recherche puisse être Case sensitive? def recurse(acc_obj): deptree = [] for dep in acc_obj.deps: deptree.append(dep) if dep.deps: deptree += recurse(dep) return deptree class InvalidFileExt(IOError): pass class AccessObject(): type_ = "" _valid_file_exts = (".bas") _re_w_sep = """^|\s|\[|\]|&|\(|\)|\.|!|"|'""" def __init__(self, nom): self.nom = nom self.functions = [] self.sourcefile = "" self.deps = [] self.refs = [] self._sourcecode = "" def __repr__(self): return "<{}: {}>".format(self.type_, self.nom) @classmethod def from_file(cls, file): file = Path(file) if file.ext not in cls._valid_file_exts: raise InvalidFileExt("Format de fichier d'entrée non valide ({})".format(file.name)) obj = cls(AccessObject.path_to_name(file)) obj.sourcefile = file obj._sourcecode = file.text() return obj @property def sourcecode(self): if not self._sourcecode: self._sourcecode = self.sourcefile.text() return self._sourcecode def add_dep(self, obj): if not obj in self.deps: self.deps.append(obj) def add_ref(self, obj): if not obj in self.refs: self.refs.append(obj) SUBSTR = {92: "\\", 47: "/", 58: ":", 42: "*", 63:"?", 34:"\"", 60:"<", 62:">", 124:"|" } @staticmethod def path_to_name(path): name_ = path.name.stripext() for ascii_code, char in AccessObject.SUBSTR.items(): name_ = name_.replace("[{}]".format(ascii_code), char) return name_ def search_me_regex(self): return re.compile(r"""({sep}){nom}({sep})""".format(sep=self._re_w_sep, nom=self.nom)) def containsRefsTo(self, access_object): if access_object is self: return False if type(access_object) is ModuleObject: # L'objet peut contenir des references aux fonctions de l'objet module for fname in access_object.functions: rx = re.compile(r"(^|\W)({})($|\W)".format(fname)) if rx.search(self.sourcecode): return True rx = access_object.search_me_regex() return rx.search(self.sourcecode) class TableObject(AccessObject): type_ = "Table" _valid_file_exts = (".xml", ".lnkd") class QueryObject(AccessObject): type_ = "Query" class FormObject(AccessObject): type_ = "Form" class ReportObject(AccessObject): type_ = "Report" class MacroObject(AccessObject): type_ = "Macro" class ModuleObject(AccessObject): type_ = "Module" @classmethod def from_file(cls, file): obj = super(ModuleObject, cls).from_file(file) rx = re.compile(r"Sub|Function ([^(]+)\(") obj.functions = [fname for fname in rx.findall(file.text()) if fname] return obj class RelationObject(AccessObject): type_ = "Relation" _valid_file_exts = (".txt") REFS_ONLY = 1 DEPS_ONLY = 2 DEPS_AND_REFS = 3 class Analyse(): objects = [] duplicated_names = [] @staticmethod def report(current, total, msg=""): pass @staticmethod def ended(): pass @classmethod def register(cls, obj): if obj.nom in [other.nom for other in cls.objects] and not obj.nom in cls.duplicated_names: cls.duplicated_names.append(obj.nom) cls.objects.append(obj) @classmethod def run(cls, source_dir, mode=DEPS_AND_REFS): source_dir = Path(source_dir) cls.objects = [] cls.duplicated_names = [] # Liste les objets à partir de l'arborescence du repertoire des sources cls.report(0, 100, "Analyse du répertoire") sourcemap = { "forms": FormObject, "reports": ReportObject, "relations": RelationObject, "scripts": MacroObject, "queries": QueryObject, "tables": TableObject, "modules": ModuleObject, } for dirname, accobj in sourcemap.items(): for file in Path(source_dir / dirname).files(): try: obj = accobj.from_file(file) cls.register(obj) except InvalidFileExt: print("Ignored unrecognized file: {}".format(file)) total = len(cls.objects) cls.report(0, total, "> {} objets trouvés".format(total)) # met à jour les dependances en listant les noms d'objets mentionnés dans le fichier subject for index, subject in enumerate(cls.objects): cls.report(index, total, "* {}: {}".format(subject.type_, subject.nom)) for candidate in cls.objects[:index] + cls.objects[index + 1:]: if subject.containsRefsTo(candidate): if mode in (DEPS_AND_REFS, DEPS_ONLY): subject.add_dep(candidate) if mode in (DEPS_AND_REFS, REFS_ONLY): candidate.add_ref(subject) cls.report(total, total, "Analyse terminée") cls.ended() return cls.objects if __name__ == "__main__": source_dir = Path(r"c:\dev\access\Analytique\source") here = Path(__file__).parent.abspath() source_dir = here / "source" datafile = here / "access_analyser.txt" datafile.remove_p() def main_report(*_, msg=""): print(msg) Analyse.report = main_report Analyse.run(source_dir) with open("data.txt", "w+") as f: for o in Analyse.objects: msg = "# {}: '{}'".format(o.type_, o.nom) msg += "\n\t Utilise: {}".format(", ".join(["'{}'".format(d) for d in o.deps])) msg += "\n\t Est utilisé par: {}".format(", ".join(["'{}'".format(d) for d in o.refs])) msg += "\n" f.write(msg) print("# Terminé")