forked from ScoDoc/DocScoDoc
200 lines
5.6 KiB
Python
200 lines
5.6 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: UTF-8 -*
|
|
|
|
|
|
"""Outil pour migration ScoDoc 7 => ScoDoc 8
|
|
|
|
- Liste des appels de la forme context.xxx
|
|
./refactor.py showcontextcalls app/scodoc/*.p
|
|
|
|
- remplace context.xxx par module.xxx
|
|
|
|
./refactor.py refactor method module app/scodoc/*.py
|
|
|
|
|
|
|
|
Pour chaque module dans views:
|
|
- construire la liste des fonctions définies dans ce module:
|
|
get_module_functions
|
|
|
|
Pour chaque module dans views et dans scodoc:
|
|
- remplacer context.xxx par app.views.M.xxx
|
|
où M est le module de views définissant xxx
|
|
Si xxx n'est pas trouvé, erreur !
|
|
"""
|
|
|
|
|
|
from __future__ import print_function
|
|
import re
|
|
from pprint import pprint as pp
|
|
import os
|
|
import sys
|
|
import types
|
|
import tempfile
|
|
import shutil
|
|
import click
|
|
|
|
# import flask
|
|
|
|
# import app
|
|
# from app import create_app, cli, db
|
|
# from app.auth.models import User, Role, UserRole
|
|
|
|
# from app.views import notes
|
|
|
|
TYPES_TO_SCAN = {
|
|
types.FunctionType,
|
|
# types.ClassType,
|
|
# types.DictionaryType,
|
|
# types.FloatType,
|
|
# types.IntType,
|
|
# types.ListType,
|
|
# types.StringType,
|
|
# types.TupleType,
|
|
}
|
|
|
|
|
|
def get_module_symbols(module):
|
|
"""returns list of symbols (functions and constants) defined in the given module"""
|
|
return [
|
|
f.__name__
|
|
for f in [getattr(module, name) for name in dir(module)]
|
|
if (type(f) in TYPES_TO_SCAN)
|
|
and ((type(f) != types.FunctionType) or (f.__module__ == module.__name__))
|
|
]
|
|
|
|
|
|
# print("\n".join(f.__name__ for f in get_module_functions(notes)))
|
|
|
|
|
|
def scan_views_symbols():
|
|
"""Scan modules in app.views and returns
|
|
{ }
|
|
"""
|
|
import app
|
|
|
|
views_modules = [
|
|
getattr(app.views, mod_name)
|
|
for mod_name in dir(app.views)
|
|
if type(getattr(app.views, mod_name)) == types.ModuleType
|
|
]
|
|
sym2mod = {} # symbole_name : module
|
|
for module in views_modules:
|
|
start = "app.views."
|
|
assert module.__name__.startswith(start)
|
|
module_name = module.__name__[len(start) :]
|
|
symbols = set(get_module_symbols(module))
|
|
print("%d symbols defined in %s" % (len(symbols), module))
|
|
dups = symbols.intersection(sym2mod)
|
|
if len(dups):
|
|
print("duplicated symbols !")
|
|
for dup in dups:
|
|
print("%s:\t%s\t%s" % (dup, sym2mod[dup], module_name))
|
|
|
|
sym2mod.update({s: module_name for s in symbols})
|
|
return sym2mod
|
|
|
|
|
|
def replace_context_calls(sourcefilename, sym2mod):
|
|
undefined_list = [] # noms de fonctions non présents dans les modules "views"
|
|
|
|
def repl(m):
|
|
funcname = m.group(1)
|
|
module = sym2mod.get(funcname, False)
|
|
if module:
|
|
return module + "." + funcname
|
|
else:
|
|
undefined_list.append((sourcefilename, funcname))
|
|
return m.group(0) # leave unchanged
|
|
|
|
print("reading %s" % sourcefilename)
|
|
source = open(sourcefilename).read()
|
|
exp = re.compile(r"context\.([a-zA-Z0-9_]+)")
|
|
source2 = exp.sub(repl, source)
|
|
return source2, undefined_list
|
|
|
|
|
|
# sym2mod = scan_views_symbols()
|
|
|
|
# source2, undefined_list = replace_context_calls("app/scodoc/sco_core.py", sym2mod)
|
|
|
|
|
|
def list_context_calls(sourcefilename):
|
|
"""List of methods called on context in this file"""
|
|
source = open(sourcefilename).read()
|
|
exp = re.compile(r"context\.([a-zA-Z0-9_]+)")
|
|
return sorted(set(exp.findall(source)))
|
|
|
|
|
|
def get_context_calls(src_filenames):
|
|
"""returns { method_name : [ list of module names in which it is called ] }"""
|
|
S = {}
|
|
for sourcefilename in src_filenames:
|
|
l = list_context_calls(sourcefilename)
|
|
module_name = os.path.splitext(os.path.split(sourcefilename)[1])[0]
|
|
for m in l:
|
|
if m in S:
|
|
S[m].append(module_name)
|
|
else:
|
|
S[m] = [module_name]
|
|
return S
|
|
|
|
|
|
@click.group()
|
|
def cli():
|
|
pass
|
|
|
|
|
|
@cli.command()
|
|
@click.argument("src_filenames", nargs=-1)
|
|
def showcontextcalls(src_filenames):
|
|
click.echo("Appels de méthodes sur l'object context")
|
|
S = get_context_calls(src_filenames)
|
|
#
|
|
for method in sorted(S.keys()):
|
|
print(method + ":\t" + ", ".join(S[method]))
|
|
|
|
|
|
@cli.command()
|
|
@click.argument("modulemethod", nargs=1)
|
|
@click.argument("src_filenames", nargs=-1)
|
|
def refactor(modulemethod, src_filenames):
|
|
"""Replace call context.method(...)
|
|
by module.method(context, ...)
|
|
in all given source filenames
|
|
"""
|
|
modulemethod = str(modulemethod) # avoid unicode in Python2
|
|
frags = modulemethod.split(".")
|
|
if len(frags) < 2:
|
|
raise click.BadParameter("must be module.method", param_hint="modulemethod")
|
|
module = ".".join(frags[:-1])
|
|
method = frags[-1]
|
|
backup = tempfile.mkdtemp(dir="/tmp")
|
|
for sourcefilename in src_filenames:
|
|
source_module_name = os.path.splitext(os.path.split(sourcefilename)[1])[0]
|
|
is_local = source_module_name == module
|
|
source = open(sourcefilename).read()
|
|
if not is_local:
|
|
source2 = source.replace(
|
|
"context." + method + "(", module + "." + method + "(context, "
|
|
)
|
|
source2 = source2.replace(
|
|
"context.Notes." + method + "(", module + "." + method + "(context, "
|
|
)
|
|
else:
|
|
# call in the same module:
|
|
source2 = source.replace("context." + method + "(", method + "(context, ")
|
|
if source2 != source:
|
|
print("changed %s" % sourcefilename)
|
|
shutil.move(sourcefilename, backup)
|
|
open(sourcefilename, "w").write(source2)
|
|
print("Done.\noriginal files saved in %s\n" % backup)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
cli(obj={})
|
|
except SystemExit as e:
|
|
if e.code != 0:
|
|
raise
|