''' @author: olivier.massot ''' import re from _regex_core import MULTILINE 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 les 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") def __init__(self, name_): self.name_ = name_ self.functions = [] self.sourcefile = "" self.deps = {} self._sourcecode = "" def __repr__(self): return "<{}: {}>".format(self.type_, self.name_) @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 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") class Mention(): def __init__(self, line, objname, quote): self.line = line self.objname = objname self.quote = quote class Analyse(): objects = [] duplicated_names = [] @classmethod def report(cls, current, total, msg=""): pass @classmethod def ended(cls): pass @classmethod def load_objects(cls, source_dir): source_dir = Path(source_dir) cls.objects = [] cls.duplicated_names = [] 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) if obj.name_ in [other.name_ for other in cls.objects] and not obj.name_ in cls.duplicated_names: cls.duplicated_names.append(obj.name_) cls.objects.append(obj) except InvalidFileExt: print("Ignored unrecognized file: {}".format(file)) @classmethod def find_deps(cls, subject): for candidate in cls.objects: if candidate is subject: continue mentions = [] if type(candidate) is ModuleObject: # L'objet peut contenir des references aux fonctions de l'objet module for fname in candidate.functions: rx = re.compile(r"((?:.*\n)*)(.*(?:^|\W)({})(?:$|\W).*)".format(fname), MULTILINE) for match in rx.finditer(subject.sourcecode): line = len(match.group(1).split("\n")) + 2 if match.group(1) is not None else 1 quote = match.group(2) objname = match.group(3) mentions.append(Mention(line, objname, quote)) rx = re.compile("""((?:.*\n)*)(.*(?:^|\s|\[|\]|&|\(|\)|\.|!|"|')({})(?:^|\s|\[|\]|&|\(|\)|\.|!|"|').*)""".format(candidate.name_), MULTILINE) for match in rx.finditer(subject.sourcecode): line = len(match.group(1).split("\n")) if match.group(1) is not None else 1 quote = match.group(2) objname = match.group(3) mentions.append(Mention(line, objname, quote)) if mentions: subject.deps[candidate] = mentions @classmethod def analyse_all(cls): # Mise à jour des dépendances: # # parcourt les objets, et recherche dans le code source de chacun des mentions du nom des autres objets. total = len(cls.objects) for index, subject in enumerate(cls.objects): cls.report(index, total, "* {}: {}".format(subject.type_, subject.name_)) cls.find_deps(subject) @classmethod def run(cls, source_dir): # Liste les objets à partir de l'arborescence du repertoire des sources cls.report(0, 100, "Analyse du répertoire") cls.load_objects(source_dir) cls.report(0, 100, "> {} objets trouvés".format(len(cls.objects))) cls.analyse_all() cls.report(100, 100, "Analyse terminée") cls.ended() return cls.objects if __name__ == "__main__": here = Path(__file__).parent.abspath() source_dir = here / r"test\source" resultfile = here / r"test\analyse.txt" resultfile.remove_p() def print_(i, total, msg): print("({}/{}) {}".format(i, total, msg)) Analyse.report = print_ Analyse.run(source_dir) with open(resultfile, "w+") as f: for obj in Analyse.objects: msg = "# L'objet [{}] '{}' mentionne dans son code-source:".format(obj.type_, obj.name_) for dep, mentions in obj.deps.items(): msg += "\n\t* [{}] '{}'".format(dep.type_, dep.name_) for mention in mentions: msg += "\n\t\tLine: {} >> {}".format(mention.line, mention.quote) if not obj.deps: msg += "\n\t (pas de dépendances)" msg += "\n" f.write(msg) print("# Terminé")