Page MenuHomePhabricator
Paste P96554

Codemod for UploadWizard packageFile migration
ActivePublic

Authored by simon04 on Sun, Oct 4, 6:45 AM.
Project Tags
Referenced Files
F106168644: Codemod for UploadWizard packageFile migration
Sun, Oct 4, 6:46 AM
F106168386: Codemod for UploadWizard packageFile migration
Sun, Oct 4, 6:45 AM
Subscribers
None
# Prepare step: in extension.json, rename "scripts" to "packageFiles" for ext.uploadWizard and ext.uploadWizard.page, put resources/ext.uploadWizard.js first in the list, and have that file require() the other files in their old order. I can also write that step into the script if you’d rather.
import json, re, os
os.chdir("/home/simon/src/mediawiki/extensions/UploadWizard")
d = json.load(open("extension.json"))
files = d["ResourceModules"]["ext.uploadWizard"]["packageFiles"][1:]
NS_BASES = [
"resources/uw/uw.base.js",
"resources/ui/uw.ui.base.js",
"resources/controller/uw.controller.base.js",
"resources/deed/uw.deed.base.js",
"resources/deed/dialog/uw.deed.dialog.base.js",
]
JQUERY = [
"resources/jquery.arrowSteps/jquery.arrowSteps.js",
"resources/jquery/jquery.morphCrossfade.js",
]
GLOBAL = (
"resources/mw.UploadWizard.js" # mw.UploadWizard stays global, holding the config
)
def code(s):
return re.sub(r"//[^\n]*", "", re.sub(r"/\*.*?\*/", "", s, flags=re.S))
# class name -> file
owner = {}
for f in files:
if f in NS_BASES or f in JQUERY:
continue
s = open(f).read()
for m in re.finditer(r"^\t?((?:uw|mw)(?:\.\w+)+)\s*=(?!=)", s, re.M):
n = m.group(1)
if (
n.startswith("mw.uploadWizard")
or n == "mw.UploadWizard"
or n.startswith("mw.UploadWizard.")
):
continue
if ".prototype." in n or ".static." in n:
continue
p = n.split(".")
if any(".".join(p[:i]) in owner for i in range(2, len(p))):
continue
owner.setdefault(n, f)
def local(n):
# canvas is a common local variable name
if n == "mw.canvas":
return "canvasUtils"
p = n.split(".")
if p[0] == "mw":
return p[1]
p = p[1:]
if p[0] == "controller":
return p[1] + "Controller"
if p[0] == "ui":
return p[1] + "UI"
if p[0] == "deed":
return p[1] + "Deed"
return p[-1]
LOCAL = {n: local(n) for n in owner}
assert len(set(LOCAL.values())) == len(LOCAL)
names = sorted(owner, key=len, reverse=True)
def pat(n):
return (
r"(?:uw|mw\.uploadWizard)\." + re.escape(n[3:])
if n.startswith("uw.")
else re.escape(n)
)
NAME_RE = re.compile(r"(?<![\w.])(?:" + "|".join(pat(n) for n in names) + r")\b")
def canon(t):
t = re.sub(r"^mw\.uploadWizard\.", "uw.", t)
return t
def rel(frm, to):
r = os.path.relpath(to, os.path.dirname(frm))
return r if r.startswith(".") else "./" + r
def unwrap(s):
m = re.search(
r"^\( function \((?: uw )?\) \{\n(.*?)\n\}\((?: mw\.uploadWizard )?\) \);\n",
s,
re.S | re.M,
)
if not m:
return s
body = re.sub(r"^\t'use strict';\n\n?", "", m.group(1))
body = "\n".join(
l[1:] if l.startswith("\t") else l for l in body.split("\n")
).strip("\n")
return s[: m.start()] + body + "\n" + s[m.end() :]
def insert_requires(s, reqs):
if not reqs:
return s
pos = 0
while True:
m = re.match(r"\s*(/\*(?!\*)(?:.|\n)*?\*/|//[^\n]*)\n", s[pos:])
if not m:
break
pos += m.end()
while s[pos : pos + 1] == "\n":
pos += 1
return s[:pos] + "".join(reqs) + "\n" + s[pos:]
for f in files:
if f in NS_BASES or f in JQUERY:
continue
s = open(f).read()
orig = s
s = unwrap(s)
own = [n for n in names if owner[n] == f]
for n in own:
s, k = re.subn(
r"^" + re.escape(n) + r"(\s*=(?!=))",
"const " + LOCAL[n] + r"\1",
s,
count=1,
flags=re.M,
)
assert k == 1, (f, n)
used = {canon(m.group(0)) for m in NAME_RE.finditer(code(s))}
s = NAME_RE.sub(lambda m: LOCAL[canon(m.group(0))], s)
reqs = [
"const %s = require( '%s' );\n" % (LOCAL[u], rel(f, owner[u]))
for u in sorted((u for u in used if owner[u] != f), key=lambda u: owner[u])
]
s = insert_requires(s, reqs)
if own:
s = s.rstrip("\n") + "\n\nmodule.exports = %s;\n" % LOCAL[own[0]]
for i, l in enumerate(s.split("\n")):
if re.search(
r"\buw\b|mw\.uploadWizard", re.sub(r"require\( '[^']*' \)", "", l)
):
print("LEFTOVER", f, i + 1, l.strip()[:90])
if s != orig:
open(f, "w").write(s)
for f in NS_BASES:
os.remove(f)
# main file
groups = {"controller": {}, "ui": {}, "deed": {}}
top = {}
for n in owner:
p = n.split(".")
if p[0] == "uw" and p[1] in groups:
groups[p[1]][p[2]] = owner[n]
else:
top[p[-1]] = owner[n]
def entries(dct, ind):
return ",\n".join(
"%s%s: require( '%s' )" % (ind, k, rel("resources/x.js", v))
for k, v in sorted(dct.items(), key=lambda kv: kv[0].lower())
)
main = "/*!\n * Entry point of the ext.uploadWizard module. Sets up the jQuery plugins and\n * mw.UploadWizard, and exports the UploadWizard classes, e.g. for tests.\n */\n"
main += "".join(
"require( '%s' );\n" % rel("resources/x.js", f) for f in JQUERY + [GLOBAL]
)
main += "\nmodule.exports = {\n" + entries(top, "\t") + ",\n"
main += (
",\n".join(
"\t%s: {\n%s\n\t}" % (g, entries(groups[g], "\t\t"))
for g in ("controller", "deed", "ui")
)
+ "\n};\n"
)
open("resources/ext.uploadWizard.js", "w").write(main)
s = open("extension.json").read()
for f in NS_BASES:
s, k = re.subn(r'\t+"' + re.escape(f) + r'",\n', "", s)
assert k == 1, f
open("extension.json", "w").write(s)
json.loads(s)
# tests: use the module exports instead of the globals
conv = [n for n in names if n.startswith("mw.")]
TEST_RE = re.compile(
r"(?<!['\w.])(?:mw\.uploadWizard\.|mw\.(?=(?:"
+ "|".join(re.escape(n[3:]) for n in conv)
+ r")\b))"
)
for root, _, fs in os.walk("tests/qunit"):
for fn in fs:
if not fn.endswith(".js"):
continue
p = os.path.join(root, fn)
t = open(p).read()
o = t
t = t.replace(
"}( mw.uploadWizard ) );", "}( require( 'ext.uploadWizard' ) ) );"
)
if not TEST_RE.search(t):
if t != o:
open(p, "w").write(t)
continue
if "( function ( uw ) {" not in t:
if "( function () {" in t:
t = t.replace("( function () {", "( function ( uw ) {", 1)
t = re.sub(
r"^\}\(\) \);$",
"}( require( 'ext.uploadWizard' ) ) );",
t,
count=1,
flags=re.M,
)
else:
lines = t.rstrip("\n").split("\n")
t = (
"( function ( uw ) {\n"
+ "\n".join(("\t" + l if l else l) for l in lines)
+ "\n}( require( 'ext.uploadWizard' ) ) );\n"
)
t = TEST_RE.sub("uw.", t)
open(p, "w").write(t)
# JSDoc headers that documented the removed uw parameter
def rep(p, a, b):
s = open(p).read()
assert s.count(a) == 1, (p, a)
open(p, "w").write(s.replace(a, b))
rep(
"resources/mw.UploadWizard.js",
"/**\n * Object that represents the entire multi-step Upload Wizard\n *\n * @param uw\n */\n/**\n * @param config\n * @class\n */",
"/**\n * Object that represents the entire multi-step Upload Wizard\n *\n * @param config\n * @class\n */",
)
rep(
"resources/mw.UploadWizardUpload.js",
"/**\n * Represents the upload -- in its local",
"/*\n * Represents the upload -- in its local",
)
rep(
"resources/mw.UploadWizardUpload.js",
" * should fork this into two -- local and remote, e.g. filename\n *\n * @param uw\n */\n// eslint-disable-next-line no-unused-vars\n/**",
" * should fork this into two -- local and remote, e.g. filename\n */\n\n/**",
)
rep(
"resources/.eslintrc.json",
''' "uw",
"uw.controller.Step",
"uw.DetailsWidget",
"uw.FieldLayout",
"uw.PatentDialog",
"uw.UlsWidget",
"uw.ui.Step",
"uw.ValidatableElement",
"uw.ValidationStatus"''',
''' "ApiUploadFormDataHandler",
"ApiUploadPostHandler",
"CategorySuggestionProvider",
"DetailsWidget",
"FieldLayout",
"PatentDialog",
"StepController",
"StepUI",
"UlsWidget",
"UploadWizardDeedChooser",
"UploadWizardUpload",
"ValidatableElement",
"ValidationStatus"''',
)
print("LOCALS", len(LOCAL))
# The upload handler was looked up by name in the mw namespace
rep(
"resources/mw.UploadWizardUpload.js",
""" let constructor; // must be the name of a function in 'mw' namespace
if ( !this.uploadHandler ) {
constructor = 'ApiUploadFormDataHandler';
if ( mw.UploadWizard.config.debug ) {
mw.log( 'mw.UploadWizard::getUploadHandler> ' + constructor );
}
if ( this.file.fromURL ) {
constructor = 'ApiUploadPostHandler';
}
this.uploadHandler = new mw[ constructor ]( this, this.api );
}""",
""" if ( !this.uploadHandler ) {
const Handler = this.file.fromURL ? ApiUploadPostHandler : ApiUploadFormDataHandler;
if ( mw.UploadWizard.config.debug ) {
mw.log( 'mw.UploadWizard::getUploadHandler> ' + Handler.name );
}
this.uploadHandler = new Handler( this, this.api );
}""",
)
s = open("resources/mw.UploadWizardUpload.js").read()
open("resources/mw.UploadWizardUpload.js", "w").write(
"const ApiUploadFormDataHandler = require( './handlers/mw.ApiUploadFormDataHandler.js' );\nconst ApiUploadPostHandler = require( './handlers/mw.ApiUploadPostHandler.js' );\n"
+ s
)

Event Timeline

simon04 edited the content of this paste. (Show Details)