| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- '''
- @author: olivier.massot
- '''
- import pickle
- import re
- from _regex_core import MULTILINE
- 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_ = "<unknown>"
- _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
- @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 = []
- 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)]
- 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).replace("\r", "").replace("\n", "").strip()
- objname = match.group(2)
- warnings = []
- if objname == subject.name_:
- obj = subject
- warnings.append(WarnRefItself())
- else:
- obj = cls.index[objname][0]
- if len(cls.index[objname]) > 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, 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é")
|