Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/*  ___________________________________________________________________________ * |                                                                           | * |                    === WARNING: GLOBAL GADGET FILE ===                    | * |                  Changes to this page affect many users.                  | * | Please discuss changes on the talk page or on [[Wikipedia_talk:Gadget]]   | * | before editing.                                                           | * |___________________________________________________________________________| * *  * libLua provides functions for interacting with Lua modules from JavaScript. *  *  *                              === USAGE === *  * The library should be loaded as a MediaWiki gadget, using mw.loader.load, * mw.loader.using, or similar. The name of the gadget is * "ext.gadget.libLua". Once the gadget is loaded, you can access its * functions from mw.libs.lua.<function name>. Documentation for the * functions can be found in the JSDoc comment blocks in the library code. For * example: *  * // Call p.main("foo", "bar") in [[Module:Example]] * mw.loader.using( [ 'ext.gadget.libLua' ], function () { *     mw.libs.lua.call( { *         module: 'Example', *         func: 'main', *         args: [ 'foo', 'bar' ] *     } ).then( function ( result ) { *         // Do something with the result *     } ); * } ); *  *  *                             === LICENCE === * * Author: Mr. Stradivarius * Licence: MIT * * The MIT License (MIT) * * Copyright (c) 2016 Mr. Stradivarius *  * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: *  * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. *  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */( function ( $, mw, undefined ) {'use strict';/*** Encode a string for including in a Lua question.* At the moment this is just a wrapper for JSON.stringify, as that does* what we need. However, encoding a Lua string is conceptually different* from encoding JSON, so we use different function names for the two tasks.* This will also make it easier to update the code in the future, if* necessary.* @private*/function makeLuaString( s ) {return JSON.stringify( s );}/*** Make a Lua question string from a module name, a function name and an* optional args array.* @private*/function makeQuestion( module, func, args ) {var escapedModule = makeLuaString( 'Module:' + module ),escapedFunc = makeLuaString( func ),json, escapedJson, argString;if ( args ) {json = JSON.stringify( args );escapedJson = makeLuaString( json );argString = 'unpack(mw.text.jsonDecode(' + escapedJson + '))';} else {argString = '';}return '=require(' + escapedModule + ')[' + escapedFunc + '](' + argString + ')';}/*** Reject a deferred object with the specified error code and error message.* If no deferred object is supplied with the third parameter, a new one is* created. We use this particular format for the error objects as it is the* same one used by the MediaWiki API, and so clients will only have to* worry about errors being formatted in one way.* @private*/function rejectDeferred( code, msg, deferred ) {if ( !deferred ) {deferred = $.Deferred();}return deferred.reject(code,{ error: {code: code,info: msg} });}mw.libs.lua = {/*** Call a function in a Lua module. The function call is made* asynchronously through the MediaWiki Action API, and its result is* wrapped in a jQuery promise.** @param {Object} options** @param {string} options.module - The name of the module to load.* (Don't use a "Module:" prefix.)** @param {string} options.func - The name of the function to call.* Only strings are accepted as function names.** @param {*[]} [options.args] - An array of arguments to pass to the* function. These must be serializable as JSON. The arguments will be* unpacked when passed to the function; when calling a function "func",* an args array of ["foo", "bar", "baz"] will be called as* func("foo", "bar", "baz"). There are limitations in what can be* decoded from JSON in Lua: for example, keys may be dropped from* arrays containing null values. See* https://www.mediawiki.org/wiki/Extension:Scribunto/Lua_reference_manual#mw.text.jsonDecode* for more details. For this reason, calls like func('foo', nil, 'bar')* cannot be made directly. To work around this you can define an* intermediary function in a Lua module that calls the desired function* indirectly, and then call that function from this library instead.** @param {('string'|'json')} [options.format=string] - The expected* return format. If this is "string" or undefined, then the return value* will be a string. (If the Lua function call returns a non-string value* it will be converted to a string, and if the function call returns* multiple values then they will be converted to strings and* concatenated with tabs as separators.) If this is "json", then the* return string from the function call is assumed to be JSON, and is* converted to a JavaScript object using JSON.parse. If the return* string is not valid JSON, the promise returned from the function is* rejected, but no error is thrown.** @param {mw.Api} [options.api] - An mw.Api object to use for API* calls. If this is not specified, a new mw.Api object using default* values is created.** @return {$.Promise}* A jQuery Promise that is resolved with the result of the function* call.*@example // Load the gadgetmw.loader.using( 'ext.gadget.libLua', function () {// Call p.main( "foo", "bar", "baz" ) in Module:Example.mw.libs.lua.call( {"module": "Example","func": "main","args": [ "foo", "bar", "baz" ]} ).done( function( resultString ) {doSomething( resultString );} );} );*@example // Load the gadgetmw.loader.using( 'ext.gadget.libLua', function () {// Call p.getJson( "foo" ) in Module:Example.mw.libs.lua.call( {"format": "json","module": "Example","func": "getJson","args": [ "foo" ]} ).done( function( data ) {doSomething( data.bar.baz );} );} );**/call: function ( options ) {// Deal with bad argumentsif ( !( options instanceof Object ) ) {return rejectDeferred('liblua-call-options-type-error',"type error in arg #1 to 'call' (object expected)");} else if ( typeof options.module !== 'string' ) {return rejectDeferred('liblua-call-module-type-error','type error in options.module (string expected)');} else if ( typeof options.func !== 'string' ) {return rejectDeferred('liblua-call-func-type-error','type error in options.func (string expected)');} else if ( options.args !== undefined && !$.isArray( options.args ) ) {return rejectDeferred('liblua-call-invalid-args','options.args was defined but was not an array');} else if ( options.format !== undefined&& options.format !== 'json'&& options.format !== 'string' ) {return rejectDeferred('liblua-call-format-type-error',"invalid format specified (must be 'json', 'string' or undefined)");} else if ( options.api !== undefined && !( options.api instanceof mw.Api ) ) {return rejectDeferred('liblua-call-invalid-api-object','options.api is not a valid mw.Api object.');}// Generate a new API object if we weren't passed one.var api = options.api || new mw.Api();// Make the API call.// The title field in scribunto-console doesn't seem to allow us to// use the p variable to load the module content, so set it to a// dummy value with blank content and load the module in the// question instead.return api.postWithToken( 'csrf', {action: 'scribunto-console',format: 'json',title: 'Example',content: '',question: makeQuestion( options.module, options.func, options.args ),clear: true} ).then( function ( obj ) {// Wrap the API query in a new jQuery Deferred object so that// we can reject API results that are invalid Lua but not// treated as errors by the API.return $.Deferred( function ( deferred ) {// Deal with any errors from the API or from Lua.if ( obj.type === 'error' ) {// Lua command failed but API call succeededreturn rejectDeferred(obj.messagename,obj.message,deferred);} else if ( obj.error ) {// API call failedreturn deferred.reject( obj.error.code, obj );} else if ( obj.type !== 'normal' ) {// Unknown API responsereturn rejectDeferred('liblua-call-unknown-api-response','Unknown API response',deferred);}var result = obj['return'];// Try to parse JSON if options.format equals 'json'if ( options.format == 'json' ) {try {result = JSON.parse( result );} catch ( e ) {if ( e instanceof SyntaxError ) {return rejectDeferred('liblua-call-json-syntax-error','The Lua function call returned invalid JSON: ' + e.message,deferred);} else {return rejectDeferred('liblua-call-json-unexpected-error','An unexpected error occurred while trying to ' +'parse the JSON returned from the Lua function call',deferred);}}}return deferred.resolve( result );} ).promise();} );}};} )( jQuery, mediaWiki );