''' @author: olivier.massot ''' import pickle import re from _regex_core import MULTILINE from path import Path # TODO: Gérer les références circulaires here = Path(__file__).parent.abspath() 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, obj, warning=""): self.line = line self.objname = objname self.quote = quote self.obj = obj self.warning = warning class AccessObject(): type_ = "" _valid_file_exts = (".bas") _order = 0 def __init__(self, name_): self.name_ = name_ self.functions = [] self.sourcefile = "" self._sourcecode = "" self.mentions = [] self.deps = [] self.refs = [] def __repr__(self): return "<{}: {}>".format(self.type_, self.name_) @classmethod def from_file(cls, file): file = Path(file) if file.ext.lower() 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") _order = 10 class QueryObject(AccessObject): type_ = "Query" _order = 30 class FormObject(AccessObject): type_ = "Form" _order = 40 class ReportObject(AccessObject): type_ = "Report" _order = 50 class MacroObject(AccessObject): type_ = "Macro" _order = 60 class ModuleObject(AccessObject): type_ = "Module" _order = 70 @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") _order = 20 class Analyse(): objects = [] index = {} @classmethod def report(cls, current, total, msg=""): pass @classmethod def ended(cls): pass @classmethod def dump_to(cls, filepath): with open(filepath, 'wb') as f: pickle.dump(cls.objects, f) @classmethod def load_from(cls, filepath): cls.objects = [] with open(filepath, 'rb') as f: cls.objects = pickle.load(f) cls.update_index() @classmethod def update_index(cls): cls.index = {} for obj in cls.objects: if not obj.name_ in cls.index: cls.index[obj.name_] = [] cls.index[obj.name_].append(obj) if type(obj) is ModuleObject: for fname in obj.functions: if not fname in cls.index: cls.index[fname] = [] cls.index[fname].append(obj) @classmethod def load_objects(cls, source_dir): source_dir = Path(source_dir) cls.objects = [] cls.index = {} sourcemap = { "tables": TableObject, "relations": RelationObject, "queries": QueryObject, "forms": FormObject, "reports": ReportObject, "scripts": MacroObject, "modules": ModuleObject, } for dirname, accobj in sourcemap.items(): for file in Path(source_dir / dirname).files(): try: obj = accobj.from_file(file) cls.objects.append(obj) except InvalidFileExt: print("Ignored unrecognized file: {}".format(file)) cls.objects.sort(key=lambda x: (x._order, x.name_)) cls.update_index() @classmethod def parse_source(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 and type(object) is not ModuleObject] + list(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) # Indexe la position des lignes line_ends = [m.end() for m in re.finditer('.*\n', subject.sourcecode)] warning = "" 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) if len(cls.index[objname]) == 1: obj = cls.index[objname][0] else: # plusieurs objets portent le même nom warning = "Plusieurs objets portant ce nom ont été trouvés, vérifiez qu'il s'agit bien de l'objet ci-contre." if objname == subject.name_: # si l'objet mentionné porte le même nom que le sujet, on part du principe qu'il se mentione lui-même obj = subject else: obj = cls.index[objname][0] subject.mentions.append(Mention(line, objname, quote, obj, warning)) @classmethod def parse_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. for index, subject in enumerate(cls.objects): cls.report(index, len(cls.objects), "* {}: {}".format(subject.type_, subject.name_)) cls.parse_source(subject) @classmethod def build_trees(cls): total = len(cls.objects) for index, subject in enumerate(cls.objects): cls.report(index, total * 2) subject.deps = [] for mention in subject.mentions: if not mention.obj in subject.deps and not mention.obj is subject: subject.deps.append(mention.obj) for index, subject in enumerate(cls.objects): cls.report(total + index, total * 2) subject.refs = [] for obj in cls.objects: if obj is subject: continue if subject in obj.deps: subject.refs.append(obj) @classmethod def run(cls, source_dir): # Liste les objets à partir de l'arborescence du repertoire des sources cls.report(0, 100, "Chargement des données") cls.load_objects(source_dir) cls.report(0, 100, "> {} objets trouvés".format(len(cls.objects))) cls.report(0, 100, "Analyse du code source".format(len(cls.objects))) cls.parse_all() cls.report(0, 100, "Construction de l'arbre des dépendances".format(len(cls.objects))) cls.build_trees() cls.report(100, 100, "Analyse terminée") cls.ended() return cls.objects @classmethod def duplicates(cls): return {k: v for k, v in cls.index.items() if len(v) > 1} if __name__ == "__main__": source_dir = here / r"test\source" resultfile = here / r"test\analyse.txt" resultfile.remove_p() def print_(i, total, msg=""): if msg: print("({}/{}) {}".format(i, total, msg)) Analyse.report = print_ Analyse.run(source_dir) with open(resultfile, "w+", encoding='utf-8') as f: for obj in Analyse.objects: msg = "# '{}' [{}]".format(obj.name_, obj.type_) if obj.deps: msg += "\n\tMentionne: {}".format(", ".join(["'{}' [{}]".format(dep.name_, dep.type_) for dep in obj.deps])) else: msg += "\n\t (ne mentionne aucun autre objet)" if obj.refs: msg += "\n\tEst mentionné par: {}".format(", ".join(["'{}' [{}]".format(ref.name_, ref.type_) for ref in obj.refs])) else: msg += "\n\t (n'est mentionné nul part ailleurs)" if obj.mentions: msg += "\n\t Détail:" for mention in obj.mentions: msg += "\n\t\t'{}'\t\tLine: {}\t>>\t{}".format(mention.objname, mention.line, mention.quote) msg += "\n" f.write(msg) print("# Terminé")