''' @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 Mention(): def __init__(self, line, objname, quote): self.line = line self.objname = objname self.quote = quote self.obj = None class AccessObject(): type_ = "" _valid_file_exts = (".bas") def __init__(self, name_): self.name_ = name_ self.functions = [] self.sourcefile = "" self._sourcecode = "" self.mentions = [] self.deps = {} 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() obj._sourcecode.replace("\r\n", "\n") 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 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): # On cherche le nom de chaque autre objet, ainsi que le nom des fonctions issues des modules 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]) names = "|".join(list(set(look_for))) rx = re.compile("""(.*(?:^|\t| |\[|\]|&|\(|\)|\.|!|"|')({})(?:$|\t| |\[|\]|&|\(|\)|\.|!|"|').*)""".format(names), MULTILINE) mentions = [] # Indexe la position des lignes line_ends = [m.end() for m in re.finditer('.*\n', subject.sourcecode)] for match in rx.finditer(subject.sourcecode): line = next(i for i in range(len(line_ends)) if line_ends[i] > match.start(1)) + 1 quote = match.group(1).strip() objname = match.group(2) mentions.append(Mention(line, objname, quote)) subject.mentions = mentions # 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 mention in obj.mentions: msg += "\n\t\t'{}'\t\tLine: {}\t>>\t{}".format(mention.objname, mention.line, mention.quote) # 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é")