''' @author: olivier.massot ''' import re from path import Path objects = [] # TODO: gérer les cas où des objets de types différents portent le même nom. Ex: une table et un formulaire... # TODO: ignorer les commentaires dans le modules # TODO: ignorer les labels dans les formulaires et états # NB: une requete et une table ne peuvent pas porter le même nom. 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") 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'enté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_ 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) @staticmethod def containsRefsTo(source, target): if type(target) is ModuleObject: # L'objet peut contenir des references aux fonctions de l'objet module for fname in target.functions: rx = re.compile(r"(?:^|\W)({})(?:$|\W)".format(fname)) if rx.search(source.sourcecode): return True else: rx = re.compile(r"(?:^|\W)({})(?:$|\W)".format(target.nom)) return rx.search(source.sourcecode) @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 Analyse.containsRefsTo(subject, 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) # # for fname in candidate.functions: # if Analyse.lookFor(fname, source): # if mode in (DEPS_AND_REFS, DEPS_ONLY): # subject.add_dep(candidate) # if mode in (DEPS_AND_REFS, REFS_ONLY): # candidate.add_ref(subject) # break 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() datafile = here / "access_data.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: f.write("* {} - {}{}\n\tdeps > {}\n".format(o.type_, o.nom, "\n\tfunctions > ".format(", ".join(o.functions)) if o.functions else "", o.deps)) print("# Terminé")