r111031 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r111030‎ | r111031 | r111032 >
Date:13:44, 9 February 2012
Author:gwicke
Status:deferred
Tags:
Comment:
Move attribute expansion back to separate handler, as this makes it easier to
only expand used branches selected by parser functions. Template (and
-argument) expansion is simply registered before general expansion.

Additionally, a few more simple time-based magic words are added in
ParserFunctions.
Modified paths:
  • /trunk/extensions/VisualEditor/modules/parser/ext.core.AttributeExpander.js (added) (history)
  • /trunk/extensions/VisualEditor/modules/parser/ext.core.ParserFunctions.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/parser/ext.core.TemplateHandler.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/parser/mediawiki.TokenTransformManager.js (modified) (history)
  • /trunk/extensions/VisualEditor/modules/parser/mediawiki.parser.js (modified) (history)

Diff [purge]

Index: trunk/extensions/VisualEditor/modules/parser/mediawiki.TokenTransformManager.js
@@ -486,28 +486,7 @@
487487
488488 for ( var i = 0; i < tokensLength; i++ ) {
489489 token = tokens[i];
490 -
491 - if ( token.attribs ) {
492 - // TODO: Transform token attributes (that need it) before general token processing.
493 - // Sync return -> continue as-is.
494 - // Async return -> return with async
495 - // per-item callback: function ( res ) {
496 - var atm = new AttributeTransformManager (
497 - this,
498 - this._returnAttributes.bind( this, token, cb )
499 - );
500490
501 - if ( atm.process( token.attribs ) ) {
502 - // async attribute expansion ongoing
503 - activeAccum = accum;
504 - accum = new TokenAccumulator( this, activeAccum.getParentCB( 'sibling' ) );
505 - cb = accum.getParentCB( 'child' );
506 - // Only transform token once attributes are fully processed
507 - // _returnAttributes will call transformTokens if sync flag is not set
508 - continue;
509 - }
510 - }
511 -
512491 switch ( token.constructor ) {
513492 case String:
514493 res = this._transformToken( token, phaseEndRank, ts.text, cb );
@@ -566,8 +545,6 @@
567546 accum = new TokenAccumulator( this, activeAccum.getParentCB( 'sibling' ) );
568547 cb = accum.getParentCB( 'child' );
569548 }
570 -
571 -
572549 }
573550
574551 // Return finished tokens directly to caller, and indicate if further
@@ -613,36 +590,6 @@
614591 }
615592 };
616593
617 -AsyncTokenTransformManager.prototype._returnAttributes
618 - = function ( token, cb, attributes, async )
619 -{
620 - this.env.dp( 'AsyncTokenTransformManager._returnAttributes: ' +
621 - JSON.stringify( token, null, 2 ) +
622 - JSON.stringify( attributes, null, 2 ) );
623 - token.attribs = attributes;
624 -
625 -
626 - if ( async ) {
627 - // Attributes were expanded asynchronously, so now we still need to
628 - // process the token.
629 - var res = this.transformTokens( [token], cb );
630 - if ( res.async ) {
631 - // Token transformation is again asynchronous. There is already a
632 - // TokenAccumulator associated with this token, so we only need to
633 - // be concerned with synchronously returned tokens.
634 - if ( res.tokens ) {
635 - // Pass on already-returned tokens to the cb
636 - cb( res.tokens, true );
637 - }
638 - } else if ( res.token ) {
639 - cb ( [res.token], false );
640 - } else {
641 - cb ( res.tokens, false );
642 - }
643 - }
644 -};
645 -
646 -
647594 /**
648595 * Callback for the end event emitted from the tokenizer.
649596 * Either signals the end of input to the tail of an ongoing asynchronous
@@ -858,11 +805,11 @@
859806 if ( this.outstanding === 0 ) {
860807 this._returnAttributes();
861808 // synchronous / done
862 - return false;
 809+ return true;
863810 } else {
864811 // async, will call back
865812 this.async = true;
866 - return true;
 813+ return false;
867814 }
868815 };
869816
Index: trunk/extensions/VisualEditor/modules/parser/ext.core.ParserFunctions.js
@@ -34,8 +34,8 @@
3535 return argDict['#default'];
3636 } else {
3737 var lastKV = argList[argList.length - 1];
38 - if ( lastKV && ! lastKV[0].length ) {
39 - return lastKV[1];
 38+ if ( lastKV && ! lastKV.v.length ) {
 39+ return lastKV.v;
4040 } else {
4141 return [];
4242 }
@@ -47,10 +47,10 @@
4848 if ( argList.length < 2 ) {
4949 return [];
5050 } else {
51 - if ( target.trim() === this.manager.env.tokensToString( argList[0][1] ).trim() ) {
52 - return ( argList[1] && argList[1][1]) || [];
 51+ if ( target.trim() === this.manager.env.tokensToString( argList[0].v ).trim() ) {
 52+ return ( argList[1] && argList[1].v) || [];
5353 } else {
54 - return ( argList[2] && argList[2][1]) || [];
 54+ return ( argList[2] && argList[2].v) || [];
5555 }
5656 }
5757 };
@@ -78,6 +78,17 @@
7979 return [];
8080 }
8181 };
 82+ParserFunctions.prototype['pf_padleft'] = function ( target, argList, argDict ) {
 83+ if ( '1' in argDict ) {
 84+ var n = argDict[1];
 85+ while ( target.length < n ) {
 86+ target = '0' + target;
 87+ }
 88+ return [target];
 89+ } else {
 90+ return [];
 91+ }
 92+};
8293
8394 ParserFunctions.prototype['pf_#tag'] = function ( target, argList, argDict ) {
8495 // XXX: handle things like {{#tag:nowiki|{{{input1|[[shouldnotbelink]]}}}}}
@@ -87,6 +98,25 @@
8899 [ new EndTagTk( target ) ] );
89100 };
90101
 102+ParserFunctions.prototype['pf_currentyear'] = function ( target, argList, argDict ) {
 103+ return this['pf_#time']( 'Y', [], {} );
 104+};
 105+ParserFunctions.prototype['pf_currentmonth'] = function ( target, argList, argDict ) {
 106+ return this['pf_#time']( 'm', [], {} );
 107+};
 108+ParserFunctions.prototype['pf_currentmonthname'] = function ( target, argList, argDict ) {
 109+ return this['pf_#time']( 'J', [], {} );
 110+};
 111+ParserFunctions.prototype['pf_currentmonthabbrev'] = function ( target, argList, argDict ) {
 112+ return this['pf_#time']( 'M', [], {} );
 113+};
 114+ParserFunctions.prototype['pf_currentday'] = function ( target, argList, argDict ) {
 115+ return this['pf_#time']( 'j', [], {} );
 116+};
 117+ParserFunctions.prototype['pf_currentdayname'] = function ( target, argList, argDict ) {
 118+ return this['pf_#time']( 'l', [], {} );
 119+};
 120+
91121 // A first approximation, anyway..
92122 // Based on http://jacwright.com/projects/javascript/date_format/ for now, MIT
93123 // licensed.
@@ -94,7 +124,7 @@
95125 var res,
96126 tpl = target.trim();
97127 //try {
98 - // var date = new Date( this.manager.env.tokensToString( argList[0][1] ) );
 128+ // var date = new Date( this.manager.env.tokensToString( argList[0].v ) );
99129 // res = [ date.format( target ) ];
100130 //} catch ( e ) {
101131 // this.manager.env.dp( 'ERROR: #time ' + e );
@@ -191,16 +221,16 @@
192222 return [ 'class="error" in expression ' + target ];
193223 }
194224 if ( res ) {
195 - return ( argList[0] && argList[0][1] ) || [];
 225+ return ( argList[0] && argList[0].v ) || [];
196226 } else {
197 - return ( argList[1] && argList[1][1] ) || [];
 227+ return ( argList[1] && argList[1].v ) || [];
198228 }
199229 };
200230 ParserFunctions.prototype['pf_#iferror'] = function ( target, argList, argDict ) {
201231 if ( target.indexOf( 'class="error"' ) >= 0 ) {
202 - return ( argList[0] && argList[0][1] ) || [];
 232+ return ( argList[0] && argList[0].v ) || [];
203233 } else {
204 - return ( argList[1] && argList[1][1] ) || [];
 234+ return ( argList[1] && argList[1].v ) || [];
205235 }
206236 };
207237 ParserFunctions.prototype['pf_#expr'] = function ( target, argList, argDict ) {
@@ -234,7 +264,7 @@
235265
236266 // FIXME
237267 ParserFunctions.prototype['pf_#ifexist'] = function ( target, argList, argDict ) {
238 - return ( argList[0] && argList[0][1] ) || [];
 268+ return ( argList[0] && argList[0].v ) || [];
239269 };
240270 ParserFunctions.prototype['pf_formatnum'] = function ( target, argList, argDict ) {
241271 return [ target ];
@@ -251,6 +281,9 @@
252282 ParserFunctions.prototype['pf_pagename'] = function ( target, argList, argDict ) {
253283 return [ target ];
254284 };
 285+ParserFunctions.prototype['pf_pagenamee'] = function ( target, argList, argDict ) {
 286+ return [ target ];
 287+};
255288 ParserFunctions.prototype['pf_fullpagename'] = function ( target, argList, argDict ) {
256289 return [target];
257290 };
@@ -283,6 +316,9 @@
284317 ParserFunctions.prototype['pf_talkspace'] = function ( target, argList, argDict ) {
285318 return ['Talk'];
286319 };
 320+ParserFunctions.prototype['pf_numberofarticles'] = function ( target, argList, argDict ) {
 321+ return ["1"];
 322+};
287323
288324 if (typeof module == "object") {
289325 module.exports.ParserFunctions = ParserFunctions;
Index: trunk/extensions/VisualEditor/modules/parser/ext.core.AttributeExpander.js
@@ -0,0 +1,91 @@
 2+/**
 3+ * Geniric attribute expansion handler.
 4+ *
 5+ * @author Gabriel Wicke <gwicke@wikimedia.org>
 6+ * @author Brion Vibber <brion@wikimedia.org>
 7+ */
 8+var $ = require('jquery'),
 9+ request = require('request'),
 10+ events = require('events'),
 11+ qs = require('querystring'),
 12+ ParserFunctions = require('./ext.core.ParserFunctions.js').ParserFunctions,
 13+ AttributeTransformManager = require('./mediawiki.TokenTransformManager.js')
 14+ .AttributeTransformManager,
 15+ defines = require('./mediawiki.parser.defines.js');
 16+
 17+
 18+function AttributeExpander ( manager ) {
 19+ this.reset();
 20+ this.register( manager );
 21+}
 22+
 23+AttributeExpander.prototype.reset = function ( token ) {
 24+ return {token: token};
 25+};
 26+
 27+// constants
 28+AttributeExpander.prototype.rank = 1.11;
 29+
 30+AttributeExpander.prototype.register = function ( manager ) {
 31+ this.manager = manager;
 32+ // Register for template and templatearg tag tokens
 33+ manager.addTransform( this.onToken.bind(this),
 34+ this.rank, 'any' );
 35+
 36+};
 37+
 38+
 39+/**
 40+ * Token handler
 41+ *
 42+ * Expands target and arguments (both keys and values) and either directly
 43+ * calls or sets up the callback to _expandTemplate, which then fetches and
 44+ * processes the template.
 45+ */
 46+AttributeExpander.prototype.onToken = function ( token, frame, cb ) {
 47+ if ( token.constructor === TagTk && token.attributes ) {
 48+ var expandData = {
 49+ token: token,
 50+ cb: cb
 51+ };
 52+ var atm = new AttributeTransformManager(
 53+ this.manager,
 54+ this._returnAttributes.bind( this, expandData )
 55+ );
 56+ if( atm.process( token.attribs ) ) {
 57+ // Attributes were transformed synchronously
 58+ this.manager.env.dp (
 59+ 'sync attribs for ' + JSON.stringify( tplExpandData.target ),
 60+ tplExpandData.expandedArgs
 61+ );
 62+ // All attributes are fully expanded synchronously (no IO was needed)
 63+ return { token: token };
 64+ } else {
 65+ // Async attribute expansion is going on
 66+ this.manager.env.dp( 'async return for ' + JSON.stringify( token ));
 67+ expandData.async = true;
 68+ return { async: true };
 69+ }
 70+ } else {
 71+ return { token: token };
 72+ }
 73+};
 74+
 75+
 76+/**
 77+ * Callback for argument (including target) expansion in AttributeTransformManager
 78+ */
 79+AttributeExpander.prototype._returnAttributes = function ( expandData,
 80+ attributes )
 81+{
 82+ this.manager.env.dp( 'AttributeExpander._returnAttributes: ' + JSON.stringify(attributes) );
 83+ // Remove the target from the attributes
 84+ expandData.token.attribs = attributes;
 85+ if ( expandData.async ) {
 86+ expandData.cb( [expandData.token], false );
 87+ }
 88+};
 89+
 90+if (typeof module == "object") {
 91+ module.exports.AttributeExpander = AttributeExpander;
 92+}
Property changes on: trunk/extensions/VisualEditor/modules/parser/ext.core.AttributeExpander.js
___________________________________________________________________
Added: svn:eol-style
193 + native
Index: trunk/extensions/VisualEditor/modules/parser/mediawiki.parser.js
@@ -24,6 +24,7 @@
2525 .PostExpandParagraphHandler,
2626 Sanitizer = require('./ext.core.Sanitizer.js').Sanitizer,
2727 TemplateHandler = require('./ext.core.TemplateHandler.js').TemplateHandler,
 28+ AttributeExpander = require('./ext.core.AttributeExpander.js').AttributeExpander,
2829 Cite = require('./ext.Cite.js').Cite,
2930 FauxHTML5 = require('./mediawiki.HTML5TreeBuilder.node.js').FauxHTML5,
3031 DOMPostProcessor = require('./mediawiki.DOMPostProcessor.js').DOMPostProcessor,
@@ -176,6 +177,7 @@
177178
178179 // Register template expansion extension
179180 new TemplateHandler( tokenExpander );
 181+ new AttributeExpander( tokenExpander );
180182
181183 tokenExpander.listenForTokensFrom ( tokenPreProcessor );
182184 // XXX: hack.
@@ -224,6 +226,7 @@
225227 args, this.env
226228 );
227229 new TemplateHandler( tokenExpander );
 230+ new AttributeExpander( tokenExpander );
228231 tokenExpander.listenForTokensFrom ( tokenPreProcessor );
229232
230233 return new CachedTokenPipeline(
Index: trunk/extensions/VisualEditor/modules/parser/ext.core.TemplateHandler.js
@@ -65,6 +65,7 @@
6666 cb: cb,
6767 origToken: token,
6868 resultTokens: [],
 69+ attribsAsync: true,
6970 overallAsync: false,
7071 expandDone: false
7172 },
@@ -72,16 +73,30 @@
7374 i = 0,
7475 res;
7576
76 - tplExpandData.target = token.attribs.shift().v;
77 - tplExpandData.expandedArgs = this._nameArgs( token.attribs );
 77+ var attributes = [token.attribs.shift()].concat( this._nameArgs( token.attribs ) );
7878
79 - // Attributes were transformed synchronously
80 - //this.manager.env.dp (
81 - // 'sync attribs for ' + JSON.stringify( tplExpandData.target ),
82 - // tplExpandData.expandedArgs
83 - //);
84 - // All attributes are fully expanded synchronously (no IO was needed)
85 - return this._expandTemplate ( tplExpandData );
 79+ this.manager.env.dp( 'before AttributeTransformManager: ' +
 80+ JSON.stringify( attributes, null, 2 ) );
 81+ new AttributeTransformManager(
 82+ this.manager,
 83+ this._returnAttributes.bind( this, tplExpandData )
 84+ ).process( attributes );
 85+
 86+ // Unblock finish
 87+ if ( ! tplExpandData.attribsAsync ) {
 88+ // Attributes were transformed synchronously
 89+ this.manager.env.dp (
 90+ 'sync attribs for ' + JSON.stringify( tplExpandData.target ),
 91+ tplExpandData.expandedArgs
 92+ );
 93+ // All attributes are fully expanded synchronously (no IO was needed)
 94+ return this._expandTemplate ( tplExpandData );
 95+ } else {
 96+ // Async attribute expansion is going on
 97+ this.manager.env.dp( 'async return for ' + JSON.stringify( token ));
 98+ tplExpandData.overallAsync = true;
 99+ return { async: true };
 100+ }
86101 };
87102
88103 /**
@@ -103,6 +118,22 @@
104119 return out;
105120 };
106121
 122+/**
 123+ * Callback for argument (including target) expansion in AttributeTransformManager
 124+ */
 125+TemplateHandler.prototype._returnAttributes = function ( tplExpandData,
 126+ attributes )
 127+{
 128+ this.manager.env.dp( 'TemplateHandler._returnAttributes: ' + JSON.stringify(attributes) );
 129+ // Remove the target from the attributes
 130+ tplExpandData.attribsAsync = false;
 131+ tplExpandData.target = attributes[0].v;
 132+ attributes.shift();
 133+ tplExpandData.expandedArgs = attributes;
 134+ if ( tplExpandData.overallAsync ) {
 135+ this._expandTemplate ( tplExpandData );
 136+ }
 137+};
107138
108139 /**
109140 * Fetch, tokenize and token-transform a template after all arguments and the
@@ -214,10 +245,9 @@
215246 // expandDone is set to true in our _onEnd handler.
216247 if ( tplExpandData.overallAsync ||
217248 ! tplExpandData.expandDone ) {
 249+ tplExpandData.overallAsync = true;
218250 this.manager.env.dp( 'Async return from _expandTemplate for ' +
219 - JSON.stringify ( tplExpandData.target ) +
220 - JSON.stringify( tplExpandData, null, 2 ));
221 - tplExpandData.overallAsync = true;
 251+ JSON.stringify ( tplExpandData.target ) );
222252 return { async: true };
223253 } else {
224254 this.manager.env.dp( 'Sync return from _expandTemplate for ' +
@@ -288,13 +318,12 @@
289319 // @fixme normalize name?
290320 var self = this;
291321 if ( title in this.manager.env.pageCache ) {
292 - callback( self.manager.env.pageCache[title], title );
293 - //// Unwind the stack
294 - //process.nextTick(
295 - // function () {
296 - // callback( self.manager.env.pageCache[title], title );
297 - // }
298 - //);
 322+ // Unwind the stack
 323+ process.nextTick(
 324+ function () {
 325+ callback( self.manager.env.pageCache[title], title );
 326+ }
 327+ );
299328 } else if ( ! this.manager.env.fetchTemplates ) {
300329 callback( 'Warning: Page/template fetching disabled, and no cache for ' +
301330 title, title );
@@ -324,26 +353,51 @@
325354 * Expand template arguments with tokens from the containing frame.
326355 */
327356 TemplateHandler.prototype.onTemplateArg = function ( token, frame, cb ) {
328 - var argName = this.manager.env.tokensToString( token.attribs.shift().v ).trim(),
 357+
 358+ token.resultTokens = false;
 359+
 360+ new AttributeTransformManager (
 361+ this.manager,
 362+ this._returnArgAttributes.bind( this, token, cb, frame )
 363+ ).process( token.attribs );
 364+
 365+ if ( token.resultTokens !== false ) {
 366+ // synchronous return
 367+ //console.log( 'synchronous attribute expand: ' + JSON.stringify( token.resultTokens ) );
 368+
 369+ return { tokens: token.resultTokens };
 370+ } else {
 371+ //console.log( 'asynchronous attribute expand: ' + JSON.stringify( token, null, 2 ) );
 372+ // asynchronous return
 373+ token.resultTokens = [];
 374+ return { async: true };
 375+ }
 376+};
 377+
 378+TemplateHandler.prototype._returnArgAttributes = function ( token, cb, frame, attributes ) {
 379+ //console.log( '_returnArgAttributes: ' + JSON.stringify( attributes ));
 380+ var argName = this.manager.env.tokensToString( attributes[0].v ).trim(),
329381 res;
330 -
331382 if ( argName in this.manager.args ) {
332383 // return tokens for argument
333384 //console.log( 'templateArg found: ' + argName +
334385 // ' vs. ' + JSON.stringify( this.manager.args ) );
335386 res = this.manager.args[argName];
336387 } else {
337 - var defaultValue = token.attribs[0];
 388+ var defaultValue = (attributes[1] && ! attributes[1].k.length && attributes[1].v) || false;
338389 this.manager.env.dp( 'templateArg not found: ' + argName +
339390 ' vs. ' + JSON.stringify( defaultValue ) );
340 - if ( defaultValue && ! defaultValue.k.length && defaultValue.v.length ) {
341 - res = defaultValue.v;
 391+ if ( defaultValue ) {
 392+ res = defaultValue;
342393 } else {
343394 res = [ '{{{' + argName + '}}}' ];
344395 }
345396 }
346 - return { tokens: res };
347 -
 397+ if ( token.resultTokens !== false ) {
 398+ cb( res );
 399+ } else {
 400+ token.resultTokens = res;
 401+ }
348402 };
349403
350404

Status & tagging log