ScoDoc/refactor.py

192 lines
5.5 KiB
Python
Executable File

#!/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 module.method 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
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