Lompat ke isi

MediaWiki:Gadget-libLua.js

Dari Wikipedia bahasa Indonesia, ensiklopedia bebas
Versi yang bisa dicetak tidak lagi didukung dan mungkin memiliki kesalahan tampilan. Tolong perbarui markah penjelajah Anda dan gunakan fungsi cetak penjelajah yang baku.

Catatan: Setelah menyimpan, Anda harus memintas tembolok (cache) peramban Anda untuk melihat perubahannya. Google Chrome, Firefox, Microsoft Edge dan Safari: Tahan tombol Shift dan klik Muat ulang (Reload) di tombol bilah alat. Untuk detail dan instruksi tentang peramban lain, lihat halaman menghapus singgahan (Inggris).

/*  ___________________________________________________________________________
 * |                                                                           |
 * |                    === 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 gadget
		mw.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 gadget
		mw.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 arguments
			if ( !( 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 succeeded
						return rejectDeferred(
							obj.messagename,
							obj.message,
							deferred
						);
					} else if ( obj.error ) {
						// API call failed
						return deferred.reject( obj.error.code, obj );
					} else if ( obj.type !== 'normal' ) {
						// Unknown API response
						return 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 );