''' @author: olivier.massot ''' import pickle import re from _regex_core import MULTILINE, IGNORECASE, VERBOSE from path import Path 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 Warn(): text = "" class WarnDuplicate(Warning): text = "Plusieurs objets portant ce nom ont été trouvés, vérifiez qu'il s'agit bien de l'objet ci-contre" class WarnComment(Warning): text = "La mention semble se trouver dans un commentaire" class WarnCaption(Warning): text = "Cette mention semble être dans un label" class WarnRefItself(Warning): text = "L'objet semble se mentionner lui-même" class Mention(): def __init__(self, line, objname, quote, obj=None, warnings=[]): self.line = line self.objname = objname self.quote = quote self._obj_index = None self.warnings = warnings self.obj = obj @property def obj(self): return Analyse.objects[self._obj_index] @obj.setter def obj(self, value): self._obj_index = Analyse.objects.index(value) 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_) def rxname(self): return self.name_.lower().replace("?", "\?").replace(".", "\.").replace("") @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 @staticmethod def path_to_name(path): name_ = path.name.stripext() for ascii_code, char in {92: "\\", 47: "/", 58: ":", 42: "*", 63:"?", 34:"\"", 60:"<", 62:">", 124:"|" }.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 = [] glossary = {} @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.build_glossary() @classmethod def build_glossary(cls): cls.glossary.clear() for obj in cls.objects: if not obj.name_.lower() in cls.glossary: cls.glossary[obj.name_.lower()] = [] cls.glossary[obj.name_.lower()].append(obj) if type(obj) is ModuleObject: for fname in obj.functions: if not fname.lower() in cls.glossary: cls.glossary[fname.lower()] = [] cls.glossary[fname.lower()].append(obj) @classmethod def load_objects(cls, source_dir): source_dir = Path(source_dir) cls.objects = [] 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.build_glossary() @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 = {re.escape(obj.name_) for obj in cls.objects if obj is not subject and type(object) is not ModuleObject} | set(sum([obj.functions for obj in cls.objects if obj is not subject], [])) names = "|".join(list(look_for)) seps = "|".join(("\t", " ", "\[", "\]", "&", "\(", "\)", "\.", "!", "'", "\"", r"\\015", r"\\012")) # Note: Les separateurs possibles incluent \015 et \012 qui sont les hexadecimaux pour \n et \r rstr = """(?:^|{seps})({names})(?:$|{seps})""".format(seps=seps, names=names) rx = re.compile(rstr, MULTILINE | IGNORECASE) # Indexe la position des lignes line_matches = list(re.finditer('.*\n', subject.sourcecode)) for match in rx.finditer(subject.sourcecode): line_number, line = next((i + 1, line) for i, line in enumerate(line_matches) if line.end() > match.start(1)) quote = line.group(0).replace("\r", "").replace("\n", "").strip() objname = match.group(1) warnings = [] if objname == subject.name_: obj = subject warnings.append(WarnRefItself()) else: obj = cls.glossary[objname.lower()][0] if len(cls.glossary[objname.lower()]) > 1: warnings.append(WarnDuplicate()) if type(subject) is ModuleObject: if re.match(r"^[^\"]*(?:\"(?:[^\"]*?)\")*[^\"]*'.*({})".format(objname), quote): warnings.append(WarnComment()) if type(subject) in (FormObject, ReportObject): if re.match(r"Caption =\".*{}.*\"".format(objname), quote): warnings.append(WarnCaption()) subject.mentions.append(Mention(line_number, objname, quote, obj, warnings)) @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) if mention.warnings: msg += "\n\t\t\t! Avertissements: {}".format("|".join([w.text for w in mention.warnings])) msg += "\n" f.write(msg) print("# Terminé")