User:Lupin/scripter.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
Documentation for this user script can be added at User:Lupin/scripter. |
//~ Scripter: the main object here. Inserts code when editing
//~ monobook.js (in fact, when editing any page)
//~ construtor
function Scripter(){
this.startString='// Scripter: managed code begins';
this.startScript='// Scripter: managed script';
this.actions=[];
this.scripts=[];
}
//~ Scripter.prototype.getOrig: grab the content of the file before we
//~ mess around with it. Store it in this.orig.
Scripter.prototype.getOrig=function() {
this.textArea=document.getElementById('wpTextbox1');
if (!this.textArea) return false;
this.orig=this.textArea.value;
return this.orig;
}
//~ Scripter.prototype.parseOrig: take this.orig, and look for special
//~ comments inserted by previous Scripter instances. Store data in
//~ this.startLine, this.endLine, this.chunk (list of lines we're
//~ going to manipulate) and if we find a chunk, call this.parseChunk
Scripter.prototype.parseOrig=function(startAt) {
if (!this.orig) return false;
var lines=this.orig.split('\n');
for (var i=0; i<lines.length; ++i) {
if (lines[i].indexOf(this.startString)==0) {
var endString=lines[i].split('begins').join('ends');
for (var j=i; j<lines.length; ++j) {
if (lines[j]==endString) {
// got it
this.startLine=i;
this.endLine=j+1;
this.chunk=lines.slice(i,j);
this.parseChunk();
return true;
}
}
}
}
this.startLine=lines.length;
this.endLine=lines.length+1;
this.chunk=[];
return false;
}
//~ Scripter.prototype.parseChunk: look for script objects referred to
//~ in the chunk we found in parseOrig. Again, we're looking for
//~ special comments on a line-by-line basis. Complain if stuff seems
//~ wonky. Store the scripts we find in this.scripts, and put metadata
//~ in a new subobject of the script, script.meta
Scripter.prototype.parseChunk=function() {
if (!this.chunk) return false;
var lines=this.chunk;
var scripts=[];
for (var i=0; i<lines.length; ++i) {
if (lines[i].indexOf(this.startScript)==0) {
if (scripts.length) scripts[scripts.length-1].meta.chunkEndLine=i-1;
// ({..}) force {} to be seen as delimiting an object, not grouping braces
var evalMe='('+lines[i].replace(this.startScript, '') + ')';
try { var scriptDesc=eval(evalMe); }
catch (err) { alert( 'Bad script description at line '+ this.startLine + i); return false; }
scriptDesc.meta={};
scriptDesc.meta.chunkStartLine=i;
scripts.push(scriptDesc);
}
}
if (scripts.length) scripts[scripts.length-1].meta.chunkEndLine=lines.length-2;
this.scripts=scripts;
return scripts;
}
//~ Scripter.prototype.gatherScriptData (script): given a script, we
//~ use xmlhttp to download the content of script.src. We set the
//~ downloading status of the script in script.meta.status and give
//~ success/failure functions to call when the download finishes.
Scripter.prototype.gatherScriptData=function(script) {
if (!script.src) return false;
var titleBase='http://wiki.riteme.site/w/index.php?action=raw';
var savedThis=this;
if (typeof script.meta == 'undefined') script.meta={};
script.meta.status='downloading';
var onComplete=function(req,bundle) {
script.meta.content=req.responseText;
script.meta.status='complete';
}
var onFailure=function(req,bundle) {
script.meta.status='failed';
confirm ('One or more downloads failed. Retry?') && this.downloadScripts(true);
}
var url=titleBase + ( script.oldid ? '&oldid='+script.oldid : '') + '&title='+script.src;
scripter_download({url: url, onSuccess: onComplete, onFailure: onFailure});
return true;
}
//~ Scripter.prototype.downloadScripts(retry): loop over this.scripts
//~ and call gatherScriptData to grab them if appropriate (based on
//~ script.meta.status). Return the number of scripts which have not
//~ yet completed downloading successfully, or -1 if something goes
//~ wrong.
Scripter.prototype.downloadScripts=function(retry) {
// returns -1 on failure
// 0 on all complete
// n > 0 if some remain
if (!this.scripts) return -1;
var incomplete=0;
for (var i=0; i<this.scripts.length; ++i) {
var script=this.scripts[i];
if (!script) continue;
if (typeof script.meta=='undefined') script.meta={};
switch (script.meta.status) {
case 'complete':
break;
case 'failed':
incomplete++;
if (retry) {
this.gatherScriptData(script);
}
break;
case 'downloading':
incomplete++;
break;
default:
incomplete++;
this.gatherScriptData(script);
}
}
return incomplete;
}
//~ Scripter.prototype.download(onComplete): run downloadScripts every
//~ 0.5 seconds. When it says that all is done, call onComplete()
Scripter.prototype.download=function(onComplete) {
if (this.downloadScripts()===0) return onComplete();
var savedThis=this;
scripter_runOnce(function() {savedThis.download.apply(savedThis, [onComplete])}, 500);
}
//~ Scripter.prototype.concoctStanza(script): make the bit of the
//~ chunk we intend to write corresponding to the script. This takes
//~ the form of a special comment, containing all string and integer
//~ properties of the script expressed in a form suitable for feeding
//~ to eval.
Scripter.prototype.concoctStanza=function(script) {
var ret=this.startScript;
ret += ' {';
var tmp=[];
for (var prop in script) {
switch (typeof script[prop]) {
case 'string':
tmp.push(prop + ':' + '"' + script[prop].split('"').join('\\"') + '"');
break;
case 'number':
tmp.push(prop + ':' + script[prop]);
break;
}
}
ret += tmp.join(', ');
ret += '}\n';
if (script.meta.content) ret += script.meta.content + '\n';
return ret;
}
//~ Scripter.prototype.concoctNewchunk: make the new chunk, with
//~ special comments at the start and end, and script stanzas from
//~ concoctStanta(script) in between.
Scripter.prototype.concoctNewchunk=function() {
var magic='';
do {magic=(new Date()).getTime().toString();}
while (this.orig.indexOf(magic) != -1);
var ret=[this.startString, magic].join(' ') + '\n';
for (var i=0; i<this.scripts.length; ++i) {
if (!this.scripts[i]) continue;
ret += this.concoctStanza(this.scripts[i]) + '\n';
}
ret += [this.startString.split('begins').join('ends'), magic].join(' ');
return ret;
}
//~ Scripter.prototype.doActions: run over the actions array and carry
//~ out the instructions. Look for actions[i].action (can be 'install'
//~ or 'remove') and use data actions[i].script to identify the
//~ script. We only need provide the actions[i].script.name for
//~ removal, but have to give a complete script spec for installation
Scripter.prototype.doActions=function() {
for (var i=0; i< this.actions.length; ++i) {
var script=this.actions[i].script;
if (this.actions[i].action=='install') {
var done=false;
for (var j=0; j<this.scripts.length; ++j) {
if (!this.scripts[j]) continue;
if (this.scripts[j].name==script.name) {
// replace old with new
this.scripts[j]=script;
done=true;
}
}
if (!done) this.scripts.push(script);
}
else if (this.actions[i].action=='remove') {
for (var j=0; j<this.scripts.length; ++j) {
if(! this.scripts[j]) continue;
if (this.scripts[j].name==script.name) {
this.scripts[j]=null;
}
}
}
}
}
//~ Scripter.prototype.install, Scripter.prototype.finishInstall: run
//~ the stuff above in the right order. We need two functions as we
//~ wait for the downloads to complete in between.
Scripter.prototype.install=function() {
document.title='Installing...';
this.getOrig();
this.parseOrig();
this.doActions();
var savedThis=this;
this.download(function() {savedThis.finishInstall.apply(savedThis)});
}
Scripter.prototype.finishInstall=function() {
var newChunk=this.concoctNewchunk();
var lines=this.orig.split('\n');
var newLines=lines.slice(0,this.startLine).join('\n')+'\n';
newLines += newChunk+'\n';
newLines+=lines.slice(this.endLine).join('\n');
this.textArea.value=newLines;
document.title+=' all done.';
}
////////////////////
// Utility functions
////////////////////
function scripter_runOnce(f, time) {
var i=scripter_runOnce.timers.length;
var ff = function () { clearInterval(scripter_runOnce.timers[i]); f() };
var timer=setInterval(ff, time);
scripter_runOnce.timers.push(timer);
}
scripter_runOnce.timers=[];
function scripter_download(bundle) {
// mandatory: bundle.url,
// optional: bundle.onSuccess, bundle.onFailure, bundle.otherStuff
var x = window.XMLHttpRequest ? new XMLHttpRequest()
: window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP")
: false;
if (!x) return false;
x.onreadystatechange=function() { x.readyState==4 && scripter_downloadComplete(x,bundle); };
x.open("GET",bundle.url,true); x.send(null);
return true;
}
function scripter_downloadComplete(x,bundle) {
x.status==200 && ( bundle.onSuccess && bundle.onSuccess(x,bundle) || true )
|| ( bundle.onFailure && bundle.onFailure(x,bundle) || alert(x.statusText));
}
function WPUS(name) {
return 'Wikipedia:WikiProject_User_scripts/Scripts/' + name + '.js';
}
function LupinScript(name) {
return 'User:Lupin/Scripter/' + name;
}
// Testing code starts here
function testScripter() {
var s=new Scripter();
/* s.getOrig(); */
/* s.parseOrig(); */
/* s.chunk */
/* s.scripts.length */
//s.download(function() { alert(s.concoctNewchunk())})
s.actions.push({action:'remove', script:{name:'Navpopups'}});
s.actions.push({action:'install', script:{name: 'addOnloadFunction', src:WPUS('addOnloadFunction'), oldid:25657320}});
s.actions.push({action:'install', script:{name: 'evaluator', src: LupinScript('evaluator'), oldid:30669595}});
s.install()
}
/* testing chunk
// Scripter: managed code begins foobar
// Scripter: managed script {name: 'Navpopups', src: 'User:Lupin/Scripter/popups', oldid:30668675}
// Scripter: managed script {name: 'add edit section 0', src:'Wikipedia:WikiProject_User_scripts/Scripts/Add_edit_section_0', oldid:21025437}
// Scripter: managed script {name: 'LAVT', src:'User:Lupin/Scripter/recent2', oldid:30669328}
// Scripter: managed script {name: 'addOnloadFunction', src:'Wikipedia:WikiProject_User_scripts/Scripts/addOnloadFunction.js', oldid:25657320}
// Scripter: managed script {name: 'evaluator', src: 'User:Lupin/Scripter/evaluator', oldid:30669595}
// Scripter: managed code ends foobar
*/
/// Local Variables: ///
/// mode:c ///
/// fill-prefix:"//~ " ///
/// End: ///