1
0
mirror of https://github.com/gnosygnu/xowa.git synced 2026-03-02 03:49:30 +00:00

Res: Add resources from xowa_app_windows_64_v4.5.26.1810

This commit is contained in:
gnosygnu
2018-11-02 09:58:55 -04:00
parent a672fd8340
commit 5721913241
6057 changed files with 1156950 additions and 0 deletions

View File

@@ -0,0 +1,209 @@
---
-- An implementation of the lua 5.2 bit32 library, in pure Lua
-- Note that in Lua, "x % n" is defined such that will always return a number
-- between 0 and n-1 for positive n. We take advantage of that a lot here.
local bit32 = {}
local function checkint( name, argidx, x, level )
local n = tonumber( x )
if not n then
error( string.format(
"bad argument #%d to '%s' (number expected, got %s)",
argidx, name, type( x )
), level + 1 )
end
return math.floor( n )
end
local function checkint32( name, argidx, x, level )
local n = tonumber( x )
if not n then
error( string.format(
"bad argument #%d to '%s' (number expected, got %s)",
argidx, name, type( x )
), level + 1 )
end
return math.floor( n ) % 0x100000000
end
function bit32.bnot( x )
x = checkint32( 'bnot', 1, x, 2 )
-- In two's complement, -x = not(x) + 1
-- So not(x) = -x - 1
return ( -x - 1 ) % 0x100000000
end
---
-- Logic tables for and/or/xor. We do pairs of bits here as a tradeoff between
-- table space and speed. If you change the number of bits, also change the
-- constants 2 and 4 in comb() below, and the initial value in bit32.band and
-- bit32.btest
local logic_and = {
[0] = { [0] = 0, 0, 0, 0},
[1] = { [0] = 0, 1, 0, 1},
[2] = { [0] = 0, 0, 2, 2},
[3] = { [0] = 0, 1, 2, 3},
}
local logic_or = {
[0] = { [0] = 0, 1, 2, 3},
[1] = { [0] = 1, 1, 3, 3},
[2] = { [0] = 2, 3, 2, 3},
[3] = { [0] = 3, 3, 3, 3},
}
local logic_xor = {
[0] = { [0] = 0, 1, 2, 3},
[1] = { [0] = 1, 0, 3, 2},
[2] = { [0] = 2, 3, 0, 1},
[3] = { [0] = 3, 2, 1, 0},
}
---
-- @param name string Function name
-- @param args table Function args
-- @param nargs number Arg count
-- @param s number Start value, 0-3
-- @param t table Logic table
-- @return number result
local function comb( name, args, nargs, s, t )
for i = 1, nargs do
args[i] = checkint32( name, i, args[i], 3 )
end
local pow = 1
local ret = 0
for b = 0, 31, 2 do
local c = s
for i = 1, nargs do
c = t[c][args[i] % 4]
args[i] = math.floor( args[i] / 4 )
end
ret = ret + c * pow
pow = pow * 4
end
return ret
end
function bit32.band( ... )
return comb( 'band', { ... }, select( '#', ... ), 3, logic_and )
end
function bit32.bor( ... )
return comb( 'bor', { ... }, select( '#', ... ), 0, logic_or )
end
function bit32.bxor( ... )
return comb( 'bxor', { ... }, select( '#', ... ), 0, logic_xor )
end
function bit32.btest( ... )
return comb( 'btest', { ... }, select( '#', ... ), 3, logic_and ) ~= 0
end
function bit32.extract( n, field, width )
n = checkint32( 'extract', 1, n, 2 )
field = checkint( 'extract', 2, field, 2 )
width = checkint( 'extract', 3, width or 1, 2 )
if field < 0 then
error( "bad argument #2 to 'extract' (field cannot be negative)", 2 )
end
if width <= 0 then
error( "bad argument #3 to 'extract' (width must be positive)", 2 )
end
if field + width > 32 then
error( 'trying to access non-existent bits', 2 )
end
return math.floor( n / 2^field ) % 2^width
end
function bit32.replace( n, v, field, width )
n = checkint32( 'replace', 1, n, 2 )
v = checkint32( 'replace', 2, v, 2 )
field = checkint( 'replace', 3, field, 2 )
width = checkint( 'replace', 4, width or 1, 2 )
if field < 0 then
error( "bad argument #3 to 'replace' (field cannot be negative)", 2 )
end
if width <= 0 then
error( "bad argument #4 to 'replace' (width must be positive)", 2 )
end
if field + width > 32 then
error( 'trying to access non-existent bits', 2 )
end
local f = 2^field
local w = 2^width
local fw = f * w
return ( n % f ) + ( v % w ) * f + math.floor( n / fw ) * fw
end
-- For the shifting functions, anything over 32 is the same as 32
-- and limiting to 32 prevents overflow/underflow
local function checkdisp( name, x )
x = checkint( name, 2, x, 3 )
return math.min( math.max( -32, x ), 32 )
end
function bit32.lshift( x, disp )
x = checkint32( 'lshift', 1, x, 2 )
disp = checkdisp( 'lshift', disp )
return math.floor( x * 2^disp ) % 0x100000000
end
function bit32.rshift( x, disp )
x = checkint32( 'rshift', 1, x, 2 )
disp = checkdisp( 'rshift', disp )
return math.floor( x / 2^disp ) % 0x100000000
end
function bit32.arshift( x, disp )
x = checkint32( 'arshift', 1, x, 2 )
disp = checkdisp( 'arshift', disp )
if disp <= 0 then
-- Non-positive displacement == left shift
-- (since exponent is non-negative, the multipication can never result
-- in a fractional part)
return ( x * 2^-disp ) % 0x100000000
elseif x < 0x80000000 then
-- High bit is 0 == right shift
-- (since exponent is positive, the division will never increase x)
return math.floor( x / 2^disp )
elseif disp > 31 then
-- Shifting off all bits
return 0xffffffff
else
-- 0x100000000 - 2 ^ ( 32 - disp ) creates a number with the high disp
-- bits set. So shift right then add that number.
return math.floor( x / 2^disp ) + ( 0x100000000 - 2 ^ ( 32 - disp ) )
end
end
-- For the rotation functions, disp works mod 32.
-- Note that lrotate( x, disp ) == rrotate( x, -disp ).
function bit32.lrotate( x, disp )
x = checkint32( 'lrotate', 1, x, 2 )
disp = checkint( 'lrotate', 2, disp, 2 ) % 32
local x = x * 2^disp
return ( x % 0x100000000 ) + math.floor( x / 0x100000000 )
end
function bit32.rrotate( x, disp )
x = checkint32( 'rrotate', 1, x, 2 )
disp = -checkint( 'rrotate', 2, disp, 2 ) % 32
local x = x * 2^disp
return ( x % 0x100000000 ) + math.floor( x / 0x100000000 )
end
return bit32

View File

@@ -0,0 +1,144 @@
--[[
compat_env - see README for details.
(c) 2012 David Manura. Licensed under Lua 5.1/5.2 terms (MIT license).
--]]
local M = {_TYPE='module', _NAME='compat_env', _VERSION='0.2.2.20120406'}
local function check_chunk_type(s, mode)
local nmode = mode or 'bt'
local is_binary = s and #s > 0 and s:byte(1) == 27
if is_binary and not nmode:match'b' then
return nil, ("attempt to load a binary chunk (mode is '%s')"):format(mode)
elseif not is_binary and not nmode:match't' then
return nil, ("attempt to load a text chunk (mode is '%s')"):format(mode)
end
return true
end
local IS_52_LOAD = pcall(load, '')
if IS_52_LOAD then
M.load = _G.load
M.loadfile = _G.loadfile
else
-- 5.2 style `load` implemented in 5.1
function M.load(ld, source, mode, env)
local f
if type(ld) == 'string' then
local s = ld
local ok, err = check_chunk_type(s, mode)
if not ok then return ok, err end
local err; f, err = loadstring(s, source)
if not f then return f, err end
elseif type(ld) == 'function' then
local ld2 = ld
if (mode or 'bt') ~= 'bt' then
local first = ld()
local ok, err = check_chunk_type(first, mode)
if not ok then return ok, err end
ld2 = function()
if first then
local chunk=first; first=nil; return chunk
else return ld() end
end
end
local err; f, err = load(ld2, source); if not f then return f, err end
else
error(("bad argument #1 to 'load' (function expected, got %s)")
:format(type(ld)), 2)
end
if env then setfenv(f, env) end
return f
end
-- 5.2 style `loadfile` implemented in 5.1
function M.loadfile(filename, mode, env)
if (mode or 'bt') ~= 'bt' then
local ioerr
local fh, err = io.open(filename, 'rb'); if not fh then return fh,err end
local function ld()
local chunk; chunk,ioerr = fh:read(4096); return chunk
end
local f, err = M.load(ld, filename and '@'..filename, mode, env)
fh:close()
if not f then return f, err end
if ioerr then return nil, ioerr end
return f
else
local f, err = loadfile(filename); if not f then return f, err end
if env then setfenv(f, env) end
return f
end
end
end
if _G.setfenv then -- Lua 5.1
M.setfenv = _G.setfenv
M.getfenv = _G.getfenv
else -- >= Lua 5.2
-- helper function for `getfenv`/`setfenv`
local function envlookup(f)
local name, val
local up = 0
local unknown
repeat
up=up+1; name, val = debug.getupvalue(f, up)
if name == '' then unknown = true end
until name == '_ENV' or name == nil
if name ~= '_ENV' then
up = nil
if unknown then
error("upvalues not readable in Lua 5.2 when debug info missing", 3)
end
end
return (name == '_ENV') and up, val, unknown
end
-- helper function for `getfenv`/`setfenv`
local function envhelper(f, name)
if type(f) == 'number' then
if f < 0 then
error(("bad argument #1 to '%s' (level must be non-negative)")
:format(name), 3)
elseif f < 1 then
error("thread environments unsupported in Lua 5.2", 3) --[*]
end
f = debug.getinfo(f+2, 'f').func
elseif type(f) ~= 'function' then
error(("bad argument #1 to '%s' (number expected, got %s)")
:format(type(name, f)), 2)
end
return f
end
-- [*] might simulate with table keyed by coroutine.running()
-- 5.1 style `setfenv` implemented in 5.2
function M.setfenv(f, t)
local f = envhelper(f, 'setfenv')
local up, val, unknown = envlookup(f)
if up then
debug.upvaluejoin(f, up, function() return up end, 1) --unique upval[*]
debug.setupvalue(f, up, t)
else
local what = debug.getinfo(f, 'S').what
if what ~= 'Lua' and what ~= 'main' then -- not Lua func
error("'setfenv' cannot change environment of given object", 2)
end -- else ignore no _ENV upvalue (warning: incompatible with 5.1)
end
return f -- invariant: original f ~= 0
end
-- [*] http://lua-users.org/lists/lua-l/2010-06/msg00313.html
-- 5.1 style `getfenv` implemented in 5.2
function M.getfenv(f)
if f == 0 or f == nil then return _G end -- simulated behavior
local f = envhelper(f, 'setfenv')
local up, val = envlookup(f)
if not up then return _G end -- simulated behavior [**]
return val
end
-- [**] possible reasons: no _ENV upvalue, C function
end
return M

View File

@@ -0,0 +1,71 @@
local libraryUtil = {}
function libraryUtil.checkType( name, argIdx, arg, expectType, nilOk )
if arg == nil and nilOk then
return
end
if type( arg ) ~= expectType then
local msg = string.format( "bad argument #%d to '%s' (%s expected, got %s)",
argIdx, name, expectType, type( arg )
)
error( msg, 3 )
end
end
function libraryUtil.checkTypeMulti( name, argIdx, arg, expectTypes )
local argType = type( arg )
for _, expectType in ipairs( expectTypes ) do
if argType == expectType then
return
end
end
local n = #expectTypes
local typeList
if n > 1 then
typeList = table.concat( expectTypes, ', ', 1, n - 1 ) .. ' or ' .. expectTypes[n]
else
typeList = expectTypes[1]
end
local msg = string.format( "bad argument #%d to '%s' (%s expected, got %s)",
argIdx,
name,
typeList,
type( arg )
)
error( msg, 3 )
end
function libraryUtil.checkTypeForIndex( index, value, expectType )
if type( value ) ~= expectType then
local msg = string.format( "value for index '%s' must be %s, %s given",
index, expectType, type( value )
)
error( msg, 3 )
end
end
function libraryUtil.checkTypeForNamedArg( name, argName, arg, expectType, nilOk )
if arg == nil and nilOk then
return
end
if type( arg ) ~= expectType then
local msg = string.format( "bad named argument %s to '%s' (%s expected, got %s)",
argName, name, expectType, type( arg )
)
error( msg, 3 )
end
end
function libraryUtil.makeCheckSelfFunction( libraryName, varName, selfObj, selfObjDesc )
return function ( self, method )
if self ~= selfObj then
error( string.format(
"%s: invalid %s. Did you call %s with a dot instead of a colon, i.e. " ..
"%s.%s() instead of %s:%s()?",
libraryName, selfObjDesc, method, varName, method, varName, method
), 3 )
end
end
end
return libraryUtil

View File

@@ -0,0 +1,264 @@
--[[---------------
LuaBit v0.4
-------------------
a bitwise operation lib for lua.
http://luaforge.net/projects/bit/
How to use:
-------------------
bit.bnot(n) -- bitwise not (~n)
bit.band(m, n) -- bitwise and (m & n)
bit.bor(m, n) -- bitwise or (m | n)
bit.bxor(m, n) -- bitwise xor (m ^ n)
bit.brshift(n, bits) -- right shift (n >> bits)
bit.blshift(n, bits) -- left shift (n << bits)
bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>)
Please note that bit.brshift and bit.blshift only support number within
32 bits.
2 utility functions are provided too:
bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence)
-- high bits first
bit.tonumb(bit_tbl) -- convert a bit table into a number
-------------------
Under the MIT license.
copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com)
2013-02-20: Brad Jorsch: Fix to not try messing with globals, doesn't work in Scribunto
--]]---------------
do
------------------------
-- bit lib implementions
local function check_int(n)
-- checking not float
if(n - math.floor(n) > 0) then
error("trying to use bitwise operation on non-integer!")
end
end
local function to_bits(n)
check_int(n)
if(n < 0) then
-- negative
return to_bits(bit.bnot(math.abs(n)) + 1)
end
-- to bits table
local tbl = {}
local cnt = 1
while (n > 0) do
local last = math.mod(n,2)
if(last == 1) then
tbl[cnt] = 1
else
tbl[cnt] = 0
end
n = (n-last)/2
cnt = cnt + 1
end
return tbl
end
local function tbl_to_number(tbl)
local n = table.getn(tbl)
local rslt = 0
local power = 1
for i = 1, n do
rslt = rslt + tbl[i]*power
power = power*2
end
return rslt
end
local function expand(tbl_m, tbl_n)
local big = {}
local small = {}
if(table.getn(tbl_m) > table.getn(tbl_n)) then
big = tbl_m
small = tbl_n
else
big = tbl_n
small = tbl_m
end
-- expand small
for i = table.getn(small) + 1, table.getn(big) do
small[i] = 0
end
end
local function bit_or(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 and tbl_n[i] == 0) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_and(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 or tbl_n[i] == 0) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_not(n)
local tbl = to_bits(n)
local size = math.max(table.getn(tbl), 32)
for i = 1, size do
if(tbl[i] == 1) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_xor(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i] ~= tbl_n[i]) then
tbl[i] = 1
else
tbl[i] = 0
end
end
--table.foreach(tbl, print)
return tbl_to_number(tbl)
end
local function bit_rshift(n, bits)
check_int(n)
local high_bit = 0
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
high_bit = 2147483648 -- 0x80000000
end
for i=1, bits do
n = n/2
n = bit_or(math.floor(n), high_bit)
end
return math.floor(n)
end
-- logic rightshift assures zero filling shift
local function bit_logic_rshift(n, bits)
check_int(n)
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
end
for i=1, bits do
n = n/2
end
return math.floor(n)
end
local function bit_lshift(n, bits)
check_int(n)
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
end
for i=1, bits do
n = n*2
end
return bit_and(n, 4294967295) -- 0xFFFFFFFF
end
local function bit_xor2(m, n)
local rhs = bit_or(bit_not(m), bit_not(n))
local lhs = bit_or(m, n)
local rslt = bit_and(lhs, rhs)
return rslt
end
--------------------
-- bit lib interface
local bit = {
-- bit operations
bnot = bit_not,
band = bit_and,
bor = bit_or,
bxor = bit_xor,
brshift = bit_rshift,
blshift = bit_lshift,
bxor2 = bit_xor2,
blogic_rshift = bit_logic_rshift,
-- utility func
tobits = to_bits,
tonumb = tbl_to_number,
}
return bit
end
--[[
for i = 1, 100 do
for j = 1, 100 do
if(bit.bxor(i, j) ~= bit.bxor2(i, j)) then
error("bit.xor failed.")
end
end
end
--]]

View File

@@ -0,0 +1,99 @@
--[[---------------
Hex v0.4
-------------------
Hex conversion lib for lua.
How to use:
hex.to_hex(n) -- convert a number to a hex string
hex.to_dec(hex) -- convert a hex string(prefix with '0x' or '0X') to number
Part of LuaBit(http://luaforge.net/projects/bit/).
Under the MIT license.
copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com)
2013-02-20: Brad Jorsch: Fix to not try messing with globals, doesn't work in Scribunto
--]]---------------
local bit = require 'bit'
do
local function to_hex(n)
if(type(n) ~= "number") then
error("non-number type passed in.")
end
-- checking not float
if(n - math.floor(n) > 0) then
error("trying to apply bitwise operation on non-integer!")
end
if(n < 0) then
-- negative
n = bit.tobits(bit.bnot(math.abs(n)) + 1)
n = bit.tonumb(n)
end
hex_tbl = {'A', 'B', 'C', 'D', 'E', 'F'}
hex_str = ""
while(n ~= 0) do
last = math.mod(n, 16)
if(last < 10) then
hex_str = tostring(last) .. hex_str
else
hex_str = hex_tbl[last-10+1] .. hex_str
end
n = math.floor(n/16)
end
if(hex_str == "") then
hex_str = "0"
end
return "0x" .. hex_str
end
local function to_dec(hex)
if(type(hex) ~= "string") then
error("non-string type passed in.")
end
head = string.sub(hex, 1, 2)
if( head ~= "0x" and head ~= "0X") then
error("wrong hex format, should lead by 0x or 0X.")
end
v = tonumber(string.sub(hex, 3), 16)
return v;
end
--------------------
-- hex lib interface
local hex = {
to_dec = to_dec,
to_hex = to_hex,
}
return hex
end
--[[
-- test
d = 4341688
h = to_hex(d)
print(h)
print(to_dec(h))
for i = 1, 100000 do
h = hex.to_hex(i)
d = hex.to_dec(h)
if(d ~= i) then
error("failed " .. i .. ", " .. h)
end
end
--]]

View File

@@ -0,0 +1,143 @@
LuaBit
------
LuaBit is a bitwise operation lib completely written in Lua. It's
written in the belief that Lua is self-contained.
The supported operations are: not, and, or, xor, right shift, logic
right shift and left shift.
Several utilities are designed to leverage the power of bit operation:
1. hex: a dec <-> hex number converter
2. utf8: convert utf8 string to ucs2
3. noki: convert nokia pc suite backuped SMS file to .txt
Under the MIT license.
Visit http://luaforge.net/projects/bit/ to get latest version.
Status
------
Now LuaBit is in v0.4.
Release date: Mar 18, 2007
Content
-------
3 files are there for LuaBit:
1) bit.lua
is the bitwise operation lib, all operations are implemented here.
2) hex.lua
is a helper lib for ease of using hex numbers with bitwise
operation.
3) noki.lua
a utility(based on bit and hex) to convert Nokia PC Suite backuped
SMS to a unicode .txt file, which is more accessible than the
original .nfb or .nfc file.
4) utf8.lua
convert utf8 string to ucs2 string
How to use
----------
Bit
---
Just require 'bit' in your project and the bit lib will be
available:
bit.bnot(n) -- bitwise not (~n)
bit.band(m, n) -- bitwise and (m & n)
bit.bor(m, n) -- bitwise or (m | n)
bit.bxor(m, n) -- bitwise xor (m ^ n)
bit.brshift(n, bits) -- right shift (n >> bits)
bit.blshift(n, bits) -- left shift (n << bits)
bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>)
Please note that bit.brshift and bit.blshift only support number within
32 bits.
2 utility functions are provided too:
bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence)
-- high bits first
bit.tonumb(bit_tbl) -- convert a bit table into a number
Hex
---
For ease of using hex numbers, a utility hex lib is also included in
LuaBit. You can require 'hex' to use them:
hex.to_hex(n) -- convert a number to a hex string
hex.to_dec(hex) -- convert a hex string(prefix with '0x' or '0X') to number
With hex, you can write code like:
bit.band(258, hex.to_dec('0xFF'))
to get the lower 8 bits of 258, that's 2.
Noki
----
require 'noki', to save your sms to .txt file:
noki.save_sms('nokia.nfb', 'sms.txt')
and you can view the output sms.txt in notepad or other editor which
support unicode.
Utf8
----
require 'utf8', to convert a utf8 string:
ucs2_string = utf8.utf_to_uni(utf8_string)
History
-------
v0.4
* utf8 to ucs2 converter(utf8.lua).
* clean up for compatible with Lua5.1 and 5.0.
* add 'How to use' section for bit.lua and hex.lua.
v0.3
* noki added as an application of bit.
* typo correction.
v0.2
* add logic right shift(>>>) support: bit.blogic_rshift.
* add 2 utility functions: bit.tobits and bit.tonumb.
* update hex.to_hex(in hex.lua) to support negative number.
v0.1
LuaBit is written when I do my own game project(Fio at http://fio.edithis.info).
When loading resources, I have to do some bit operation. And I do not
like the embedded way of bit operation. So I decide to implement those
ops in lua. And that's LuaBit. It's not as fast as the embedded one, but
it works. And Lua is self-contained :-)
To-Do List
---------
v0.1
It'll be useful if LuaBit support those bitwise op like:
bit.band(258, '0xFF')
ease to type and use. This will be supported in next release.
v0.2
I decide to delay this feature to later version for it'll mess up the
interface of LuaBit.
v0.3
May more utility functions add to Noki - phonebook might be a nice candidate.
v0.4
There's no UCS2 -> UTF8 convertion now, this feature may add in next release
or when the project need.
Noki'll be be exluded from LuaBit in next release; I decide to let Noki grow
into a powerful tool to support more Nokia PC Suite backup format(.nfb,
.nfc and .nbu).
Trial Noki demo at http://nokisms.googlepages.com/(in Chinese)
Known issues
------------
LuaBit doesn't play very well with negative number. The return value of the
bitwise operations might change to positive when applied on negative numbers
though the bit sequence is correct. So if you want do some arithmetic with
the result of bit operation, be careful.
Feedback
--------
Please send your comments, bugs, patches or change request to
hanzhao(abrash_han@hotmail.com).

View File

@@ -0,0 +1,422 @@
--[[
A module for building complex HTML from Lua using a
fluent interface.
Originally written on the English Wikipedia by
Toohool and Mr. Stradivarius.
Code released under the GPL v2+ as per:
https://en.wikipedia.org/w/index.php?diff=next&oldid=581399786
https://en.wikipedia.org/w/index.php?diff=next&oldid=581403025
@license GNU GPL v2+
@author Marius Hoch < hoo@online.de >
]]
local HtmlBuilder = {}
local util = require 'libraryUtil'
local checkType = util.checkType
local checkTypeMulti = util.checkTypeMulti
local metatable = {}
local methodtable = {}
local selfClosingTags = {
area = true,
base = true,
br = true,
col = true,
command = true,
embed = true,
hr = true,
img = true,
input = true,
keygen = true,
link = true,
meta = true,
param = true,
source = true,
track = true,
wbr = true,
}
local htmlencodeMap = {
['>'] = '&gt;',
['<'] = '&lt;',
['&'] = '&amp;',
['"'] = '&quot;',
}
metatable.__index = methodtable
metatable.__tostring = function( t )
local ret = {}
t:_build( ret )
return table.concat( ret )
end
-- Get an attribute table (name, value) and its index
--
-- @param name
local function getAttr( t, name )
for i, attr in ipairs( t.attributes ) do
if attr.name == name then
return attr, i
end
end
end
-- Is this a valid attribute name?
--
-- @param s
local function isValidAttributeName( s )
-- Good estimate: http://www.w3.org/TR/2000/REC-xml-20001006#NT-Name
return s:match( '^[a-zA-Z_:][a-zA-Z0-9_.:-]*$' )
end
-- Is this a valid tag name?
--
-- @param s
local function isValidTag( s )
return s:match( '^[a-zA-Z0-9]+$' )
end
-- Escape a value, for use in HTML
--
-- @param s
local function htmlEncode( s )
-- The parentheses ensure that there is only one return value
return ( string.gsub( s, '[<>&"]', htmlencodeMap ) )
end
local function cssEncode( s )
-- XXX: I'm not sure this character set is complete.
-- bug #68011: allow delete character (\127)
return mw.ustring.gsub( s, '[^\32-\57\60-\127]', function ( m )
return string.format( '\\%X ', mw.ustring.codepoint( m ) )
end )
end
-- Create a builder object. This is a separate function so that we can show the
-- correct error levels in both HtmlBuilder.create and metatable.tag.
--
-- @param tagName
-- @param args
local function createBuilder( tagName, args )
if tagName ~= nil and tagName ~= '' and not isValidTag( tagName ) then
error( string.format( "invalid tag name '%s'", tagName ), 3 )
end
args = args or {}
local builder = {}
setmetatable( builder, metatable )
builder.nodes = {}
builder.attributes = {}
builder.styles = {}
if tagName ~= '' then
builder.tagName = tagName
end
builder.parent = args.parent
builder.selfClosing = selfClosingTags[tagName] or args.selfClosing or false
return builder
end
-- Append a builder to the current node. This is separate from methodtable.node
-- so that we can show the correct error level in both methodtable.node and
-- methodtable.wikitext.
--
-- @param builder
local function appendBuilder( t, builder )
if t.selfClosing then
error( "self-closing tags can't have child nodes", 3 )
end
if builder then
table.insert( t.nodes, builder )
end
return t
end
methodtable._build = function( t, ret )
if t.tagName then
table.insert( ret, '<' .. t.tagName )
for i, attr in ipairs( t.attributes ) do
table.insert(
ret,
-- Note: Attribute names have already been validated
' ' .. attr.name .. '="' .. htmlEncode( attr.val ) .. '"'
)
end
if #t.styles > 0 then
table.insert( ret, ' style="' )
local css = {}
for i, prop in ipairs( t.styles ) do
if type( prop ) ~= 'table' then -- added with cssText()
table.insert( css, htmlEncode( prop ) )
else -- added with css()
table.insert(
css,
htmlEncode( cssEncode( prop.name ) .. ':' .. cssEncode( prop.val ) )
)
end
end
table.insert( ret, table.concat( css, ';' ) )
table.insert( ret, '"' )
end
if t.selfClosing then
table.insert( ret, ' />' )
return
end
table.insert( ret, '>' )
end
for i, node in ipairs( t.nodes ) do
if node then
if type( node ) == 'table' then
node:_build( ret )
else
table.insert( ret, tostring( node ) )
end
end
end
if t.tagName then
table.insert( ret, '</' .. t.tagName .. '>' )
end
end
-- Append a builder to the current node
--
-- @param builder
methodtable.node = function( t, builder )
return appendBuilder( t, builder )
end
-- Appends some markup to the node. This will be treated as wikitext.
methodtable.wikitext = function( t, ... )
local vals = {...}
for i = 1, #vals do
checkTypeMulti( 'wikitext', i, vals[i], { 'string', 'number' } )
appendBuilder( t, vals[i] )
end
return t
end
-- Appends a newline character to the node.
methodtable.newline = function( t )
t:wikitext( '\n' )
return t
end
-- Appends a new child node to the builder, and returns an HtmlBuilder instance
-- representing that new node.
--
-- @param tagName
-- @param args
methodtable.tag = function( t, tagName, args )
checkType( 'tag', 1, tagName, 'string' )
checkType( 'tag', 2, args, 'table', true )
args = args or {}
args.parent = t
local builder = createBuilder( tagName, args )
t:node( builder )
return builder
end
-- Get the value of an html attribute
--
-- @param name
methodtable.getAttr = function( t, name )
checkType( 'getAttr', 1, name, 'string' )
local attr = getAttr( t, name )
if attr then
return attr.val
end
return nil
end
-- Set an HTML attribute on the node.
--
-- @param name Attribute to set, alternative table of name-value pairs
-- @param val Value of the attribute. Nil causes the attribute to be unset
methodtable.attr = function( t, name, val )
if type( name ) == 'table' then
if val ~= nil then
error(
"bad argument #2 to 'attr' " ..
'(if argument #1 is a table, argument #2 must be left empty)',
2
)
end
local callForTable = function()
for attrName, attrValue in pairs( name ) do
t:attr( attrName, attrValue )
end
end
if not pcall( callForTable ) then
error(
"bad argument #1 to 'attr' " ..
'(table keys must be strings, and values must be strings or numbers)',
2
)
end
return t
end
checkType( 'attr', 1, name, 'string' )
checkTypeMulti( 'attr', 2, val, { 'string', 'number', 'nil' } )
-- if caller sets the style attribute explicitly, then replace all styles
-- previously added with css() and cssText()
if name == 'style' then
t.styles = { val }
return t
end
if not isValidAttributeName( name ) then
error( string.format(
"bad argument #1 to 'attr' (invalid attribute name '%s')",
name
), 2 )
end
local attr, i = getAttr( t, name )
if attr then
if val ~= nil then
attr.val = val
else
table.remove( t.attributes, i )
end
elseif val ~= nil then
table.insert( t.attributes, { name = name, val = val } )
end
return t
end
-- Adds a class name to the node's class attribute. Spaces will be
-- automatically added to delimit each added class name.
--
-- @param class
methodtable.addClass = function( t, class )
checkTypeMulti( 'addClass', 1, class, { 'string', 'number', 'nil' } )
if class == nil then
return t
end
local attr = getAttr( t, 'class' )
if attr then
attr.val = attr.val .. ' ' .. class
else
t:attr( 'class', class )
end
return t
end
-- Set a CSS property to be added to the node's style attribute.
--
-- @param name CSS attribute to set, alternative table of name-value pairs
-- @param val The value to set. Nil causes it to be unset
methodtable.css = function( t, name, val )
if type( name ) == 'table' then
if val ~= nil then
error(
"bad argument #2 to 'css' " ..
'(if argument #1 is a table, argument #2 must be left empty)',
2
)
end
local callForTable = function()
for attrName, attrValue in pairs( name ) do
t:css( attrName, attrValue )
end
end
if not pcall( callForTable ) then
error(
"bad argument #1 to 'css' " ..
'(table keys and values must be strings or numbers)',
2
)
end
return t
end
checkTypeMulti( 'css', 1, name, { 'string', 'number' } )
checkTypeMulti( 'css', 2, val, { 'string', 'number', 'nil' } )
for i, prop in ipairs( t.styles ) do
if prop.name == name then
if val ~= nil then
prop.val = val
else
table.remove( t.styles, i )
end
return t
end
end
if val ~= nil then
table.insert( t.styles, { name = name, val = val } )
end
return t
end
-- Add some raw CSS to the node's style attribute. This is typically used
-- when a template allows some CSS to be passed in as a parameter
--
-- @param css
methodtable.cssText = function( t, css )
checkTypeMulti( 'cssText', 1, css, { 'string', 'number', 'nil' } )
if css ~= nil then
table.insert( t.styles, css )
end
return t
end
-- Returns the parent node under which the current node was created. Like
-- jQuery.end, this is a convenience function to allow the construction of
-- several child nodes to be chained together into a single statement.
methodtable.done = function( t )
return t.parent or t
end
-- Like .done(), but traverses all the way to the root node of the tree and
-- returns it.
methodtable.allDone = function( t )
while t.parent do
t = t.parent
end
return t
end
-- Create a new instance
--
-- @param tagName
-- @param args
function HtmlBuilder.create( tagName, args )
checkType( 'mw.html.create', 1, tagName, 'string', true )
checkType( 'mw.html.create', 2, args, 'table', true )
return createBuilder( tagName, args )
end
mw_interface = nil
-- Register this library in the "mw" global
mw = mw or {}
mw.html = HtmlBuilder
package.loaded['mw.html'] = HtmlBuilder
return HtmlBuilder

View File

@@ -0,0 +1,197 @@
local language = {}
local php
local util = require 'libraryUtil'
local init_site_for_lang_needed = true -- xowa
function language.setupInterface()
-- Boilerplate
language.setupInterface = nil
php = mw_interface
mw_interface = nil
-- Register this library in the "mw" global
mw = mw or {}
mw.language = language
mw.getContentLanguage = language.getContentLanguage
mw.getLanguage = mw.language.new
local lang = mw.getContentLanguage();
-- Extend ustring
if mw.ustring then
mw.ustring.upper = function ( s )
return lang:uc( s )
end
mw.ustring.lower = function ( s )
return lang:lc( s )
end
string.uupper = mw.ustring.upper
string.ulower = mw.ustring.lower
end
package.loaded['mw.language'] = language
end
-- xowa:bgn
function language.notify_lang_changed()
init_site_for_lang_needed = true
end
-- xowa:end
function language.isSupportedLanguage( code )
return php.isSupportedLanguage( code )
end
function language.isKnownLanguageTag( code )
return php.isKnownLanguageTag( code )
end
function language.isValidCode( code )
return php.isValidCode( code )
end
function language.isValidBuiltInCode( code )
return php.isValidBuiltInCode( code )
end
function language.fetchLanguageName( code, inLanguage )
return php.fetchLanguageName( code, inLanguage )
end
function language.fetchLanguageNames( inLanguage, include )
return php.fetchLanguageNames( inLanguage, include )
end
function language.getFallbacksFor( code )
return php.getFallbacksFor( code )
end
function language.new( code )
if code == nil then
error( "too few arguments to mw.language.new()", 2 )
end
local lang = { code = code }
local checkSelf = util.makeCheckSelfFunction( 'mw.language', 'lang', lang, 'language object' )
local wrappers = {
lcfirst = 1,
ucfirst = 1,
lc = 1,
uc = 1,
caseFold = 1,
formatNum = 1,
formatDate = 1,
formatDuration = 1,
getDurationIntervals = 1,
convertPlural = 2,
convertGrammar = 2,
gender = 2,
}
for name, numArgs in pairs( wrappers ) do
lang[name] = function ( self, ... )
checkSelf( self, name )
if select( '#', ... ) < numArgs then
error( "too few arguments to mw.language:" .. name, 2 )
end
return php[name]( self.code, ... )
end
end
-- This one could use caching
function lang:isRTL()
checkSelf( self, 'isRTL' )
local rtl = php.isRTL( self.code )
self.isRTL = function ()
return rtl
end
return rtl
end
-- Fix semantics
function lang:parseFormattedNumber( ... )
checkSelf( self, 'parseFormattedNumber' )
if select( '#', ... ) < 1 then
error( "too few arguments to mw.language:parseFormattedNumber", 2 )
end
return tonumber( php.parseFormattedNumber( self.code, ... ) )
end
-- Alias
lang.plural = lang.convertPlural
-- Parser function compat
function lang:grammar( case, word )
checkSelf( self, name )
return self:convertGrammar( word, case )
end
-- Other functions
function lang:getCode()
checkSelf( self, 'getCode' )
return self.code
end
function lang:getDir()
checkSelf( self, 'getDir' )
return self:isRTL() and 'rtl' or 'ltr'
end
function lang:getDirMark( opposite )
checkSelf( self, 'getDirMark' )
local b = self:isRTL()
if opposite then
b = not b
end
return b and '\226\128\143' or '\226\128\142'
end
function lang:getDirMarkEntity( opposite )
checkSelf( self, 'getDirMarkEntity' )
local b = self:isRTL()
if opposite then
b = not b
end
return b and '&rlm;' or '&lrm;'
end
function lang:getArrow( direction )
checkSelf( self, 'getArrow' )
direction = direction or 'forwards'
util.checkType( 'getArrow', 1, direction, 'string' )
if direction == 'forwards' then
return self:isRTL() and '' or ''
elseif direction == 'backwards' then
return self:isRTL() and '' or ''
elseif direction == 'left' then
return ''
elseif direction == 'right' then
return ''
elseif direction == 'up' then
return ''
elseif direction == 'down' then
return ''
end
end
function lang:getFallbackLanguages()
checkSelf( self, 'getFallbackLanguages' )
return language.getFallbacksFor( self.code )
end
return lang
end
local contLangCode
function language.getContentLanguage()
if init_site_for_lang_needed then -- xowa; was contLangCode == nil
contLangCode = php.getContLangCode()
init_site_for_lang_needed = false -- xowa
end
return language.new( contLangCode )
end
return language

View File

@@ -0,0 +1,816 @@
mw = mw or {}
local packageCache
local packageModuleFunc
local php
local allowEnvFuncs = false
local logBuffer = ''
local currentFrame
local loadedData = {}
local executeFunctionDepth = 0
----[[
local dump_table_count = 0;
function mw.dump_table(o)
if dump_table_count == 10 then
return "overflow";
end
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
dump_table_count = dump_table_count + 1;
s = s .. '['..k..'] = ' .. mw.dump_table(v) .. ','
dump_table_count = dump_table_count - 1;
end
return s .. '} '
else
return tostring(o)
end
end
local diagnostic_preprocess_nested_data = ''
function mw.diagnostic_preprocess_nested(o)
if o == 'clear' then
diagnostic_preprocess_nested_data = ''
elseif o == 'exec' then
diagnostic_preprocess_nested_data = diagnostic_preprocess_nested_data .. 'once';
end
return diagnostic_preprocess_nested_data
end
--]]
--- Put an isolation-friendly package module into the specified environment
-- table. The package module will have an empty cache, because caching of
-- module functions from other cloned environments would break module isolation.
--
-- @param env The cloned environment
local function makePackageModule( env )
-- Remove loaders from env, we don't want it inheriting our loadPackage.
if env.package then
env.package.loaders = nil
end
-- Create the package globals in the given environment
setfenv( packageModuleFunc, env )()
-- Make a loader function
local function loadPackage( modName )
local init
if packageCache[modName] == 'missing' then
return nil
elseif packageCache[modName] == nil then
local lib = php.loadPHPLibrary( modName )
if lib ~= nil then
init = function ()
return mw.clone( lib )
end
else
init = php.loadPackage( modName )
if init == nil then
packageCache[modName] = 'missing'
return nil
end
end
packageCache[modName] = init
else
init = packageCache[modName]
end
setfenv( init, env )
return init
end
table.insert( env.package.loaders, loadPackage )
end
--- Set up the base environment. The PHP host calls this function after any
-- necessary host-side initialisation has been done.
function mw.setupInterface( options )
-- Don't allow any more calls
mw.setupInterface = nil
-- Don't allow getmetatable() on a non-table, since if you can get the metatable,
-- you can set values in it, breaking isolation
local old_getmetatable = getmetatable
function getmetatable(obj)
if type(obj) == 'table' then
return old_getmetatable(obj)
else
return nil
end
end
if options.allowEnvFuncs then
allowEnvFuncs = true
end
-- Store the interface table
--
-- mw_interface.loadPackage() returns function values with their environment
-- set to the base environment, which would violate module isolation if they
-- were run from a cloned environment. We can only allow access to
-- mw_interface.loadPackage via our environment-setting wrapper.
--
php = mw_interface
mw_interface = nil
packageModuleFunc = php.loadPackage( 'package' )
makePackageModule( _G )
package.loaded.mw = mw
packageCache = {}
end
--- Create a table like the one os.date() returns, but with a metatable that sets TTLs as the values are looked at.
local function wrapDateTable( now )
return setmetatable( {}, {
__index = function( t, k )
if k == 'sec' then
php.setTTL( 1 )
elseif k == 'min' then
php.setTTL( 60 - now.sec )
elseif k == 'hour' then
php.setTTL( 3600 - now.min * 60 - now.sec )
elseif now[k] ~= nil then
php.setTTL( 86400 - now.hour * 3600 - now.min * 60 - now.sec )
end
t[k] = now[k]
return now[k]
end
} )
end
--- Wrappers for os.date() and os.time() that set the TTL of the output, if necessary
local function ttlDate( format, time )
if time == nil and ( format == nil or type( format ) == 'string' ) then
local now = os.date( format and format:sub( 1, 1 ) == '!' and '!*t' or '*t' )
if format == '!*t' or format == '*t' then
return wrapDateTable( now )
end
local cleanedFormat = format and format:gsub( '%%%%', '' )
if not format or cleanedFormat:find( '%%[EO]?[crsSTX+]' ) then
php.setTTL( 1 ) -- second
elseif cleanedFormat:find( '%%[EO]?[MR]' ) then
php.setTTL( 60 - now.sec ) -- minute
elseif cleanedFormat:find( '%%[EO]?[HIkl]' ) then
php.setTTL( 3600 - now.min * 60 - now.sec ) -- hour
elseif cleanedFormat:find( '%%[EO]?[pP]' ) then
php.setTTL( 43200 - ( now.hour % 12 ) * 3600 - now.min * 60 - now.sec ) -- am/pm
else
-- It's not worth the complexity to figure out the exact TTL of larger units than days.
-- If they haven't used anything shorter than days, then just set the TTL to expire at
-- the end of today.
php.setTTL( 86400 - now.hour * 3600 - now.min * 60 - now.sec )
end
end
return os.date( format, time )
end
local function ttlTime( t )
if t == nil then
php.setTTL( 1 )
end
return os.time( t )
end
local function newFrame( frameId, ... )
if not php.frameExists( frameId ) then
return nil
end
local frame = {}
local parentFrameIds = { ... }
local argCache = {}
local argNames
local args_mt = {}
local function checkSelf( self, method )
if self ~= frame then
error( "frame:" .. method .. ": invalid frame object. " ..
"Did you call " .. method .. " with a dot instead of a colon, i.e. " ..
"frame." .. method .. "() instead of frame:" .. method .. "()?",
3 )
end
end
-- Getter for args
local function getExpandedArgument( dummy, name )
name = tostring( name )
if argCache[name] == nil then
local arg = php.getExpandedArgument( frameId, name )
if arg == nil then
argCache[name] = false
else
argCache[name] = arg
end
end
if argCache[name] == false then
return nil
else
return argCache[name]
end
end
args_mt.__index = getExpandedArgument
-- pairs handler for args
args_mt.__pairs = function ()
if not argNames then
local arguments = php.getAllExpandedArguments( frameId )
argNames = {}
for name, value in pairs( arguments ) do
table.insert( argNames, name )
argCache[name] = value
end
end
local index = 0
return function ()
index = index + 1
if argNames[index] then
return argNames[index], argCache[argNames[index]]
end
end
end
-- ipairs 'next' function for args
local function argsInext( dummy, i )
local value = getExpandedArgument( dummy, i + 1 )
if value then
return i + 1, value
end
end
args_mt.__ipairs = function () return argsInext, nil, 0 end
frame.args = {}
setmetatable( frame.args, args_mt )
local function newCallbackParserValue( callback )
local value = {}
local cache
function value:expand()
if not cache then
cache = callback()
end
return cache
end
return value
end
function frame:getArgument( opt )
checkSelf( self, 'getArgument' )
local name
if type( opt ) == 'table' then
name = opt.name
else
name = opt
end
return newCallbackParserValue(
function ()
return getExpandedArgument( nil, name )
end
)
end
function frame:getParent()
checkSelf( self, 'getParent' )
return newFrame( unpack( parentFrameIds ) )
end
local function checkArgs( name, args )
local ret = {}
for k, v in pairs( args ) do
local tp = type( k )
if tp ~= 'string' and tp ~= 'number' then
error( name .. ": arg keys must be strings or numbers, " .. tp .. " given", 3 )
end
tp = type( v )
if tp == 'boolean' then
ret[k] = v and '1' or ''
elseif tp == 'string' or tp == 'number' then
ret[k] = tostring( v )
else
error( name .. ": invalid type " .. tp .. " for arg '" .. k .. "'", 3 )
end
end
return ret
end
function frame:newChild( opt )
checkSelf( self, 'newChild' )
if type( opt ) ~= 'table' then
error( "frame:newChild: the first parameter must be a table", 2 )
end
local title, args
if opt.title == nil then
title = false
else
title = tostring( opt.title )
end
if opt.args == nil then
args = {}
elseif type( opt.args ) ~= 'table' then
error( "frame:newChild: args must be a table", 2 )
else
args = checkArgs( 'frame:newChild', opt.args )
end
local newFrameId = php.newChildFrame( frameId, title, args )
return newFrame( newFrameId, frameId, unpack( parentFrameIds ) )
end
function frame:expandTemplate( opt )
checkSelf( self, 'expandTemplate' )
local title
if type( opt ) ~= 'table' then
error( "frame:expandTemplate: the first parameter must be a table" )
end
if opt.title == nil then
error( "frame:expandTemplate: a title is required" )
else
if type( opt.title ) == 'table' and opt.title.namespace == 0 then
title = ':' .. tostring( opt.title )
else
title = tostring( opt.title )
end
end
local args
if opt.args == nil then
args = {}
elseif type( opt.args ) ~= 'table' then
error( "frame:expandTemplate: args must be a table" )
else
args = checkArgs( 'frame:expandTemplate', opt.args )
end
return php.expandTemplate( frameId, title, args )
end
function frame:callParserFunction( name, args, ... )
checkSelf( self, 'callParserFunction' )
if type( name ) == 'table' then
name, args = name.name, name.args
if type( args ) ~= 'table' then
args = { args }
end
elseif type( args ) ~= 'table' then
args = { args, ... }
end
if name == nil then
error( "frame:callParserFunction: a function name is required", 2 )
elseif type( name ) == 'string' or type( name ) == 'number' then
name = tostring( name )
else
error( "frame:callParserFunction: function name must be a string or number", 2 )
end
args = checkArgs( 'frame:callParserFunction', args )
return php.callParserFunction( frameId, name, args )
end
function frame:extensionTag( name, content, args )
checkSelf( self, 'extensionTag' )
if type( name ) == 'table' then
name, content, args = name.name, name.content, name.args
end
if name == nil then
error( "frame:extensionTag: a function name is required", 2 )
elseif type( name ) == 'string' or type( name ) == 'number' then
name = tostring( name )
else
error( "frame:extensionTag: tag name must be a string or number", 2 )
end
if content == nil then
content = ''
elseif type( content ) == 'string' or type( content ) == 'number' then
content = tostring( content )
else
error( "frame:extensionTag: content must be a string or number", 2 )
end
if args == nil then
args = { content }
elseif type( args ) == 'string' or type( args ) == 'number' then
args = { content, args }
elseif type( args ) == 'table' then
args = checkArgs( 'frame:extensionTag', args )
table.insert( args, 1, content )
else
error( "frame:extensionTag: args must be a string, number, or table", 2 )
end
return php.callParserFunction( frameId, '#tag:' .. name, args )
end
function frame:preprocess( opt )
checkSelf( self, 'preprocess' )
local text
if type( opt ) == 'table' then
text = opt.text
else
text = opt
end
text = tostring( text )
return php.preprocess( frameId, text )
end
function frame:newParserValue( opt )
checkSelf( self, 'newParserValue' )
local text
if type( opt ) == 'table' then
text = opt.text
else
text = opt
end
return newCallbackParserValue(
function ()
return self:preprocess( text )
end
)
end
function frame:newTemplateParserValue( opt )
checkSelf( self, 'newTemplateParserValue' )
if type( opt ) ~= 'table' then
error( "frame:newTemplateParserValue: the first parameter must be a table" )
end
if opt.title == nil then
error( "frame:newTemplateParserValue: a title is required" )
end
return newCallbackParserValue(
function ()
return self:expandTemplate( opt )
end
)
end
function frame:getTitle()
checkSelf( self, 'getTitle' )
return php.getFrameTitle( frameId )
end
-- For backwards compat
function frame:argumentPairs()
checkSelf( self, 'argumentPairs' )
return pairs( self.args )
end
return frame
end
--- Set up a cloned environment for execution of a module chunk, then execute
-- the module in that environment. This is called by the host to implement
-- {{#invoke}}.
--
-- @param chunk The module chunk
-- @param name The name of the function to be returned. Nil or false causes the entire export table to be returned
-- @return boolean Whether the requested value was able to be returned
-- @return table|function|string The requested value, or if that was unable to be returned, the type of the value returned by the module
function mw.executeModule( chunk, name )
local env = mw.clone( _G )
makePackageModule( env )
-- These are unsafe
env.mw.makeProtectedEnvFuncs = nil
env.mw.executeModule = nil
if name ~= false then -- console sets name to false when evaluating its code and nil when evaluating a module's
env.mw.getLogBuffer = nil
env.mw.clearLogBuffer = nil
end
if allowEnvFuncs then
env.setfenv, env.getfenv = mw.makeProtectedEnvFuncs( {[_G] = true}, {} )
else
env.setfenv = nil
env.getfenv = nil
end
env.os.date = ttlDate
env.os.time = ttlTime
setfenv( chunk, env )
local oldFrame = currentFrame
if not currentFrame then
currentFrame = newFrame( 'current', 'parent' )
end
local res = chunk()
currentFrame = oldFrame
if not name then -- catch console whether it's evaluating its own code or a module's
return true, res
end
if type(res) ~= 'table' then
return false, type(res)
end
return true, res[name]
end
function mw.executeFunction( chunk )
local frame = newFrame( 'current', 'parent' )
local oldFrame = currentFrame
if executeFunctionDepth == 0 then
-- math.random is defined as using C's rand(), and C's rand() uses 1 as
-- a seed if not explicitly seeded. So reseed with 1 for each top-level
-- #invoke to avoid people passing state via the RNG.
math.randomseed( 1 )
end
executeFunctionDepth = executeFunctionDepth + 1
currentFrame = frame
local results = { chunk( frame ) }
currentFrame = oldFrame
local stringResults = {}
for i, result in ipairs( results ) do
stringResults[i] = tostring( result )
end
executeFunctionDepth = executeFunctionDepth - 1
return table.concat( stringResults )
end
function mw.allToString( ... )
local t = { ... }
for i = 1, select( '#', ... ) do
t[i] = tostring( t[i] )
end
return table.concat( t, '\t' )
end
function mw.log( ... )
logBuffer = logBuffer .. mw.allToString( ... ) .. '\n'
end
function mw.dumpObject( object )
local doneTable = {}
local doneObj = {}
local ct = {}
local function sorter( a, b )
local ta, tb = type( a ), type( b )
if ta ~= tb then
return ta < tb
end
if ta == 'string' or ta == 'number' then
return a < b
end
if ta == 'boolean' then
return tostring( a ) < tostring( b )
end
return false -- Incomparable
end
local function _dumpObject( object, indent, expandTable )
local tp = type( object )
if tp == 'number' or tp == 'nil' or tp == 'boolean' then
return tostring( object )
elseif tp == 'string' then
return string.format( "%q", object )
elseif tp == 'table' then
if not doneObj[object] then
local s = tostring( object )
if s == 'table' then
ct[tp] = ( ct[tp] or 0 ) + 1
doneObj[object] = 'table#' .. ct[tp]
else
doneObj[object] = s
doneTable[object] = true
end
end
if doneTable[object] or not expandTable then
return doneObj[object]
end
doneTable[object] = true
local ret = { doneObj[object], ' {\n' }
local mt = getmetatable( object )
if mt then
ret[#ret + 1] = string.rep( " ", indent + 2 )
ret[#ret + 1] = 'metatable = '
ret[#ret + 1] = _dumpObject( mt, indent + 2, false )
ret[#ret + 1] = "\n"
end
local doneKeys = {}
for key, value in ipairs( object ) do
doneKeys[key] = true
ret[#ret + 1] = string.rep( " ", indent + 2 )
ret[#ret + 1] = _dumpObject( value, indent + 2, true )
ret[#ret + 1] = ',\n'
end
local keys = {}
for key in pairs( object ) do
if not doneKeys[key] then
keys[#keys + 1] = key
end
end
table.sort( keys, sorter )
for i = 1, #keys do
local key = keys[i]
ret[#ret + 1] = string.rep( " ", indent + 2 )
ret[#ret + 1] = '['
ret[#ret + 1] = _dumpObject( key, indent + 3, false )
ret[#ret + 1] = '] = '
ret[#ret + 1] = _dumpObject( object[key], indent + 2, true )
ret[#ret + 1] = ",\n"
end
ret[#ret + 1] = string.rep( " ", indent )
ret[#ret + 1] = '}'
return table.concat( ret )
else
if not doneObj[object] then
ct[tp] = ( ct[tp] or 0 ) + 1
doneObj[object] = tostring( object ) .. '#' .. ct[tp]
end
return doneObj[object]
end
end
return _dumpObject( object, 0, true )
end
function mw.logObject( object, prefix )
if prefix and prefix ~= '' then
logBuffer = logBuffer .. prefix .. ' = '
end
logBuffer = logBuffer .. mw.dumpObject( object ) .. '\n'
end
function mw.clearLogBuffer()
logBuffer = ''
end
function mw.getLogBuffer()
return logBuffer
end
function mw.getCurrentFrame()
if not currentFrame then
currentFrame = newFrame( 'current', 'parent' )
end
return currentFrame
end
function mw.isSubsting()
return php.isSubsting()
end
function mw.incrementExpensiveFunctionCount()
php.incrementExpensiveFunctionCount()
end
function mw.addWarning( text )
-- php.addWarning( text )
end
---
-- Wrapper for mw.loadData. This creates the read-only dummy table for
-- accessing the real data.
--
-- @param data table Data to access
-- @param seen table|nil Table of already-seen tables.
-- @return table
local function dataWrapper( data, seen )
local t = {}
seen = seen or { [data] = t }
local function pairsfunc( s, k )
k = next( data, k )
if k ~= nil then
return k, t[k]
end
return nil
end
local function ipairsfunc( s, i )
i = i + 1
if data[i] ~= nil then
return i, t[i]
end
return -- no nil to match default ipairs()
end
local mt = {
mw_loadData = true,
__index = function ( tt, k )
assert( t == tt )
local v = data[k]
if type( v ) == 'table' then
seen[v] = seen[v] or dataWrapper( v, seen )
return seen[v]
end
return v
end,
__newindex = function ( t, k, v )
error( "table from mw.loadData is read-only", 2 )
end,
__pairs = function ( tt )
assert( t == tt )
return pairsfunc, t, nil
end,
__ipairs = function ( tt )
assert( t == tt )
return ipairsfunc, t, 0
end,
}
-- This is just to make setmetatable() fail
mt.__metatable = mt
return setmetatable( t, mt )
end
---
-- Validator for mw.loadData. This scans through the data looking for things
-- that are not supported, e.g. functions (which may be closures).
--
-- @param d table Data to access.
-- @param seen table|nil Table of already-seen tables.
-- @return string|nil Error message, if any
local function validateData( d, seen )
seen = seen or {}
local tp = type( d )
if tp == 'nil' or tp == 'boolean' or tp == 'number' or tp == 'string' then
return nil
elseif tp == 'table' then
if seen[d] then
return nil
end
seen[d] = true
if getmetatable( d ) ~= nil then
return "data for mw.loadData contains a table with a metatable"
end
for k, v in pairs( d ) do
if type( k ) == 'table' then
return "data for mw.loadData contains a table as a key"
end
local err = validateData( k, seen ) or validateData( v, seen )
if err then
return err
end
end
return nil
else
return "data for mw.loadData contains unsupported data type '" .. tp .. "'"
end
end
function mw.loadData( module )
local data = loadedData[module]
if type( data ) == 'string' then
-- No point in re-validating
error( data, 2 )
end
if not data then
-- Don't allow accessing the current frame's info (bug 65687)
local oldFrame = currentFrame
currentFrame = newFrame( 'empty' )
-- The point of this is to load big data, so don't save it in package.loaded
-- where it will have to be copied for all future modules.
local l = package.loaded[module]
local _
_, data = mw.executeModule( function() return require( module ) end )
package.loaded[module] = l
currentFrame = oldFrame
-- Validate data
local err
if type( data ) == 'table' then
err = validateData( data )
else
err = module .. ' returned ' .. type( data ) .. ', table expected'
end
if err then
loadedData[module] = err
error( err, 2 )
end
loadedData[module] = data
end
return dataWrapper( data )
end
return mw

View File

@@ -0,0 +1,220 @@
local message = {}
local php
local util = require 'libraryUtil'
local checkType = util.checkType
local init_message_for_lang_needed = true -- xowa
local valuemt = {
__tostring = function ( t )
return tostring( t.raw or t.num )
end
}
local function checkScalar( name, argIdx, arg, level, valuemtOk )
local tp = type( arg )
-- If special params are ok, detect them
if valuemtOk and tp == 'table' and getmetatable( arg ) == valuemt then
return arg
end
-- If it's a table with a custom __tostring function, use that string
if tp == 'table' and getmetatable( arg ) and getmetatable( arg ).__tostring then
return tostring( arg )
end
if tp ~= 'string' and tp ~= 'number' then
error( string.format(
"bad argument #%d to '%s' (string or number expected, got %s)",
argIdx, name, tp
), level + 1 )
end
return arg
end
local function checkParams( name, valueOk, ... )
-- Accept an array of params, or params as individual command line arguments
local params, nparams
local first = select( 1, ... )
if type( first ) == 'table' and
not ( getmetatable( first ) and getmetatable( first ).__tostring )
then
if select( '#', ... ) == 1 then
params = first
nparams = table.maxn( params )
else
error(
"bad arguments to '" .. name .. "' (pass either a table of params or params as individual arguments)",
3
)
end
else
params = { ... }
nparams = select( '#', ... )
end
for i = 1, nparams do
params[i] = checkScalar( 'params', i, params[i], 3, valueOk )
end
return params
end
function message.setupInterface( options )
-- Boilerplate
message.setupInterface = nil
php = mw_interface
mw_interface = nil
php.options = message.make_proxy(options) -- xowa
-- Register this library in the "mw" global
mw = mw or {}
mw.message = message
package.loaded['mw.message'] = message
end
local function makeMessage( options )
local obj = {}
local checkSelf = util.makeCheckSelfFunction( 'mw.message', 'msg', obj, 'message object' )
local data = {
keys = options.keys,
rawMessage = options.rawMessage,
params = {},
lang = php.options.lang,
useDB = true,
}
function obj:params( ... )
checkSelf( self, 'params' )
local params = checkParams( 'params', true, ... )
local j = #data.params
for i = 1, #params do
data.params[j + i] = params[i]
end
return self
end
function obj:rawParams( ... )
checkSelf( self, 'rawParams' )
local params = checkParams( 'rawParams', false, ... )
local j = #data.params
for i = 1, #params do
data.params[j + i] = setmetatable( { raw = params[i] }, valuemt )
end
return self
end
function obj:numParams( ... )
checkSelf( self, 'numParams' )
local params = checkParams( 'numParams', false, ... )
local j = #data.params
for i = 1, #params do
data.params[j + i] = setmetatable( { num = params[i] }, valuemt )
end
return self
end
function obj:inLanguage( lang )
checkSelf( self, 'inLanguage' )
if type( lang ) == 'table' and lang.getCode then
-- probably a mw.language object
lang = lang:getCode()
end
checkType( 'inLanguage', 1, lang, 'string' )
data.lang = lang
return self
end
function obj:useDatabase( value )
checkSelf( self, 'useDatabase' )
checkType( 'useDatabase', 1, value, 'boolean' )
data.useDB = value
return self
end
function obj:plain()
checkSelf( self, 'plain' )
return php.plain( data )
end
function obj:exists()
checkSelf( self, 'exists' )
return php.check( 'exists', data )
end
function obj:isBlank()
checkSelf( self, 'isBlank' )
return php.check( 'isBlank', data )
end
function obj:isDisabled()
checkSelf( self, 'isDisabled' )
return php.check( 'isDisabled', data )
end
return setmetatable( obj, {
__tostring = function ( t )
return t:plain()
end
} )
end
function message.new( key, ... )
checkType( 'message.new', 1, key, 'string' )
return makeMessage{ keys = { key } }:params( ... )
end
function message.newFallbackSequence( ... )
for i = 1, math.max( 1, select( '#', ... ) ) do
checkType( 'message.newFallbackSequence', i, select( i, ... ), 'string' )
end
return makeMessage{ keys = { ... } }
end
function message.newRawMessage( msg, ... )
checkType( 'message.newRawMessage', 1, msg, 'string' )
return makeMessage{ rawMessage = msg }:params( ... )
end
function message.rawParam( value )
value = checkScalar( 'message.rawParam', 1, value )
return setmetatable( { raw = value }, valuemt )
end
function message.numParam( value )
value = checkScalar( 'message.numParam', 1, value )
return setmetatable( { num = value }, valuemt )
end
function message.getDefaultLanguage()
if mw.language then
return mw.language.new( php.options.lang )
else
return php.options.lang
end
end
-- xowa:bgn
function message.notify_lang_changed()
init_message_for_lang_needed = true
end
function message.make_proxy(real)
local proxy = {}
setmetatable(proxy, {__index =
function(tbl, key)
if key == "lang" and init_message_for_lang_needed then
real[key] = php.init_message_for_lang().lang
init_message_for_lang_needed = false
end
return real[key]
end
}
)
return proxy
end
-- xowa:end
return message

View File

@@ -0,0 +1,141 @@
local site = {}
local init_site_for_wiki_needed = true -- xowa
local php
function site.setupInterface( info )
-- Boilerplate
site.setupInterface = nil
php = mw_interface
mw_interface = nil
-- site.setupSite(info) -- xowa
-- Register this library in the "mw" global
mw = mw or {}
mw.site = site.make_proxy(site) -- xowa
package.loaded['mw.site'] = site
end
-- xowa:bgn
function site.notify_wiki_changed()
init_site_for_wiki_needed = true
end
function site.make_proxy(real)
local proxy = {}
setmetatable(proxy, {__index =
function(tbl, key)
if init_site_for_wiki_needed then
info = php.init_site_for_wiki() -- more performant than calling setupInterface for every page load
site.setupSite(info)
init_site_for_wiki_needed = false
end
return real[key]
end
}
)
return proxy
end
function site.setupSite( info )
site.siteName = info.siteName
site.server = info.server
site.scriptPath = info.scriptPath
site.stylePath = info.stylePath
site.currentVersion = info.currentVersion
site.stats = info.stats
site.stats.pagesInCategory = php.pagesInCategory
site.stats.pagesInNamespace = php.pagesInNamespace
site.stats.usersInGroup = php.usersInGroup
site.interwikiMap = php.interwikiMap
-- Process namespace list into more useful tables
site.namespaces = {}
local namespacesByName = {}
site.subjectNamespaces = {}
site.talkNamespaces = {}
site.contentNamespaces = {}
for ns, data in pairs( info.namespaces ) do
data.subject = info.namespaces[data.subject]
data.talk = info.namespaces[data.talk]
data.associated = info.namespaces[data.associated]
site.namespaces[ns] = data
namespacesByName[data.name] = data
if data.canonicalName then
namespacesByName[data.canonicalName] = data
end
for i = 1, #data.aliases do
namespacesByName[data.aliases[i]] = data
end
if data.isSubject then
site.subjectNamespaces[ns] = data
end
if data.isTalk then
site.talkNamespaces[ns] = data
end
if data.isContent then
site.contentNamespaces[ns] = data
end
end
-- Set __index for namespacesByName to handle names-with-underscores
-- and non-standard case
local getNsIndex = php.getNsIndex
setmetatable( namespacesByName, {
__index = function ( t, k )
if type( k ) == 'string' then
-- Try with fixed underscores
k = string.gsub( k, '_', ' ' )
if rawget( t, k ) then
return rawget( t, k )
end
-- Ask PHP, because names are case-insensitive
local ns = getNsIndex( k )
if ns then
rawset( t, k, site.namespaces[ns] )
end
end
return rawget( t, k )
end
} )
-- Set namespacesByName as the lookup table for site.namespaces, so
-- something like site.namespaces.Wikipedia works without having
-- pairs( site.namespaces ) iterate all those names.
setmetatable( site.namespaces, { __index = namespacesByName } )
-- Copy the site stats, and set up the metatable to load them if necessary.
local loadSiteStats = php.loadSiteStats
site.stats.x = php.loadSiteStats
if info.stats then
loadSiteStats = nil
for k, v in pairs( info.stats ) do
site.stats[k] = v
end
end
setmetatable( site.stats, {
__index = function ( t, k )
if t ~= site.stats then -- cloned
return site.stats[k]
end
if k == 'admins' then
t.admins = t.usersInGroup( 'sysop' )
elseif loadSiteStats then
for k, v in pairs( loadSiteStats() ) do
t[k] = v
end
loadSiteStats = nil
end
return rawget( t, k )
end
} )
end
-- xowa:end
return site

View File

@@ -0,0 +1,352 @@
local mwtext = {}
local php
local options
local init_text_for_wiki_needed = true -- xowa
local util = require 'libraryUtil'
local checkType = util.checkType
local checkTypeForNamedArg = util.checkTypeForNamedArg
function mwtext.setupInterface( opts )
-- Boilerplate
mwtext.setupInterface = nil
php = mw_interface
mw_interface = nil
-- options = opts -- xowa
-- Register this library in the "mw" global
mw = mw or {}
mw.text = mwtext.make_proxy(mwtext) -- xowa
package.loaded['mw.text'] = mwtext
end
function mwtext.trim( s, charset )
charset = charset or '\t\r\n\f '
s = mw.ustring.gsub( s, '^[' .. charset .. ']*(.-)[' .. charset .. ']*$', '%1' )
return s
end
local htmlencode_map = {
['>'] = '&gt;',
['<'] = '&lt;',
['&'] = '&amp;',
['"'] = '&quot;',
["'"] = '&#039;',
['\194\160'] = '&nbsp;',
}
local htmldecode_map = {}
for k, v in pairs( htmlencode_map ) do
htmldecode_map[v] = k
end
local decode_named_entities = nil
function mwtext.encode( s, charset )
charset = charset or '<>&"\'\194\160'
s = mw.ustring.gsub( s, '[' .. charset .. ']', function ( m )
if not htmlencode_map[m] then
local e = string.format( '&#%d;', mw.ustring.codepoint( m ) )
htmlencode_map[m] = e
htmldecode_map[e] = m
end
return htmlencode_map[m]
end )
return s
end
function mwtext.decode( s, decodeNamedEntities )
local dec
if decodeNamedEntities then
if decode_named_entities == nil then
decode_named_entities = php.getEntityTable()
setmetatable( decode_named_entities, { __index = htmldecode_map } )
end
dec = decode_named_entities
else
dec = htmldecode_map
end
-- string.gsub is safe here, because only ASCII chars are in the pattern
s = string.gsub( s, '(&(#?x?)([a-zA-Z0-9]+);)', function ( m, flg, name )
if not dec[m] then
local n = nil
if flg == '#' then
n = tonumber( name, 10 )
elseif flg == '#x' then
n = tonumber( name, 16 )
end
if n and n <= 0x10ffff then
n = mw.ustring.char( n )
if n then
htmldecode_map[m] = n
htmlencode_map[n] = m
end
end
end
return dec[m]
end )
return s
end
local nowikiRepl1 = {
['"'] = '&#34;',
['&'] = '&#38;',
["'"] = '&#39;',
['<'] = '&#60;',
['='] = '&#61;',
['>'] = '&#62;',
['['] = '&#91;',
[']'] = '&#93;',
['{'] = '&#123;',
['|'] = '&#124;',
['}'] = '&#125;',
}
local nowikiRepl2 = {
["\n#"] = "\n&#35;", ["\r#"] = "\r&#35;",
["\n*"] = "\n&#42;", ["\r*"] = "\r&#42;",
["\n:"] = "\n&#58;", ["\r:"] = "\r&#58;",
["\n;"] = "\n&#59;", ["\r;"] = "\r&#59;",
["\n "] = "\n&#32;", ["\r "] = "\r&#32;",
["\n\n"] = "\n&#10;", ["\r\n"] = "&#13;\n",
["\n\r"] = "\n&#13;", ["\r\r"] = "\r&#13;",
["\n\t"] = "\n&#9;", ["\r\t"] = "\r&#9;",
}
local nowikiReplMagic = {}
for sp, esc in pairs( {
[' '] = '&#32;',
['\t'] = '&#9;',
['\r'] = '&#13;',
['\n'] = '&#10;',
['\f'] = '&#12;',
} ) do
nowikiReplMagic['ISBN' .. sp] = 'ISBN' .. esc
nowikiReplMagic['RFC' .. sp] = 'RFC' .. esc
nowikiReplMagic['PMID' .. sp] = 'PMID' .. esc
end
function mwtext.nowiki( s )
-- string.gsub is safe here, because we're only caring about ASCII chars
s = string.gsub( s, '["&\'<=>%[%]{|}]', nowikiRepl1 )
s = '\n' .. s
s = string.gsub( s, '[\r\n][#*:; \n\r\t]', nowikiRepl2 )
s = string.gsub( s, '([\r\n])%-%-%-%-', '%1&#45;---' )
s = string.sub( s, 2 )
s = string.gsub( s, '__', '_&#95;' )
s = string.gsub( s, '://', '&#58;//' )
s = string.gsub( s, 'ISBN%s', nowikiReplMagic )
s = string.gsub( s, 'RFC%s', nowikiReplMagic )
s = string.gsub( s, 'PMID%s', nowikiReplMagic )
for k, v in pairs( options.nowiki_protocols ) do
s = string.gsub( s, k, v )
end
return s
end
function mwtext.tag( name, attrs, content )
local named = false
if type( name ) == 'table' then
named = true
name, attrs, content = name.name, name.attrs, name.content
checkTypeForNamedArg( 'tag', 'name', name, 'string' )
checkTypeForNamedArg( 'tag', 'attrs', attrs, 'table', true )
else
checkType( 'tag', 1, name, 'string' )
checkType( 'tag', 2, attrs, 'table', true )
end
local ret = { '<' .. name }
for k, v in pairs( attrs or {} ) do
if type( k ) ~= 'string' then
error( "bad named argument attrs to 'tag' (keys must be strings, found " .. type( k ) .. ")", 2 )
end
if string.match( k, '[\t\r\n\f /<>"\'=]' ) then
error( "bad named argument attrs to 'tag' (invalid key '" .. k .. "')", 2 )
end
local tp = type( v )
if tp == 'boolean' then
if v then
ret[#ret+1] = ' ' .. k
end
elseif tp == 'string' or tp == 'number' then
ret[#ret+1] = string.format( ' %s="%s"', k, mwtext.encode( tostring( v ) ) )
else
error( "bad named argument attrs to 'tag' (value for key '" .. k .. "' may not be " .. tp .. ")", 2 )
end
end
local tp = type( content )
if content == nil then
ret[#ret+1] = '>'
elseif content == false then
ret[#ret+1] = ' />'
elseif tp == 'string' or tp == 'number' then
ret[#ret+1] = '>'
ret[#ret+1] = content
ret[#ret+1] = '</' .. name .. '>'
else
if named then
checkTypeForNamedArg( 'tag', 'content', content, 'string, number, nil, or false' )
else
checkType( 'tag', 3, content, 'string, number, nil, or false' )
end
end
return table.concat( ret )
end
function mwtext.unstrip( s )
return php.unstrip( s )
end
function mwtext.unstripNoWiki( s )
return php.unstripNoWiki( s )
end
function mwtext.killMarkers( s )
return php.killMarkers( s )
end
function mwtext.split( text, pattern, plain )
local ret = {}
for m in mwtext.gsplit( text, pattern, plain ) do
ret[#ret+1] = m
end
return ret
end
function mwtext.gsplit( text, pattern, plain )
local s, l = 1, mw.ustring.len( text )
return function ()
if s then
local e, n = mw.ustring.find( text, pattern, s, plain )
local ret
if not e then
ret = mw.ustring.sub( text, s )
s = nil
elseif n < e then
-- Empty separator!
ret = mw.ustring.sub( text, s, e )
if e < l then
s = e + 1
else
s = nil
end
else
ret = e > s and mw.ustring.sub( text, s, e - 1 ) or ''
s = n + 1
end
return ret
end
end, nil, nil
end
function mwtext.listToText( list, separator, conjunction )
separator = separator or options.comma
conjunction = conjunction or options['and']
local n = #list
local ret
if n > 1 then
ret = table.concat( list, separator, 1, n - 1 ) .. conjunction .. list[n]
else
ret = tostring( list[1] or '' )
end
return ret
end
function mwtext.truncate( text, length, ellipsis, adjustLength )
local l = mw.ustring.len( text )
if l <= math.abs( length ) then
return text
end
ellipsis = ellipsis or options.ellipsis
local elen = 0
if adjustLength then
elen = mw.ustring.len( ellipsis )
end
local ret
if math.abs( length ) <= elen then
ret = ellipsis
elseif length > 0 then
ret = mw.ustring.sub( text, 1, length - elen ) .. ellipsis
else
ret = ellipsis .. mw.ustring.sub( text, length + elen )
end
if mw.ustring.len( ret ) < l then
return ret
else
return text
end
end
-- Check for stuff that can't even be passed to PHP properly and other stuff
-- that gives different error messages in different versions of PHP
local function checkForJsonEncode( t, seen, lvl )
local tp = type( t )
if tp == 'table' then
if seen[t] then
error( "mw.text.jsonEncode: Cannot use recursive tables", lvl )
end
seen[t] = 1
for k, v in pairs( t ) do
if type( k ) == 'number' then
if k >= math.huge or k <= -math.huge then
error( string.format( "mw.text.jsonEncode: Cannot use 'inf' as a table key", type( k ) ), lvl )
end
elseif type( k ) ~= 'string' then
error( string.format( "mw.text.jsonEncode: Cannot use type '%s' as a table key", type( k ) ), lvl )
end
checkForJsonEncode( v, seen, lvl + 1 )
end
seen[t] = nil
elseif tp == 'number' then
if t ~= t or t >= math.huge or t <= -math.huge then
error( "mw.text.jsonEncode: Cannot encode non-finite numbers", lvl )
end
elseif tp ~= 'boolean' and tp ~= 'string' and tp ~= 'nil' then
error( string.format( "mw.text.jsonEncode: Cannot encode type '%s'", tp ), lvl )
end
end
function mwtext.jsonEncode( value, flags )
checkForJsonEncode( value, {}, 3 )
return php.jsonEncode( value, flags )
end
function mwtext.jsonDecode( json, flags )
return php.jsonDecode( json, flags )
end
-- Matches PHP Scribunto_LuaTextLibrary constants
mwtext.JSON_PRESERVE_KEYS = 1
mwtext.JSON_TRY_FIXING = 2
mwtext.JSON_PRETTY = 4
-- xowa:bgn
function mwtext.notify_wiki_changed()
init_text_for_wiki_needed = true
end
function mwtext.make_proxy(real)
local proxy = {}
setmetatable(proxy, {__index =
function(tbl, key)
if init_text_for_wiki_needed then
options = php.init_text_for_wiki() -- more performant than calling setupInterface for every page load
init_text_for_wiki_needed = false
end
return real[key]
end
}
)
return proxy
end
-- xowa:end
return mwtext

View File

@@ -0,0 +1,392 @@
local title = {}
local php
local NS_MEDIA = -2
local init_title_for_page_needed = true -- xowa
local util = require 'libraryUtil'
local checkType = util.checkType
local checkTypeForIndex = util.checkTypeForIndex
local function checkNamespace( name, argIdx, arg )
if type( arg ) == 'string' and tostring( tonumber( arg ) ) == arg then
arg = tonumber( arg )
end
if type( arg ) == 'number' then
arg = math.floor( arg + 0.5 )
if not mw.site.namespaces[arg] then
local msg = string.format( "bad argument #%d to '%s' (unrecognized namespace number '%s')",
argIdx, name, arg
)
error( msg, 3 )
end
elseif type( arg ) == 'string' then
local ns = mw.site.namespaces[arg]
if not ns then
local msg = string.format( "bad argument #%d to '%s' (unrecognized namespace name '%s')",
argIdx, name, arg
)
error( msg, 3 )
end
arg = ns.id
else
local msg = string.format( "bad argument #%d to '%s' (string or number expected, got %s)",
argIdx, name, type( arg )
)
error( msg, 3 )
end
return arg
end
local function lt( a, b )
if a.interwiki ~= b.interwiki then
return a.interwiki < b.interwiki
end
if a.namespace ~= b.namespace then
return a.namespace < b.namespace
end
return a.text < b.text
end
local function makeTitleObject( data )
if not data then
return nil
end
local obj = {}
local checkSelf = util.makeCheckSelfFunction( 'mw.title', 'title', obj, 'title object' );
local ns = mw.site.namespaces[data.namespace]
data.isContentPage = ns.isContent
data.isExternal = data.interwiki ~= ''
data.isSpecialPage = data.namespace == mw.site.namespaces.Special.id
data.isTalkPage = ns.isTalk
data.subjectNsText = ns.subject.name
data.canTalk = ns.talk ~= nil
data.prefixedText = data.text
if data.nsText ~= '' then
data.prefixedText = string.gsub( data.nsText .. ':' .. data.prefixedText, '_', ' ' )
end
if data.interwiki ~= '' then
data.prefixedText = data.interwiki .. ':' .. data.prefixedText
end
local firstSlash, lastSlash
if ns.hasSubpages then
firstSlash, lastSlash = string.match( data.text, '^[^/]*().*()/[^/]*$' )
end
if firstSlash then
data.isSubpage = true
data.rootText = string.sub( data.text, 1, firstSlash - 1 )
data.baseText = string.sub( data.text, 1, lastSlash - 1 )
data.subpageText = string.sub( data.text, lastSlash + 1 )
else
data.isSubpage = false
data.rootText = data.text
data.baseText = data.text
data.subpageText = data.text
end
function data:inNamespace( ns )
checkSelf( self, 'inNamespace' )
ns = checkNamespace( 'inNamespace', 1, ns )
return ns == self.namespace
end
function data:inNamespaces( ... )
checkSelf( self, 'inNamespaces' )
for i = 1, select( '#', ... ) do
local ns = checkNamespace( 'inNamespaces', i, select( i, ... ) )
if ns == self.namespace then
return true
end
end
return false
end
function data:hasSubjectNamespace( ns )
checkSelf( self, 'hasSubjectNamespace' )
ns = checkNamespace( 'hasSubjectNamespace', 1, ns )
return ns == mw.site.namespaces[self.namespace].subject.id
end
function data:isSubpageOf( title )
checkSelf( self, 'isSubpageOf' )
checkType( 'isSubpageOf', 1, title, 'table' )
return self.interwiki == title.interwiki and
self.namespace == title.namespace and
title.text .. '/' == string.sub( self.text, 1, #title.text + 1 )
end
function data:subPageTitle( text )
checkSelf( self, 'subpageTitle' )
checkType( 'subpageTitle', 1, text, 'string' )
return title.makeTitle( data.namespace, data.text .. '/' .. text )
end
function data:partialUrl()
checkSelf( self, 'partialUrl' )
return data.thePartialUrl
end
function data:fullUrl( query, proto )
checkSelf( self, 'fullUrl' )
return php.getUrl( self.fullText, 'fullUrl', query, proto )
end
function data:localUrl( query )
checkSelf( self, 'localUrl' )
return php.getUrl( self.fullText, 'localUrl', query )
end
function data:canonicalUrl( query )
checkSelf( self, 'canonicalUrl' )
return php.getUrl( self.fullText, 'canonicalUrl', query )
end
function data:getContent()
checkSelf( self, 'getContent' )
local content = php.getContent( self.fullText )
data.getContent = function ( self )
checkSelf( self, 'getContent' )
return content
end
return content
end
-- Known fields, both those defined above and any dynamically handled in
-- __index. Truthy values represent read-only, and falsey values represent
-- read-write. If the value is the string 'e', expensive data will be loaded
-- if the field is read.
local readOnlyFields = {
fragment = false,
fullText = true,
rootPageTitle = true,
basePageTitle = true,
talkPageTitle = true,
subjectPageTitle = true,
fileExists = true,
file = true,
protectionLevels = true,
cascadingProtection = true,
exists = 'e',
isRedirect = 'e',
contentModel = 'e',
id = 'e',
redirectTarget = true,
}
for k in pairs( data ) do
readOnlyFields[k] = true
end
local function pairsfunc( t, k )
local v
repeat
k = next( readOnlyFields, k )
if k == nil then
return nil
end
v = t[k]
until v ~= nil
return k, v
end
return setmetatable( obj, {
__eq = title.equals,
__lt = lt,
__pairs = function ( t )
return pairsfunc, t, nil
end,
__index = function ( t, k )
if k == 'exists' and data.namespace == NS_MEDIA then
k = 'fileExists'
end
if readOnlyFields[k] == 'e' and data[k] == nil then
for k,v in pairs( php.getExpensiveData( t.fullText ) ) do
data[k] = v
end
end
if k == 'fullText' then
if data.fragment ~= '' then
return data.prefixedText .. '#' .. data.fragment
else
return data.prefixedText
end
end
if k == 'rootPageTitle' then
return title.makeTitle( data.namespace, data.rootText )
end
if k == 'basePageTitle' then
return title.makeTitle( data.namespace, data.baseText )
end
if k == 'talkPageTitle' then
local ns = mw.site.namespaces[data.namespace].talk
if not ns then
return nil
end
if ns.id == data.namespace then
return obj
end
return title.makeTitle( ns.id, data.text )
end
if k == 'subjectPageTitle' then
local ns = mw.site.namespaces[data.namespace].subject
if ns.id == data.namespace then
return obj
end
return title.makeTitle( ns.id, data.text )
end
if k == 'file' then
if data.file == nil then
data.file = php.getFileInfo( data.prefixedText )
end
return data.file or nil
end
if k == 'fileExists' then -- Kept for backward compatibility. Since 1.25, file.exists is preferred over this
return t.file and t.file.exists
end
if k == 'protectionLevels' then
if data.protectionLevels == nil then
data.protectionLevels = php.protectionLevels( data.prefixedText )
end
return data.protectionLevels
end
if k == 'cascadingProtection' then
if data.cascadingProtection == nil then
data.cascadingProtection = php.cascadingProtection( data.prefixedText )
end
return data.cascadingProtection
end
if k == 'redirectTarget' then
if data.redirectTarget == nil then
data.redirectTarget = makeTitleObject( php.redirectTarget( data.prefixedText ) ) or false
end
return data.redirectTarget
end
return data[k]
end,
__newindex = function ( t, k, v )
if k == 'fragment' then
checkTypeForIndex( k, v, 'string' )
v = string.gsub( v, '[%s_]+', ' ' )
v = string.gsub( v, '^(.-) ?$', '%1' )
data[k] = v
elseif readOnlyFields[k] then
error( "index '" .. k .. "' is read only", 2 )
else
readOnlyFields[k] = v and false -- assigns nil if v == nil, false otherwise
rawset( t, k, v )
end
end,
__tostring = function ( t )
return t.prefixedText
end
} )
end
function title.setupInterface( options )
-- Boilerplate
title.setupInterface = nil
php = mw_interface
mw_interface = nil
NS_MEDIA = options.NS_MEDIA
-- xowa:bgn
-- Set current title
--title.getCurrentTitle = function ()
-- return makeTitleObject( options.thisTitle )
--end
-- xowa:end
-- Register this library in the "mw" global
mw = mw or {}
mw.title = title
title.notify_page_changed()
package.loaded['mw.title'] = title
end
function title.new( text_or_id, defaultNamespace )
return makeTitleObject( php.newTitle( text_or_id, defaultNamespace ) )
end
function title.makeTitle( ns, title, fragment, interwiki )
return makeTitleObject( php.makeTitle( ns, title, fragment, interwiki ) )
end
function title.equals( a, b )
return a.interwiki == b.interwiki and
a.namespace == b.namespace and
a.text == b.text
end
function title.compare( a, b )
if a.interwiki ~= b.interwiki then
return a.interwiki < b.interwiki and -1 or 1
end
if a.namespace ~= b.namespace then
return a.namespace < b.namespace and -1 or 1
end
if a.text ~= b.text then
return a.text < b.text and -1 or 1
end
return 0
end
-- xowa:bgn
local system_globals =
{ ["string"] = true
, ["xpcall"] = true
, ["package"] = true
, ["rawset"] = true
, ["_VERSION"] = true
, ["error"] = true
, ["pcall"] = true
, ["os"] = true
, ["unpack"] = true
, ["math"] = true
, ["ipairs"] = true
, ["require"] = true
, ["mw"] = true
, ["tostring"] = true
, ["type"] = true
, ["pairs"] = true
, ["next"] = true
, ["select"] = true
, ["assert"] = true
, ["rawget"] = true
, ["tonumber"] = true
, ["table"] = true
, ["_G"] = true
, ["rawequal"] = true
, ["setfenv"] = true
, ["setmetatable"] = true
, ["getfenv"] = true
, ["getmetatable"] = true
, ["debug"] = true
}
function title.notify_page_changed()
local php_data = php.getCurrentTitle()
title.getCurrentTitle = function ()
return makeTitleObject(php_data)
end
local delete_list = {}
for k,v in pairs(_G) do
local type_name = type(v)
if not system_globals[k] and type_name ~= 'function' and type_name ~= 'table' then
delete_list[k] = k
end
end
for k,v in pairs(delete_list) do
_G[k] = nil
end
end
-- xowa:end
return title

View File

@@ -0,0 +1,636 @@
local uri = {}
local urifuncs = {}
local urimt = {}
local php
local util = require 'libraryUtil'
local checkType = util.checkType
local checkTypeForIndex = util.checkTypeForIndex
local init_uri_for_page_needed = true -- xowa
function uri.setupInterface( options )
-- Boilerplate
uri.setupInterface = nil
php = mw_interface
mw_interface = nil
-- Store options
php.options = uri.make_proxy(options) -- xowa
-- Register this library in the "mw" global
mw = mw or {}
mw.uri = uri
package.loaded['mw.uri'] = uri
end
local function rawencode( s, space )
space = space or '%20'
local ret = string.gsub( s, '([^a-zA-Z0-9_.~-])', function ( c )
if c == ' ' then
return space
else
return string.format( '%%%02X', string.byte( c, 1, 1 ) )
end
end );
return ret
end
local function rawdecode( s )
local ret = string.gsub( s, '%%(%x%x)', function ( hex )
return string.char( tonumber( hex, 16 ) )
end );
return ret
end
function uri.encode( s, enctype )
checkType( 'encode', 1, s, 'string' )
enctype = string.upper( enctype or 'QUERY' )
if enctype == 'QUERY' then
return rawencode( s, '+' )
elseif enctype == 'PATH' then
return rawencode( s, '%20' )
elseif enctype == 'WIKI' then
return rawencode( s, '_' )
else
error( "bad argument #2 to 'encode' (expected QUERY, PATH, or WIKI)", 2 )
end
end
function uri.decode( s, enctype )
checkType( 'decode', 1, s, 'string' )
enctype = string.upper( enctype or 'QUERY' )
if enctype == 'QUERY' then
return rawdecode( string.gsub( s, '%+', ' ' ) )
elseif enctype == 'PATH' then
return rawdecode( s )
elseif enctype == 'WIKI' then
return rawdecode( string.gsub( s, '_', ' ' ) )
else
error( "bad argument #2 to 'decode' (expected QUERY, PATH, or WIKI)", 2 )
end
end
function uri.anchorEncode( s )
checkType( 'anchorEncode', 1, s, 'string' )
return php.anchorEncode( s )
end
function uri.localUrl( page, query )
checkType( 'localurl', 1, page, 'string' )
if query ~= nil and type( query ) ~= 'string' and type( query ) ~= 'table' then
checkType( 'localurl', 2, query, 'string or table' )
end
local url = php.localUrl( page, query )
if not url then
return nil
end
return uri.new( url )
end
function uri.fullUrl( page, query )
checkType( 'fullurl', 1, page, 'string' )
if query ~= nil and type( query ) ~= 'string' and type( query ) ~= 'table' then
checkType( 'fullurl', 2, query, 'string or table' )
end
local url = php.fullUrl( page, query )
if not url then
return nil
end
return uri.new( url )
end
function uri.canonicalUrl( page, query )
checkType( 'canonicalurl', 1, page, 'string' )
if query ~= nil and type( query ) ~= 'string' and type( query ) ~= 'table' then
checkType( 'canonicalurl', 2, query, 'string or table' )
end
local url = php.canonicalUrl( page, query )
if not url then
return nil
end
return uri.new( url )
end
function uri.new( s )
if s == nil or type( s ) == 'string' then
local obj = {
-- Yes, technically all this does nothing.
protocol = nil,
user = nil,
password = nil,
host = nil,
port = nil,
path = nil,
query = nil,
fragment = nil,
}
setmetatable( obj, urimt )
obj:parse( s or php.options.defaultUrl )
return obj
elseif type( s ) == 'table' then
local obj = {
protocol = s.protocol,
user = s.user,
password = s.password,
host = s.host,
port = s.port,
path = s.path,
query = mw.clone( s.query ),
fragment = s.fragment,
}
setmetatable( obj, urimt )
return obj
else
checkType( 'new', 1, s, 'string or table or nil' )
end
end
function uri.validate( obj )
checkType( 'validate', 1, obj, 'table' )
local err = {}
if obj.protocol then
if type( obj.protocol ) ~= 'string' then
err[#err+1] = '.protocol must be a string, not ' .. type( obj.protocol )
elseif not string.match( obj.protocol, '^[^:/?#]+$' ) then
err[#err+1] = 'invalid .protocol'
end
end
if obj.user then
if type( obj.user ) ~= 'string' then
err[#err+1] = '.user must be a string, not ' .. type( obj.user )
elseif not string.match( obj.user, '^[^:@/?#]*$' ) then
err[#err+1] = 'invalid .user'
end
end
if obj.password then
if type( obj.password ) ~= 'string' then
err[#err+1] = '.password must be a string, not ' .. type( obj.password )
elseif not string.match( obj.password, '^[^:@/?#]*$' ) then
err[#err+1] = 'invalid .password'
end
end
if obj.host then
if type( obj.host ) ~= 'string' then
err[#err+1] = '.host must be a string, not ' .. type( obj.host )
elseif not string.match( obj.host, '^[^:/?#]*$' ) then
err[#err+1] = 'invalid .host'
end
end
if obj.port then
if type( obj.port ) ~= 'number' or math.floor( obj.port ) ~= obj.port then
err[#err+1] = '.port must be an integer, not ' .. type( obj.port )
elseif obj.port < 1 or obj.port > 65535 then
err[#err+1] = 'invalid .port'
end
end
local authority = obj.user or obj.password or obj.host or obj.port
if not obj.path then
err[#err+1] = 'missing .path'
elseif type( obj.path ) ~= 'string' then
err[#err+1] = '.path must be a string, not ' .. type( obj.path )
elseif authority and not ( obj.path == '' or string.match( obj.path, '^/[^?#]*$' ) ) then
err[#err+1] = 'invalid .path'
elseif not authority and not (
obj.path == '' or
obj.path == '/' or
string.match( obj.path, '^/[^?#/][^?#]*$' ) or
obj.protocol and string.match( obj.path, '^[^?#/][^?#]*$' ) or
not obj.protocol and string.match( obj.path, '^[^?#/:]+$' ) or
not obj.protocol and string.match( obj.path, '^[^?#/:]+/[^?#]*$' )
) then
err[#err+1] = 'invalid .path'
end
if obj.query and type( obj.query ) ~= 'table' then
err[#err+1] = '.query must be a table, not ' .. type( obj.query )
end
if obj.fragment and type( obj.fragment ) ~= 'string' then
err[#err+1] = '.fragment must be a string, not ' .. type( obj.fragment )
end
return #err == 0, table.concat( err, '; ' )
end
-- Lua tables are unsorted, but we want to preserve the insertion order.
-- So, track the insertion order explicitly.
local function makeQueryTable()
local ret = {}
local keys = {}
local seenkeys = {}
setmetatable( ret, {
__newindex = function ( t, k, v )
if seenkeys[k] and not t[k] then
for i = 1, #keys do
if keys[i] == k then
table.remove( keys, i )
break
end
end
end
seenkeys[k] = 1
keys[#keys+1] = k
rawset( t, k, v )
end,
__pairs = function ( t )
local i, l = 0, #keys
return function ()
while i < l do
i = i + 1
local k = keys[i]
if t[k] ~= nil then
return k, t[k]
end
end
return nil
end
end
} )
return ret
end
function uri.parseQueryString( s, i, j )
checkType( 'parseQueryString', 1, s, 'string' )
checkType( 'parseQueryString', 2, i, 'number', true )
checkType( 'parseQueryString', 3, j, 'number', true )
s = string.gsub( string.sub( s, i or 1, j or -1 ), '%+', ' ' )
i = 1
j = string.len( s )
local qs = makeQueryTable()
if string.sub( s, i, 1 ) == '?' then
i = i + 1
end
while i <= j do
local amp = string.find( s, '&', i, true )
if not amp or amp > j then
amp = j + 1
end
local eq = string.find( s, '=', i, true )
local k, v
if not eq or eq > amp then
k = rawdecode( string.sub( s, i, amp - 1 ) )
v = false
else
k = rawdecode( string.sub( s, i, eq - 1 ) )
v = rawdecode( string.sub( s, eq + 1, amp - 1 ) )
end
if qs[k] then
if type( qs[k] ) ~= table then
qs[k] = { qs[k], v }
else
table.insert( qs[k], v )
end
else
qs[k] = v
end
i = amp + 1
end
return qs
end
function uri.buildQueryString( qs )
checkType( 'buildQueryString', 1, qs, 'table' )
local t = {}
for k, v in pairs( qs ) do
if type( v ) ~= 'table' then
v = { v }
end
for i = 1, #v do
t[#t+1] = '&'
t[#t+1] = rawencode( k, '+' )
if v[i] then
t[#t+1] = '='
t[#t+1] = rawencode( v[i], '+' )
end
end
end
return table.concat( t, '', 2 )
end
-- Fields mapped to whether they're handled by __index
local knownFields = {
protocol = false,
user = false,
password = false,
host = false,
port = false,
path = false,
query = false,
fragment = false,
userInfo = true,
hostPort = true,
authority = true,
queryString = true,
relativePath = true,
}
local function pairsfunc( t, k )
local v, f
repeat
k, f = next( knownFields, k )
if k == nil then
return nil
end
if f then
v = t[k]
else
v = rawget( t, k )
end
until v ~= nil
return k, v
end
function urimt:__pairs()
return pairsfunc, self, nil
end
function urimt:__index( key )
if urifuncs[key] then
return urifuncs[key]
end
if key == 'userInfo' then
local user = rawget( self, 'user' )
local password = rawget( self, 'password' )
if user and password then
return user .. ':' .. password
else
return user
end
end
if key == 'hostPort' then
local host = rawget( self, 'host' )
local port = rawget( self, 'port' )
if port then
return ( host or '' ) .. ':' .. port
else
return host
end
end
if key == 'authority' then
local info = self.userInfo
local hostPort = self.hostPort
if info then
return info .. '@' .. ( hostPort or '' )
else
return hostPort
end
end
if key == 'queryString' then
local query = rawget( self, 'query' )
if not query then
return nil
end
return uri.buildQueryString( query )
end
if key == 'relativePath' then
local ret = rawget( self, 'path' ) or ''
local qs = self.queryString
if qs then
ret = ret .. '?' .. qs
end
local fragment = rawget( self, 'fragment' )
if fragment then
ret = ret .. '#' .. fragment
end
return ret
end
return nil
end
function urimt:__newindex( key, value )
if key == 'userInfo' then
local user, password = nil, nil
if value then
checkTypeForIndex( key, value, 'string' )
local i = string.find( value, ':', 1, true )
if i then
user = string.sub( value, 1, i - 1 )
password = string.sub( value, i + 1 )
else
user = value
end
end
rawset( self, 'user', user )
rawset( self, 'password', password )
return
end
if key == 'hostPort' then
local host, port = nil, nil
if value then
checkTypeForIndex( key, value, 'string' )
local i = string.find( value, ':', 1, true )
if i then
host = string.sub( value, 1, i - 1 )
port = tonumber( string.sub( value, i + 1 ) )
if not port then
error( string.format( "Invalid port in '%s'", value ), 2 )
end
else
host = value
end
end
rawset( self, 'host', host )
rawset( self, 'port', port )
return
end
if key == 'authority' then
if value then
checkTypeForIndex( key, value, 'string' )
local i = string.find( value, '@', 1, true )
if i then
self.userInfo = string.sub( value, 1, i - 1 )
self.hostPort = string.sub( value, i + 1 )
else
self.userInfo = nil
self.hostPort = value
end
else
self.userInfo = nil
self.hostPort = nil
end
return
end
if key == 'queryString' then
if value then
checkTypeForIndex( key, value, 'string' )
rawset( self, 'query', uri.parseQueryString( value ) )
else
rawset( self, 'query', nil )
end
return
end
if key == 'relativePath' then
local path, query, fragment = nil, nil, nil
if value then
checkTypeForIndex( key, value, 'string' )
local i, j = nil, string.len( value )
i = string.find( value, '#', 1, true )
if i and i <= j then
fragment = string.sub( value, i + 1, j )
j = i - 1
end
i = string.find( value, '?', 1, true )
if i and i <= j then
query = uri.parseQueryString( string.sub( value, i + 1, j ) )
j = i - 1
end
path = string.sub( value, 1, j )
end
rawset( self, 'path', path )
rawset( self, 'query', query )
rawset( self, 'fragment', fragment )
return
end
if knownFields[key] then
error( "index '" .. key .. "' is read only", 2 )
end
-- Default behavior
knownFields[key] = false
rawset( self, key, value )
end
function urimt:__tostring()
local ret = ''
local protocol = self.protocol
local authority = self.authority
if protocol then
ret = protocol .. ':'
end
if authority then
ret = ret .. '//' .. authority
end
return ret .. self.relativePath
end
urifuncs.validate = uri.validate
function urifuncs:parse( s )
checkType( 'uri:parse', 1, s, 'string' )
-- Since Lua's patterns can't do (...)?, we have to try with and without each part.
local protocol, authority, relativePath = string.match( s, '^([^:/?#]+)://([^/?#]*)(.*)$' )
if not ( protocol or authority or relativePath ) then
authority, relativePath = string.match( s, '^//([^/?#]*)(.*)$' )
end
if not ( protocol or authority or relativePath ) then
protocol, relativePath = string.match( s, '^([^:/?#]+):(.*)$' )
end
if not ( protocol or authority or relativePath ) then
relativePath = s
end
-- Parse it into a temporary object, so if there's an error we didn't break the real one
local tmp = { protocol = protocol }
setmetatable( tmp, urimt )
if not pcall( urimt.__newindex, tmp, 'authority', authority ) then
error( 'Invalid port number in string', 2 )
end
tmp.relativePath = relativePath
-- Check for validity
local ok, err = uri.validate( tmp )
if not ok then
error( err, 2 )
end
-- Merge in fields
if tmp.protocol then
self.protocol = tmp.protocol
end
if tmp.user or tmp.password then
self.user, self.password = tmp.user, tmp.password
end
if tmp.host then
self.host = tmp.host
end
if tmp.port then
self.port = tmp.port
end
if tmp.path then
self.path = tmp.path
end
if tmp.query then
self.query = tmp.query
end
if tmp.fragment then
self.fragment = tmp.fragment
end
return self
end
function urifuncs:clone()
return uri.new( self )
end
function urifuncs:extend( parameters )
checkType( 'uri:extend', 1, parameters, 'table' )
local query = self.query
if not query then
query = makeQueryTable()
self.query = query
end
for k, v in pairs( parameters ) do
query[k] = v
end
return self
end
-- Add all urifuncs as known fields
for k in pairs( urifuncs ) do
knownFields[k] = true
end
-- xowa:bgn
function uri.notify_page_changed()
init_uri_for_page_needed = true
end
function uri.make_proxy(real)
local proxy = {}
setmetatable(proxy, {__index =
function(tbl, key)
if key == "defaultUrl" then
if init_uri_for_page_needed then
real[key] = php.init_uri_for_page()
init_uri_for_page_needed = false
end
end
return real[key]
end
}
)
return proxy
end
-- xowa:end
return uri

View File

@@ -0,0 +1,85 @@
-- Get a fresh copy of the basic ustring
local old_ustring = package.loaded.ustring
package.loaded.ustring = nil
local ustring = require( 'ustring' )
package.loaded.ustring = old_ustring
old_ustring = nil
local util = require 'libraryUtil'
local checkType = util.checkType
local gmatch_init = nil
local gmatch_callback = nil
local function php_gmatch( s, pattern )
checkType( 'gmatch', 1, s, 'string' )
checkType( 'gmatch', 2, pattern, 'string' )
local re, capt = gmatch_init( s, pattern )
local pos = 0
return function()
local ret
pos, ret = gmatch_callback( s, re, capt, pos )
return unpack( ret )
end, nil, nil
end
function ustring.setupInterface( opt )
-- Boilerplate
ustring.setupInterface = nil
-- Set string limits
ustring.maxStringLength = opt.stringLengthLimit
ustring.maxPatternLength = opt.patternLengthLimit
-- Gmatch
if mw_interface.gmatch_callback and mw_interface.gmatch_init then
gmatch_init = mw_interface.gmatch_init
gmatch_callback = mw_interface.gmatch_callback
ustring.gmatch = php_gmatch
end
mw_interface.gmatch_init = nil
mw_interface.gmatch_callback = nil
-- Replace pure-lua implementation with php callbacks
local nargs = {
char = 0,
find = 2,
match = 2,
gsub = 3,
}
for k, v in pairs( mw_interface ) do
local n = nargs[k] or 1
if n == 0 then
ustring[k] = v
else
-- Avoid PHP warnings for missing arguments by checking before
-- calling PHP.
ustring[k] = function ( ... )
if select( '#', ... ) < n then
error( "too few arguments to mw.ustring." .. k, 2 )
end
return v( ... )
end
end
end
mw_interface = nil
-- Replace upper/lower with mw.language versions if available
if mw and mw.language then
local lang = mw.language.getContentLanguage()
ustring.upper = function ( s )
return lang:uc( s )
end
ustring.lower = function ( s )
return lang:lc( s )
end
end
-- Register this library in the "mw" global
mw = mw or {}
mw.ustring = ustring
package.loaded['mw.ustring'] = ustring
end
return ustring

View File

@@ -0,0 +1,255 @@
--XOWA:updated 2017-03-16
--[[
Registers and defines functions to handle Wikibase Entities through the Scribunto extension.
@since 0.5
@license GNU GPL v2+
@author Marius Hoch < hoo@online.de >
@author Bene* < benestar.wikimedia@gmail.com >
]]
local php = mw_interface
local entity = {}
local metatable = {}
local methodtable = {}
local util = require 'libraryUtil'
local checkType = util.checkType
local checkTypeMulti = util.checkTypeMulti
metatable.__index = methodtable
-- Claim ranks (Claim::RANK_* in PHP)
entity.claimRanks = {
RANK_TRUTH = 3,
RANK_PREFERRED = 2,
RANK_NORMAL = 1,
RANK_DEPRECATED = 0
}
-- Create new entity object from given data
--
-- @param {table} data
entity.create = function( data )
if type( data ) ~= 'table' or type( data.schemaVersion ) ~= 'number' then
error( 'The entity data must be a table obtained via mw.wikibase.getEntityObject' )
end
if data.schemaVersion < 2 then
error( 'mw.wikibase.entity must not be constructed using legacy data' )
end
local entity = data
setmetatable( entity, metatable )
return entity
end
-- Get a term of a given type for a given language code or the content language (on monolingual wikis)
-- or the user's language (on multilingual wikis).
-- Second return parameter is the language the term is in.
--
-- @param {table} entity
-- @param {string} termType A valid key in the entity table (either labels, descriptions or aliases)
-- @param {string|number} langCode
local getTermAndLang = function( entity, termType, langCode )
langCode = langCode or php.getLanguageCode()
if langCode == nil then
return nil, nil
end
if entity[termType] == nil then
return nil, nil
end
local term = entity[termType][langCode]
if term == nil then
return nil, nil
end
local actualLang = term.language or langCode
return term.value, actualLang
end
-- Get the label for a given language code or the content language (on monolingual wikis)
-- or the user's language (on multilingual wikis).
--
-- @param {string|number} [langCode]
methodtable.getLabel = function( entity, langCode )
checkTypeMulti( 'getLabel', 1, langCode, { 'string', 'number', 'nil' } )
local label = getTermAndLang( entity, 'labels', langCode )
return label
end
-- Get the description for a given language code or the content language (on monolingual wikis)
-- or the user's language (on multilingual wikis).
--
-- @param {string|number} [langCode]
methodtable.getDescription = function( entity, langCode )
checkTypeMulti( 'getDescription', 1, langCode, { 'string', 'number', 'nil' } )
local description = getTermAndLang( entity, 'descriptions', langCode )
return description
end
-- Get the label for a given language code or the content language (on monolingual wikis)
-- or the user's language (on multilingual wikis).
-- Has the language the returned label is in as an additional second return parameter.
--
-- @param {string|number} [langCode]
methodtable.getLabelWithLang = function( entity, langCode )
checkTypeMulti( 'getLabelWithLang', 1, langCode, { 'string', 'number', 'nil' } )
return getTermAndLang( entity, 'labels', langCode )
end
-- Get the description for a given language code or the content language (on monolingual wikis)
-- or the user's language (on multilingual wikis).
-- Has the language the returned description is in as an additional second return parameter.
--
-- @param {string|number} [langCode]
methodtable.getDescriptionWithLang = function( entity, langCode )
checkTypeMulti( 'getDescriptionWithLang', 1, langCode, { 'string', 'number', 'nil' } )
return getTermAndLang( entity, 'descriptions', langCode )
end
-- Get the sitelink title linking to the given site id
--
-- @param {string|number} [globalSiteId]
methodtable.getSitelink = function( entity, globalSiteId )
checkTypeMulti( 'getSitelink', 1, globalSiteId, { 'string', 'number', 'nil' } )
if entity.sitelinks == nil then
return nil
end
globalSiteId = globalSiteId or php.getGlobalSiteId()
if globalSiteId == nil then
return nil
end
local sitelink = entity.sitelinks[globalSiteId]
if sitelink == nil then
return nil
end
return sitelink.title
end
-- Get the best statements with the given property id
--
-- @param {string} propertyId
methodtable.getBestStatements = function( entity, propertyId )
if entity.claims == nil or not entity.claims[propertyId] then
return {}
end
local statements = {}
local bestRank = 'normal'
for k, statement in pairs( entity.claims[propertyId] ) do
if statement.rank == bestRank then
statements[#statements + 1] = statement
elseif statement.rank == 'preferred' then
statements = { statement }
bestRank = 'preferred'
end
end
return statements
end
-- Get a table with all property ids attached to the entity.
methodtable.getProperties = function( entity )
if entity.claims == nil then
return {}
end
-- Get the keys (property ids)
local properties = {}
local n = 0
for k, v in pairs( entity.claims ) do
n = n + 1
properties[n] = k
end
return properties
end
-- Get the formatted value of the claims with the given property id
--
-- @param {table} entity
-- @param {string} phpFormatterFunction
-- @param {string} propertyLabelOrId
-- @param {table} [acceptableRanks]
local formatValuesByPropertyId = function( entity, phpFormatterFunction, propertyLabelOrId, acceptableRanks )
acceptableRanks = acceptableRanks or nil
local formatted = php[phpFormatterFunction](
entity.id,
propertyLabelOrId,
acceptableRanks
)
local label
if propertyLabelOrId:match( '^P%d+$' ) then
label = mw.wikibase.label( propertyLabelOrId )
end
if label == nil then
-- Make the label fallback on the entity id for convenience/ consistency
label = propertyLabelOrId
end
return {
value = formatted,
label = label
}
end
-- Format the main Snaks belonging to a Statement (which is identified by a PropertyId
-- or the label of a Property) as wikitext escaped plain text.
--
-- @param {string} propertyLabelOrId
-- @param {table} [acceptableRanks]
methodtable.formatPropertyValues = function( entity, propertyLabelOrId, acceptableRanks )
checkType( 'formatPropertyValues', 1, propertyLabelOrId, 'string' )
checkTypeMulti( 'formatPropertyValues', 2, acceptableRanks, { 'table', 'nil' } )
return formatValuesByPropertyId(
entity,
'formatPropertyValues',
propertyLabelOrId,
acceptableRanks
);
end
-- Format the main Snaks belonging to a Statement (which is identified by a PropertyId
-- or the label of a Property) as rich wikitext.
--
-- @param {string} propertyLabelOrId
-- @param {table} [acceptableRanks]
methodtable.formatStatements = function( entity, propertyLabelOrId, acceptableRanks )
checkType( 'formatStatements', 1, propertyLabelOrId, 'string' )
checkTypeMulti( 'formatStatements', 2, acceptableRanks, { 'table', 'nil' } )
return formatValuesByPropertyId(
entity,
'formatStatements',
propertyLabelOrId,
acceptableRanks
);
end
mw.wikibase.entity = entity
package.loaded['mw.wikibase.entity'] = entity
mw_interface = nil
return entity

View File

@@ -0,0 +1,418 @@
--XOWA:updated 2018-06-21
--[[
Registers and defines functions to access Wikibase through the Scribunto extension
Provides Lua setupInterface
@since 0.4
@license GNU GPL v2+
@author Jens Ohlig < jens.ohlig@wikimedia.de >
@author Marius Hoch < hoo@online.de >
@author Bene* < benestar.wikimedia@gmail.com >
]]
local wikibase = {}
-- xowa:bgn "uncache" entities; DATE:2014-07-26
local reset_entity_id = false
local reset_entity_cache = false
-- xowa:end
local util = require 'libraryUtil'
local checkType = util.checkType
local checkTypeMulti = util.checkTypeMulti
local cacheSize = 15 -- Size of the LRU cache being used to cache entities
local cacheOrder = {}
local entityCache = {}
-- Cache a given entity (can also be false, in case it doesn't exist).
--
-- @param entityId
-- @param entity
local cacheEntity = function( entityId, entity )
if #cacheOrder == cacheSize then
local entityIdToRemove = table.remove( cacheOrder, cacheSize )
entityCache[ entityIdToRemove ] = nil
end
table.insert( cacheOrder, 1, entityId )
entityCache[ entityId ] = entity
end
-- Retrieve an entity. Will return false in case it's known to not exist
-- and nil in case of a cache miss.
--
-- @param entityId
local getCachedEntity = function( entityId )
-- xowa:bgn
if reset_entity_cache then
reset_entity_cache = false
entityCache = {}
end
-- xowa:end
if entityCache[ entityId ] ~= nil then
for cacheOrderId, cacheOrderEntityId in pairs( cacheOrder ) do
if cacheOrderEntityId == entityId then
table.remove( cacheOrder, cacheOrderId )
break
end
end
table.insert( cacheOrder, 1, entityId )
end
return entityCache[ entityId ]
end
function wikibase.setupInterface()
local php = mw_interface
mw_interface = nil
-- Caching variable for the entity id string belonging to the current page (nil if page is not linked to an entity)
local pageEntityId = false
-- Get the entity id of the connected item, if id is nil. Cached.
local getIdOfConnectedItemIfNil = function( id )
if id == nil then
return wikibase.getEntityIdForCurrentPage()
end
return id
end
-- Get the mw.wikibase.entity object for a given id. Cached.
local getEntityObject = function( id )
local entity = getCachedEntity( id )
if entity == nil then
entity = php.getEntity( id )
if id ~= wikibase.getEntityIdForCurrentPage() then
-- Accessing an arbitrary entity is supposed to increment the expensive function count
php.incrementExpensiveFunctionCount()
end
if type( entity ) ~= 'table' then
entity = false
end
cacheEntity( id, entity )
end
if type( entity ) ~= 'table' then
return nil
end
-- Use a deep clone here, so that people can't modify the entity
return wikibase.entity.create( mw.clone( entity ) )
end
-- Get the entity id for the current page. Cached.
-- Nil if not linked to an entity.
wikibase.getEntityIdForCurrentPage = function()
-- xowa:bgn
if reset_entity_id then
reset_entity_id = false
pageEntityId = false
end
-- xowa:end
if pageEntityId == false then
pageEntityId = php.getEntityId( tostring( mw.title.getCurrentTitle().prefixedText ) )
end
return pageEntityId
end
-- Takes a page title string either in the local wiki or another wiki on the same cluster
-- specified by the global site identifier, and returns the item ID connected via a sitelink, if
-- one exists. Returns nil if there's no linked item.
--
-- @param {string} pageTitle
-- @param {string} [globalSiteId]
wikibase.getEntityIdForTitle = function( pageTitle, globalSiteId )
checkType( 'getEntityIdForTitle', 1, pageTitle, 'string' )
checkTypeMulti( 'getEntityIdForTitle', 2, globalSiteId, { 'string', 'nil' } )
return php.getEntityId( pageTitle, globalSiteId )
end
-- Get the mw.wikibase.entity object for the current page or for the
-- specified id.
--
-- @param {string} [id]
wikibase.getEntity = function( id )
checkTypeMulti( 'getEntity', 1, id, { 'string', 'nil' } )
id = getIdOfConnectedItemIfNil( id )
if id == nil then
return nil
end
-- if not php.getSetting( 'allowArbitraryDataAccess' ) and id ~= wikibase.getEntityIdForCurrentPage() then
-- error( 'Access to arbitrary entities has been disabled.', 2 )
-- end
return getEntityObject( id )
end
-- getEntityObject is an alias for getEntity as these used to be different.
wikibase.getEntityObject = wikibase.getEntity
-- @param {string} entityId
-- @param {string} propertyId
-- @param {string} funcName for error logging
-- @param {string} rank Which statements to include. Either "best" or "all".
local getEntityStatements = function( entityId, propertyId, funcName, rank )
-- if not php.getSetting( 'allowArbitraryDataAccess' ) and entityId ~= wikibase.getEntityIdForCurrentPage() then
-- error( 'Access to arbitrary entities has been disabled.', 2 )
-- end
checkType( funcName, 1, entityId, 'string' )
checkType( funcName, 2, propertyId, 'string' )
local statements = php.getEntityStatements( entityId, propertyId, rank )
if statements and statements[propertyId] then
return statements[propertyId]
end
return {}
end
-- Returns a table with the "best" statements matching the given property ID on the given entity
-- ID. The definition of "best" is that the function will return "preferred" statements, if
-- there are any, otherwise "normal" ranked statements. It will never return "deprecated"
-- statements. This is what you usually want when surfacing values to an ordinary reader.
--
-- @param {string} entityId
-- @param {string} propertyId
wikibase.getBestStatements = function( entityId, propertyId )
return getEntityStatements( entityId, propertyId, 'getBestStatements', 'best' )
end
-- Returns a table with all statements (including all ranks, even "deprecated") matching the
-- given property ID on the given entity ID.
--
-- @param {string} entityId
-- @param {string} propertyId
wikibase.getAllStatements = function( entityId, propertyId )
return getEntityStatements( entityId, propertyId, 'getAllStatements', 'all' )
end
-- Get the URL for the given entity id, if specified, or of the
-- connected entity, if exists.
--
-- @param {string} [id]
wikibase.getEntityUrl = function( id )
checkTypeMulti( 'getEntityUrl', 1, id, { 'string', 'nil' } )
id = getIdOfConnectedItemIfNil( id )
if id == nil then
return nil
end
return php.getEntityUrl( id )
end
-- Get the label, label language for the given entity id, if specified,
-- or of the connected entity, if exists.
--
-- @param {string} [id]
wikibase.getLabelWithLang = function( id )
checkTypeMulti( 'getLabelWithLang', 1, id, { 'string', 'nil' } )
id = getIdOfConnectedItemIfNil( id )
if id == nil then
return nil, nil
end
return php.getLabel( id )
end
-- Like wikibase.getLabelWithLang, but only returns the plain label.
--
-- @param {string} [id]
wikibase.getLabel = function( id )
checkTypeMulti( 'getLabel', 1, id, { 'string', 'nil' } )
local label = wikibase.getLabelWithLang( id )
return label
end
-- Legacy alias for getLabel
wikibase.label = wikibase.getLabel
-- Get the label in languageCode for the given entity id.
--
-- @param {string} id
-- @param {string} languageCode
wikibase.getLabelByLang = function( id, languageCode )
checkType( 'getLabelByLang', 1, id, 'string' )
checkType( 'getLabelByLang', 2, languageCode, 'string' )
return php.getLabelByLanguage( id, languageCode )
end
-- Get the description, description language for the given entity id, if specified,
-- or of the connected entity, if exists.
--
-- @param {string} [id]
wikibase.getDescriptionWithLang = function( id )
checkTypeMulti( 'getDescriptionWithLang', 1, id, { 'string', 'nil' } )
id = getIdOfConnectedItemIfNil( id )
if id == nil then
return nil, nil
end
return php.getDescription( id )
end
-- Like wikibase.getDescriptionWithLang, but only returns the plain description.
--
-- @param {string} [id]
wikibase.getDescription = function( id )
checkTypeMulti( 'getDescription', 1, id, { 'string', 'nil' } )
local description = wikibase.getDescriptionWithLang( id )
return description
end
-- Legacy alias for getDescription
wikibase.description = wikibase.getDescription
-- Get the local sitelink title for the given entity id.
--
-- @param {string} itemId
-- @param {string} [globalSiteId]
wikibase.getSitelink = function( itemId, globalSiteId )
checkType( 'getSitelink', 1, itemId, 'string' )
checkTypeMulti( 'getSitelink', 2, globalSiteId, { 'string', 'nil' } )
return php.getSiteLinkPageName( itemId, globalSiteId )
end
-- Legacy alias for getSitelink
wikibase.sitelink = wikibase.getSitelink
-- Is this a valid (parseable) entity id?
--
-- @param {string} entityIdSerialization
wikibase.isValidEntityId = function( entityIdSerialization )
checkType( 'isValidEntityId', 1, entityIdSerialization, 'string' )
return php.isValidEntityId( entityIdSerialization )
end
-- Does the entity in question exist?
--
-- @param {string} entityId
wikibase.entityExists = function( entityId )
checkType( 'entityExists', 1, entityId, 'string' )
if not php.getSetting( 'allowArbitraryDataAccess' ) and entityId ~= wikibase.getEntityIdForCurrentPage() then
error( 'Access to arbitrary entities has been disabled.', 2 )
end
return php.entityExists( entityId )
end
-- Render a Snak value from its serialization as wikitext escaped plain text.
--
-- @param {table} snakSerialization
wikibase.renderSnak = function( snakSerialization )
checkType( 'renderSnak', 1, snakSerialization, 'table' )
return php.renderSnak( snakSerialization )
end
-- Render a Snak value from its serialization as rich wikitext.
--
-- @param {table} snakSerialization
wikibase.formatValue = function( snakSerialization )
checkType( 'formatValue', 1, snakSerialization, 'table' )
return php.formatValue( snakSerialization )
end
-- Render a list of Snak values from their serialization as wikitext escaped plain text.
--
-- @param {table} snaksSerialization
wikibase.renderSnaks = function( snaksSerialization )
checkType( 'renderSnaks', 1, snaksSerialization, 'table' )
return php.renderSnaks( snaksSerialization )
end
-- Render a list of Snak values from their serialization as rich wikitext.
--
-- @param {table} snaksSerialization
wikibase.formatValues = function( snaksSerialization )
checkType( 'formatValues', 1, snaksSerialization, 'table' )
return php.formatValues( snaksSerialization )
end
-- Returns a property id for the given label or id
--
-- @param {string} propertyLabelOrId
wikibase.resolvePropertyId = function( propertyLabelOrId )
checkType( 'resolvePropertyId', 1, propertyLabelOrId, 'string' )
return php.resolvePropertyId( propertyLabelOrId )
end
-- Returns a table of the given property IDs ordered
--
-- @param {table} propertyIds
wikibase.orderProperties = function( propertyIds )
checkType( 'orderProperties', 1, propertyIds, 'table' )
return php.orderProperties( propertyIds )
end
-- Returns an ordered table of serialized property IDs
wikibase.getPropertyOrder = function()
return php.getPropertyOrder()
end
-- Get the closest referenced entity (out of toIds), from a given entity.
--
-- @param {string} fromEntityId
-- @param {string} propertyId
-- @param {table} toIds
wikibase.getReferencedEntityId = function( fromEntityId, propertyId, toIds )
checkType( 'getReferencedEntityId', 1, fromEntityId, 'string' )
checkType( 'getReferencedEntityId', 2, propertyId, 'string' )
checkType( 'getReferencedEntityId', 3, toIds, 'table' )
-- Check the type of all toId values, Scribunto has no function for this yet (T196048)
for i, toId in ipairs( toIds ) do
if type( toId ) ~= 'string' then
error(
'toIds value at index ' .. i .. ' must be string, ' .. type( toId ) .. ' given.',
1
)
end
end
if not php.getSetting( 'allowArbitraryDataAccess' ) then
error( 'Access to arbitrary entities has been disabled.', 2 )
end
return php.getReferencedEntityId( fromEntityId, propertyId, toIds )
end
mw = mw or {}
mw.wikibase = wikibase
package.loaded['mw.wikibase'] = wikibase
wikibase.setupInterface = nil
end
-- xowa:bgn
function wikibase.notify_page_changed()
reset_entity_id = true
reset_entity_cache = true
end
-- xowa:end
return wikibase

View File

@@ -0,0 +1,134 @@
-- This file is for anything that needs to be set up before a Lua engine can
-- start. Things in this file may run more than once, so avoid putting anything
-- other than function definitions in it. Also, because this can run before
-- PHP can do anything, mw_interface is unavailable here.
mw = mw or {}
-- Extend pairs and ipairs to recognize __pairs and __ipairs, if they don't already
do
local t = {}
setmetatable( t, { __pairs = function() return 1, 2, 3 end } )
local f = pairs( t )
if f ~= 1 then
local old_pairs = pairs
pairs = function ( t )
local mt = getmetatable( t )
local f, s, var = ( mt and mt.__pairs or old_pairs )( t )
return f, s, var
end
local old_ipairs = ipairs
ipairs = function ( t )
local mt = getmetatable( t )
local f, s, var = ( mt and mt.__ipairs or old_ipairs )( t )
return f, s, var
end
end
end
--- Do a "deep copy" of a table or other value.
function mw.clone( val )
local tableRefs = {}
local function recursiveClone( val )
if type( val ) == 'table' then
-- Encode circular references correctly
if tableRefs[val] ~= nil then
return tableRefs[val]
end
local retVal
retVal = {}
tableRefs[val] = retVal
-- Copy metatable
if getmetatable( val ) then
setmetatable( retVal, recursiveClone( getmetatable( val ) ) )
end
for key, elt in pairs( val ) do
retVal[key] = recursiveClone( elt )
end
return retVal
else
return val
end
end
return recursiveClone( val )
end
--- Make isolation-safe setfenv and getfenv functions
--
-- @param protectedEnvironments A table where the keys are protected environment
-- tables. These environments cannot be accessed with getfenv(), and
-- functions with these environments cannot be modified or accessed using
-- integer indexes to setfenv(). However, functions with these environments
-- can have their environment set with setfenv() with a function value
-- argument.
--
-- @param protectedFunctions A table where the keys are protected functions,
-- which cannot have their environments set by setfenv() with a function
-- value argument.
--
-- @return setfenv
-- @return getfenv
function mw.makeProtectedEnvFuncs( protectedEnvironments, protectedFunctions )
local old_setfenv = setfenv
local old_getfenv = getfenv
local function my_setfenv( func, newEnv )
if type( func ) == 'number' then
local stackIndex = math.floor( func )
if stackIndex <= 0 then
error( "'setfenv' cannot set the global environment, it is protected", 2 )
end
if stackIndex > 10 then
error( "'setfenv' cannot set an environment at a level greater than 10", 2 )
end
-- Add one because we are still in Lua and 1 is right here
stackIndex = stackIndex + 1
local env = old_getfenv( stackIndex )
if env == nil or protectedEnvironments[ env ] then
error( "'setfenv' cannot set the requested environment, it is protected", 2 )
end
func = old_setfenv( stackIndex, newEnv )
elseif type( func ) == 'function' then
if protectedFunctions[func] then
error( "'setfenv' cannot be called on a protected function", 2 )
end
local env = old_getfenv( func )
if env == nil or protectedEnvironments[ env ] then
error( "'setfenv' cannot set the requested environment, it is protected", 2 )
end
old_setfenv( func, newEnv )
else
error( "'setfenv' can only be called with a function or integer as the first argument", 2 )
end
return func
end
local function my_getfenv( func )
local env
if type( func ) == 'number' then
if func <= 0 then
error( "'getfenv' cannot get the global environment" )
end
env = old_getfenv( func + 1 )
elseif type( func ) == 'function' then
env = old_getfenv( func )
else
error( "'getfenv' cannot get the global environment" )
end
if protectedEnvironments[env] then
return nil
else
return env
end
end
return my_setfenv, my_getfenv
end
return mw

View File

@@ -0,0 +1,127 @@
--[[
-- A package library similar to the one that comes with Lua 5.1, but without
-- the local filesystem access. Based on Compat-5.1 which comes with the
-- following license notice:
--
-- Copyright © 2004-2006 The Kepler Project.
--
-- 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.
--]]
local assert, error, ipairs, setmetatable, type = assert, error, ipairs, setmetatable, type
local format = string.format
--
-- avoid overwriting the package table if it's already there
--
package = package or {}
local _PACKAGE = package
package.loaded = package.loaded or {}
package.loaded.debug = debug
package.loaded.string = string
package.loaded.math = math
package.loaded.io = io
package.loaded.os = os
package.loaded.table = table
package.loaded._G = _G
package.loaded.coroutine = coroutine
package.loaded.package = package
local _LOADED = package.loaded
--
-- avoid overwriting the package.preload table if it's already there
--
package.preload = package.preload or {}
local _PRELOAD = package.preload
--
-- check whether library is already loaded
--
local function loader_preload (name)
assert (type(name) == "string", format (
"bad argument #1 to `require' (string expected, got %s)", type(name)))
assert (type(_PRELOAD) == "table", "`package.preload' must be a table")
return _PRELOAD[name]
end
-- create `loaders' table
package.loaders = package.loaders or { loader_preload }
local _LOADERS = package.loaders
--
-- iterate over available loaders
--
local function load (name, loaders)
-- iterate over available loaders
assert (type (loaders) == "table", "`package.loaders' must be a table")
for i, loader in ipairs (loaders) do
local f = loader (name)
if f then
return f
end
end
error (format ("module `%s' not found", name))
end
-- sentinel
local sentinel = function () end
--
-- require
--
function _G.require (modname)
assert (type(modname) == "string", format (
"bad argument #1 to `require' (string expected, got %s)", type(modname)))
local p = _LOADED[modname]
if p then -- is it there?
if p == sentinel then
error (format ("loop or previous error loading module '%s'", modname))
end
return p -- package is already loaded
end
local init = load (modname, _LOADERS)
_LOADED[modname] = sentinel
local actual_arg = _G.arg
_G.arg = { modname }
local res = init (modname)
if res then
_LOADED[modname] = res
end
_G.arg = actual_arg
if _LOADED[modname] == sentinel then
_LOADED[modname] = true
end
return _LOADED[modname]
end
--
-- package.seeall function
--
function _PACKAGE.seeall (module)
local t = type(module)
assert (t == "table", "bad argument #1 to package.seeall (table expected, got "..t..")")
local meta = getmetatable (module)
if not meta then
meta = {}
setmetatable (module, meta)
end
meta.__index = _G
end

View File

@@ -0,0 +1,51 @@
This is ustring, a pure-Lua library to handle UTF-8 strings.
It implements generally the same interface as the standard string library, with
the following differences:
* Most functions work on codepoints rather than bytes or characters. Yes, this
means that even though "á" and "á" should appear identical and represent the
same character, the former is one codepoint (U+00E1) while the latter is two
(U+0061 U+0301).
* Added functions isutf8, byteoffset, codepoint, gcodepoint, toNFC, toNFD.
* No workalike for string.reverse is provided.
Contents:
* README - This file.
* ustring.lua - The main file for the library.
* string.lua - Extend the string metatable with methods from this library.
* upper.lua - Data table for ustring.upper.
* lower.lua - Data table for ustring.lower.
* charsets.lua - Data tables for pattern matching functions.
* make-tables.php - Regenerate upper.lua and lower.lua using PHP's multibyte
string library, and charsets.lua using PCRE.
* normalization-data.lua - Data tables for toNFC and toNFD.
* make-normalization-table.php - Regenerate normalization-data.lua based on the
file includes/normal/UtfNormalData.inc from MediaWiki core.
This library (consisting of the files described above) is released under the MIT
License:
Copyright (C) 2012 Brad Jorsch <bjorsch@wikimedia.org>
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.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,968 @@
-- This file is automatically generated by make-tables.php
return {
["A"] = "a",
["B"] = "b",
["C"] = "c",
["D"] = "d",
["E"] = "e",
["F"] = "f",
["G"] = "g",
["H"] = "h",
["I"] = "i",
["J"] = "j",
["K"] = "k",
["L"] = "l",
["M"] = "m",
["N"] = "n",
["O"] = "o",
["P"] = "p",
["Q"] = "q",
["R"] = "r",
["S"] = "s",
["T"] = "t",
["U"] = "u",
["V"] = "v",
["W"] = "w",
["X"] = "x",
["Y"] = "y",
["Z"] = "z",
["À"] = "à",
["Á"] = "á",
["Â"] = "â",
["Ã"] = "ã",
["Ä"] = "ä",
["Å"] = "å",
["Æ"] = "æ",
["Ç"] = "ç",
["È"] = "è",
["É"] = "é",
["Ê"] = "ê",
["Ë"] = "ë",
["Ì"] = "ì",
["Í"] = "í",
["Î"] = "î",
["Ï"] = "ï",
["Ð"] = "ð",
["Ñ"] = "ñ",
["Ò"] = "ò",
["Ó"] = "ó",
["Ô"] = "ô",
["Õ"] = "õ",
["Ö"] = "ö",
["Ø"] = "ø",
["Ù"] = "ù",
["Ú"] = "ú",
["Û"] = "û",
["Ü"] = "ü",
["Ý"] = "ý",
["Þ"] = "þ",
["Ā"] = "ā",
["Ă"] = "ă",
["Ą"] = "ą",
["Ć"] = "ć",
["Ĉ"] = "ĉ",
["Ċ"] = "ċ",
["Č"] = "č",
["Ď"] = "ď",
["Đ"] = "đ",
["Ē"] = "ē",
["Ĕ"] = "ĕ",
["Ė"] = "ė",
["Ę"] = "ę",
["Ě"] = "ě",
["Ĝ"] = "ĝ",
["Ğ"] = "ğ",
["Ġ"] = "ġ",
["Ģ"] = "ģ",
["Ĥ"] = "ĥ",
["Ħ"] = "ħ",
["Ĩ"] = "ĩ",
["Ī"] = "ī",
["Ĭ"] = "ĭ",
["Į"] = "į",
["İ"] = "i",
["IJ"] = "ij",
["Ĵ"] = "ĵ",
["Ķ"] = "ķ",
["Ĺ"] = "ĺ",
["Ļ"] = "ļ",
["Ľ"] = "ľ",
["Ŀ"] = "ŀ",
["Ł"] = "ł",
["Ń"] = "ń",
["Ņ"] = "ņ",
["Ň"] = "ň",
["Ŋ"] = "ŋ",
["Ō"] = "ō",
["Ŏ"] = "ŏ",
["Ő"] = "ő",
["Œ"] = "œ",
["Ŕ"] = "ŕ",
["Ŗ"] = "ŗ",
["Ř"] = "ř",
["Ś"] = "ś",
["Ŝ"] = "ŝ",
["Ş"] = "ş",
["Š"] = "š",
["Ţ"] = "ţ",
["Ť"] = "ť",
["Ŧ"] = "ŧ",
["Ũ"] = "ũ",
["Ū"] = "ū",
["Ŭ"] = "ŭ",
["Ů"] = "ů",
["Ű"] = "ű",
["Ų"] = "ų",
["Ŵ"] = "ŵ",
["Ŷ"] = "ŷ",
["Ÿ"] = "ÿ",
["Ź"] = "ź",
["Ż"] = "ż",
["Ž"] = "ž",
["Ɓ"] = "ɓ",
["Ƃ"] = "ƃ",
["Ƅ"] = "ƅ",
["Ɔ"] = "ɔ",
["Ƈ"] = "ƈ",
["Ɖ"] = "ɖ",
["Ɗ"] = "ɗ",
["Ƌ"] = "ƌ",
["Ǝ"] = "ǝ",
["Ə"] = "ə",
["Ɛ"] = "ɛ",
["Ƒ"] = "ƒ",
["Ɠ"] = "ɠ",
["Ɣ"] = "ɣ",
["Ɩ"] = "ɩ",
["Ɨ"] = "ɨ",
["Ƙ"] = "ƙ",
["Ɯ"] = "ɯ",
["Ɲ"] = "ɲ",
["Ɵ"] = "ɵ",
["Ơ"] = "ơ",
["Ƣ"] = "ƣ",
["Ƥ"] = "ƥ",
["Ʀ"] = "ʀ",
["Ƨ"] = "ƨ",
["Ʃ"] = "ʃ",
["Ƭ"] = "ƭ",
["Ʈ"] = "ʈ",
["Ư"] = "ư",
["Ʊ"] = "ʊ",
["Ʋ"] = "ʋ",
["Ƴ"] = "ƴ",
["Ƶ"] = "ƶ",
["Ʒ"] = "ʒ",
["Ƹ"] = "ƹ",
["Ƽ"] = "ƽ",
["DŽ"] = "dž",
["LJ"] = "lj",
["NJ"] = "nj",
["Ǎ"] = "ǎ",
["Ǐ"] = "ǐ",
["Ǒ"] = "ǒ",
["Ǔ"] = "ǔ",
["Ǖ"] = "ǖ",
["Ǘ"] = "ǘ",
["Ǚ"] = "ǚ",
["Ǜ"] = "ǜ",
["Ǟ"] = "ǟ",
["Ǡ"] = "ǡ",
["Ǣ"] = "ǣ",
["Ǥ"] = "ǥ",
["Ǧ"] = "ǧ",
["Ǩ"] = "ǩ",
["Ǫ"] = "ǫ",
["Ǭ"] = "ǭ",
["Ǯ"] = "ǯ",
["DZ"] = "dz",
["Ǵ"] = "ǵ",
["Ƕ"] = "ƕ",
["Ƿ"] = "ƿ",
["Ǹ"] = "ǹ",
["Ǻ"] = "ǻ",
["Ǽ"] = "ǽ",
["Ǿ"] = "ǿ",
["Ȁ"] = "ȁ",
["Ȃ"] = "ȃ",
["Ȅ"] = "ȅ",
["Ȇ"] = "ȇ",
["Ȉ"] = "ȉ",
["Ȋ"] = "ȋ",
["Ȍ"] = "ȍ",
["Ȏ"] = "ȏ",
["Ȑ"] = "ȑ",
["Ȓ"] = "ȓ",
["Ȕ"] = "ȕ",
["Ȗ"] = "ȗ",
["Ș"] = "ș",
["Ț"] = "ț",
["Ȝ"] = "ȝ",
["Ȟ"] = "ȟ",
["Ƞ"] = "ƞ",
["Ȣ"] = "ȣ",
["Ȥ"] = "ȥ",
["Ȧ"] = "ȧ",
["Ȩ"] = "ȩ",
["Ȫ"] = "ȫ",
["Ȭ"] = "ȭ",
["Ȯ"] = "ȯ",
["Ȱ"] = "ȱ",
["Ȳ"] = "ȳ",
["Ⱥ"] = "",
["Ȼ"] = "ȼ",
["Ƚ"] = "ƚ",
["Ⱦ"] = "",
["Ɂ"] = "ɂ",
["Ƀ"] = "ƀ",
["Ʉ"] = "ʉ",
["Ʌ"] = "ʌ",
["Ɇ"] = "ɇ",
["Ɉ"] = "ɉ",
["Ɋ"] = "ɋ",
["Ɍ"] = "ɍ",
["Ɏ"] = "ɏ",
["Ͱ"] = "ͱ",
["Ͳ"] = "ͳ",
["Ͷ"] = "ͷ",
["Ά"] = "ά",
["Έ"] = "έ",
["Ή"] = "ή",
["Ί"] = "ί",
["Ό"] = "ό",
["Ύ"] = "ύ",
["Ώ"] = "ώ",
["Α"] = "α",
["Β"] = "β",
["Γ"] = "γ",
["Δ"] = "δ",
["Ε"] = "ε",
["Ζ"] = "ζ",
["Η"] = "η",
["Θ"] = "θ",
["Ι"] = "ι",
["Κ"] = "κ",
["Λ"] = "λ",
["Μ"] = "μ",
["Ν"] = "ν",
["Ξ"] = "ξ",
["Ο"] = "ο",
["Π"] = "π",
["Ρ"] = "ρ",
["Σ"] = "σ",
["Τ"] = "τ",
["Υ"] = "υ",
["Φ"] = "φ",
["Χ"] = "χ",
["Ψ"] = "ψ",
["Ω"] = "ω",
["Ϊ"] = "ϊ",
["Ϋ"] = "ϋ",
["Ϗ"] = "ϗ",
["Ϙ"] = "ϙ",
["Ϛ"] = "ϛ",
["Ϝ"] = "ϝ",
["Ϟ"] = "ϟ",
["Ϡ"] = "ϡ",
["Ϣ"] = "ϣ",
["Ϥ"] = "ϥ",
["Ϧ"] = "ϧ",
["Ϩ"] = "ϩ",
["Ϫ"] = "ϫ",
["Ϭ"] = "ϭ",
["Ϯ"] = "ϯ",
["ϴ"] = "θ",
["Ϸ"] = "ϸ",
["Ϲ"] = "ϲ",
["Ϻ"] = "ϻ",
["Ͻ"] = "ͻ",
["Ͼ"] = "ͼ",
["Ͽ"] = "ͽ",
["Ѐ"] = "ѐ",
["Ё"] = "ё",
["Ђ"] = "ђ",
["Ѓ"] = "ѓ",
["Є"] = "є",
["Ѕ"] = "ѕ",
["І"] = "і",
["Ї"] = "ї",
["Ј"] = "ј",
["Љ"] = "љ",
["Њ"] = "њ",
["Ћ"] = "ћ",
["Ќ"] = "ќ",
["Ѝ"] = "ѝ",
["Ў"] = "ў",
["Џ"] = "џ",
["А"] = "а",
["Б"] = "б",
["В"] = "в",
["Г"] = "г",
["Д"] = "д",
["Е"] = "е",
["Ж"] = "ж",
["З"] = "з",
["И"] = "и",
["Й"] = "й",
["К"] = "к",
["Л"] = "л",
["М"] = "м",
["Н"] = "н",
["О"] = "о",
["П"] = "п",
["Р"] = "р",
["С"] = "с",
["Т"] = "т",
["У"] = "у",
["Ф"] = "ф",
["Х"] = "х",
["Ц"] = "ц",
["Ч"] = "ч",
["Ш"] = "ш",
["Щ"] = "щ",
["Ъ"] = "ъ",
["Ы"] = "ы",
["Ь"] = "ь",
["Э"] = "э",
["Ю"] = "ю",
["Я"] = "я",
["Ѡ"] = "ѡ",
["Ѣ"] = "ѣ",
["Ѥ"] = "ѥ",
["Ѧ"] = "ѧ",
["Ѩ"] = "ѩ",
["Ѫ"] = "ѫ",
["Ѭ"] = "ѭ",
["Ѯ"] = "ѯ",
["Ѱ"] = "ѱ",
["Ѳ"] = "ѳ",
["Ѵ"] = "ѵ",
["Ѷ"] = "ѷ",
["Ѹ"] = "ѹ",
["Ѻ"] = "ѻ",
["Ѽ"] = "ѽ",
["Ѿ"] = "ѿ",
["Ҁ"] = "ҁ",
["Ҋ"] = "ҋ",
["Ҍ"] = "ҍ",
["Ҏ"] = "ҏ",
["Ґ"] = "ґ",
["Ғ"] = "ғ",
["Ҕ"] = "ҕ",
["Җ"] = "җ",
["Ҙ"] = "ҙ",
["Қ"] = "қ",
["Ҝ"] = "ҝ",
["Ҟ"] = "ҟ",
["Ҡ"] = "ҡ",
["Ң"] = "ң",
["Ҥ"] = "ҥ",
["Ҧ"] = "ҧ",
["Ҩ"] = "ҩ",
["Ҫ"] = "ҫ",
["Ҭ"] = "ҭ",
["Ү"] = "ү",
["Ұ"] = "ұ",
["Ҳ"] = "ҳ",
["Ҵ"] = "ҵ",
["Ҷ"] = "ҷ",
["Ҹ"] = "ҹ",
["Һ"] = "һ",
["Ҽ"] = "ҽ",
["Ҿ"] = "ҿ",
["Ӏ"] = "ӏ",
["Ӂ"] = "ӂ",
["Ӄ"] = "ӄ",
["Ӆ"] = "ӆ",
["Ӈ"] = "ӈ",
["Ӊ"] = "ӊ",
["Ӌ"] = "ӌ",
["Ӎ"] = "ӎ",
["Ӑ"] = "ӑ",
["Ӓ"] = "ӓ",
["Ӕ"] = "ӕ",
["Ӗ"] = "ӗ",
["Ә"] = "ә",
["Ӛ"] = "ӛ",
["Ӝ"] = "ӝ",
["Ӟ"] = "ӟ",
["Ӡ"] = "ӡ",
["Ӣ"] = "ӣ",
["Ӥ"] = "ӥ",
["Ӧ"] = "ӧ",
["Ө"] = "ө",
["Ӫ"] = "ӫ",
["Ӭ"] = "ӭ",
["Ӯ"] = "ӯ",
["Ӱ"] = "ӱ",
["Ӳ"] = "ӳ",
["Ӵ"] = "ӵ",
["Ӷ"] = "ӷ",
["Ӹ"] = "ӹ",
["Ӻ"] = "ӻ",
["Ӽ"] = "ӽ",
["Ӿ"] = "ӿ",
["Ԁ"] = "ԁ",
["Ԃ"] = "ԃ",
["Ԅ"] = "ԅ",
["Ԇ"] = "ԇ",
["Ԉ"] = "ԉ",
["Ԋ"] = "ԋ",
["Ԍ"] = "ԍ",
["Ԏ"] = "ԏ",
["Ԑ"] = "ԑ",
["Ԓ"] = "ԓ",
["Ԕ"] = "ԕ",
["Ԗ"] = "ԗ",
["Ԙ"] = "ԙ",
["Ԛ"] = "ԛ",
["Ԝ"] = "ԝ",
["Ԟ"] = "ԟ",
["Ԡ"] = "ԡ",
["Ԣ"] = "ԣ",
["Ԥ"] = "ԥ",
["Ԧ"] = "ԧ",
["Ա"] = "ա",
["Բ"] = "բ",
["Գ"] = "գ",
["Դ"] = "դ",
["Ե"] = "ե",
["Զ"] = "զ",
["Է"] = "է",
["Ը"] = "ը",
["Թ"] = "թ",
["Ժ"] = "ժ",
["Ի"] = "ի",
["Լ"] = "լ",
["Խ"] = "խ",
["Ծ"] = "ծ",
["Կ"] = "կ",
["Հ"] = "հ",
["Ձ"] = "ձ",
["Ղ"] = "ղ",
["Ճ"] = "ճ",
["Մ"] = "մ",
["Յ"] = "յ",
["Ն"] = "ն",
["Շ"] = "շ",
["Ո"] = "ո",
["Չ"] = "չ",
["Պ"] = "պ",
["Ջ"] = "ջ",
["Ռ"] = "ռ",
["Ս"] = "ս",
["Վ"] = "վ",
["Տ"] = "տ",
["Ր"] = "ր",
["Ց"] = "ց",
["Ւ"] = "ւ",
["Փ"] = "փ",
["Ք"] = "ք",
["Օ"] = "օ",
["Ֆ"] = "ֆ",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "ḿ",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "ṿ",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "ß",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "ế",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "ỿ",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
["Ἷ"] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "ω",
[""] = "k",
[""] = "å",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "ⰿ",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "ɫ",
[""] = "",
[""] = "ɽ",
[""] = "",
[""] = "",
[""] = "",
[""] = "ɑ",
[""] = "ɱ",
[""] = "ɐ",
[""] = "ɒ",
[""] = "",
[""] = "",
[""] = "ȿ",
["Ɀ"] = "ɀ",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "ⲿ",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "ɥ",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
[""] = "",
["𐐀"] = "𐐨",
["𐐁"] = "𐐩",
["𐐂"] = "𐐪",
["𐐃"] = "𐐫",
["𐐄"] = "𐐬",
["𐐅"] = "𐐭",
["𐐆"] = "𐐮",
["𐐇"] = "𐐯",
["𐐈"] = "𐐰",
["𐐉"] = "𐐱",
["𐐊"] = "𐐲",
["𐐋"] = "𐐳",
["𐐌"] = "𐐴",
["𐐍"] = "𐐵",
["𐐎"] = "𐐶",
["𐐏"] = "𐐷",
["𐐐"] = "𐐸",
["𐐑"] = "𐐹",
["𐐒"] = "𐐺",
["𐐓"] = "𐐻",
["𐐔"] = "𐐼",
["𐐕"] = "𐐽",
["𐐖"] = "𐐾",
["𐐗"] = "𐐿",
["𐐘"] = "𐑀",
["𐐙"] = "𐑁",
["𐐚"] = "𐑂",
["𐐛"] = "𐑃",
["𐐜"] = "𐑄",
["𐐝"] = "𐑅",
["𐐞"] = "𐑆",
["𐐟"] = "𐑇",
["𐐠"] = "𐑈",
["𐐡"] = "𐑉",
["𐐢"] = "𐑊",
["𐐣"] = "𐑋",
["𐐤"] = "𐑌",
["𐐥"] = "𐑍",
["𐐦"] = "𐑎",
["𐐧"] = "𐑏",
}

View File

@@ -0,0 +1,185 @@
#!/usr/bin/php
<?
if ( PHP_SAPI !== 'cli' ) {
die( "This script may only be executed from the command line.\n" );
}
$datafile = null;
if ( count( $argv ) > 1 ) {
$datafile = $argv[1];
if ( !file_exists( $datafile ) ) {
die( "The specified file '$datafile' does not exist\n" );
}
} else {
foreach( array(
__DIR__ . '/../../../../../core/includes/normal/UtfNormalData.inc',
__DIR__ . '/../../../../../includes/normal/UtfNormalData.inc',
) as $tryfile ) {
$tryfile = realpath( $tryfile );
if ( file_exists( $tryfile ) ) {
$datafile = $tryfile;
break;
}
}
if ( !$datafile ) {
die( "Cannot find UtfNormalData.inc. Please specify the path explicitly.\n" );
}
}
echo "Loading data file $datafile...\n";
class UtfNormal {
static $utfCheckNFC = null;
static $utfCombiningClass = null;
static $utfCanonicalDecomp = null;
static $utfCanonicalComp = null;
}
require_once( $datafile );
if ( !UtfNormal::$utfCheckNFC ||
!UtfNormal::$utfCombiningClass ||
!UtfNormal::$utfCanonicalDecomp ||
!UtfNormal::$utfCanonicalComp
) {
die( "Data file $datafile did not contain needed data.\n" );
}
function uord( $c, $firstOnly ) {
$ret = unpack( 'N*', mb_convert_encoding( $c, 'UTF-32BE', 'UTF-8' ) );
return $firstOnly ? $ret[1] : $ret;
}
echo "Creating normalization table...\n";
$X = fopen( __DIR__ . '/normalization-data.lua', 'w' );
if ( !$X ) {
die( "Failed to open normalization-data.lua\n" );
}
fprintf( $X, "-- This file is automatically generated by make-normalization-table.php\n" );
fprintf( $X, "local normal = {\n" );
fprintf( $X, "\t-- Characters that might change depending on the following combiner\n" );
fprintf( $X, "\t-- (minus any that are themselves combiners, those are added later)\n" );
fprintf( $X, "\tcheck = {\n" );
foreach ( UtfNormal::$utfCheckNFC as $k => $v ) {
if ( isset( UtfNormal::$utfCombiningClass[$k] ) ) {
// Skip, because it's in the other table already
continue;
}
fprintf( $X, "\t\t[0x%06x] = 1,\n", uord( $k, true ) );
}
fprintf( $X, "\t},\n\n" );
fprintf( $X, "\t-- Combining characters, mapped to combining class\n" );
fprintf( $X, "\tcombclass = {\n" );
$comb = array();
foreach ( UtfNormal::$utfCombiningClass as $k => $v ) {
$cp = uord( $k, true );
$comb[$cp] = 1;
fprintf( $X, "\t\t[0x%06x] = %d,\n", $cp, $v );
}
fprintf( $X, "\t},\n\n" );
fprintf( $X, "\t-- Characters mapped to what they decompose to\n" );
fprintf( $X, "\t-- Note Hangul to Jamo is done separately below\n" );
fprintf( $X, "\tdecomp = {\n" );
foreach ( UtfNormal::$utfCanonicalDecomp as $k => $v ) {
fprintf( $X, "\t\t[0x%06x] = { ", uord( $k, true ) );
$fmt = "0x%06x";
foreach ( uord( $v, false ) as $c ) {
fprintf( $X, $fmt, $c );
$fmt = ", 0x%06x";
}
fprintf( $X, " },\n" );
}
fprintf( $X, "\t},\n\n" );
fprintf( $X, "\t-- Character-pairs mapped to what they compose to\n" );
fprintf( $X, "\t-- Note Jamo to Hangul is done separately below\n" );
$t = array();
foreach ( UtfNormal::$utfCanonicalComp as $k => $v ) {
$k = uord( $k, false );
if ( count( $k ) == 1 ) {
// No idea why these are in the file
continue;
}
if ( isset( $comb[$k[1]] ) ) {
// Non-starter, no idea why these are in the file either
continue;
}
$t[$k[1]][$k[2]] = uord( $v, true );
}
fprintf( $X, "\tcomp = {\n" );
ksort( $t );
foreach ( $t as $k1 => $v1 ) {
fprintf( $X, "\t\t[0x%06x] = {\n", $k1 );
ksort( $v1 );
foreach ( $v1 as $k2 => $v2 ) {
if ( $k2 < 0 ) {
fprintf( $X, "\t\t\t[-1] = 0x%06x,\n", $v2 );
} else {
fprintf( $X, "\t\t\t[0x%06x] = 0x%06x,\n", $k2, $v2 );
}
}
fprintf( $X, "\t\t},\n" );
}
fprintf( $X, "\t},\n" );
fprintf( $X, "}\n" );
fprintf( $X, "\n%s\n", <<<LUA
-- All combining characters need to be checked, so just do that
setmetatable( normal.check, { __index = normal.combclass } )
-- Handle Hangul to Jamo decomposition
setmetatable( normal.decomp, { __index = function ( _, k )
if k >= 0xac00 and k <= 0xd7a3 then
-- Decompose a Hangul syllable into Jamo
k = k - 0xac00
local ret = {
0x1100 + math.floor( k / 588 ),
0x1161 + math.floor( ( k % 588 ) / 28 )
}
if k % 28 ~= 0 then
ret[3] = 0x11a7 + ( k % 28 )
end
return ret
end
return nil
end } )
-- Handle Jamo to Hangul composition
local jamo_l_v_mt = { __index = function ( t, k )
if k >= 0x1161 and k <= 0x1175 then
-- Jamo leading + Jamo vowel
return t.base + 28 * ( k - 0x1161 )
end
return nil
end }
local hangul_jamo_mt = { __index = function ( t, k )
if k >= 0x11a7 and k <= 0x11c2 then
-- Hangul + jamo final
return t.base + k - 0x11a7
end
return nil
end }
setmetatable( normal.comp, { __index = function ( t, k )
if k >= 0x1100 and k <= 0x1112 then
-- Jamo leading, return a second table that combines with a Jamo vowel
local t2 = { base = 0xac00 + 588 * ( k - 0x1100 ) }
setmetatable( t2, jamo_l_v_mt )
t[k] = t2 -- cache it
return t2
elseif k >= 0xac00 and k <= 0xd7a3 and k % 28 == 16 then
-- Hangul. "k % 28 == 16" picks out just the ones that are
-- Jamo leading + vowel, no final. Return a second table that combines
-- with a Jamo final.
local t2 = { base = k }
setmetatable( t2, hangul_jamo_mt )
t[k] = t2 -- cache it
return t2
end
return nil
end } )
return normal
LUA
);
fclose( $X );

View File

@@ -0,0 +1,151 @@
#!/usr/bin/php
<?
if ( PHP_SAPI !== 'cli' ) {
die( "This script may only be executed from the command line.\n" );
}
$chars=array();
for ( $i = 0; $i <= 0x10ffff; $i++ ) {
if ( $i < 0xd800 || $i > 0xdfff ) { // Skip UTF-16 surrogates
$chars[$i] = mb_convert_encoding( pack( 'N', $i ), 'UTF-8', 'UTF-32BE' );
}
}
### Uppercase and Lowercase mappings
echo "Creating upper and lower tables...\n";
$L = fopen( __DIR__ . '/lower.lua', 'w' );
if ( !$L ) {
die( "Failed to open lower.lua\n" );
}
$U = fopen( __DIR__ . '/upper.lua', 'w' );
if ( !$U ) {
die( "Failed to open upper.lua\n" );
}
fprintf( $L, "-- This file is automatically generated by make-tables.php\n" );
fprintf( $L, "return {\n" );
fprintf( $U, "-- This file is automatically generated by make-tables.php\n" );
fprintf( $U, "return {\n" );
foreach ( $chars as $i => $c ) {
$l = mb_strtolower( $c, 'UTF-8' );
$u = mb_strtoupper( $c, 'UTF-8' );
if ( $c !== $l ) {
fprintf( $L, "\t[\"%s\"] = \"%s\",\n", $c, $l );
}
if ( $c !== $u ) {
fprintf( $U, "\t[\"%s\"] = \"%s\",\n", $c, $u );
}
}
fprintf( $L, "}\n" );
fprintf( $U, "}\n" );
fclose( $L );
fclose( $U );
### Pattern code mappings
echo "Creating charsets table...\n";
$X = fopen( __DIR__ . '/charsets.lua', 'w' );
if ( !$X ) {
die( "Failed to open charsets.lua\n" );
}
$pats = array(
// These should match the expressions in UstringLibrary::patternToRegex()
'a' => array( '\p{L}', 'lu' ),
'c' => array( '\p{Cc}', null ),
'd' => array( '\p{Nd}', null ),
'l' => array( '\p{Ll}', null ),
'p' => array( '\p{P}', null ),
's' => array( '\p{Xps}', null ),
'u' => array( '\p{Lu}', null ),
'w' => array( null, 'da' ), # '[\p{L}\p{Nd}]' exactly matches 'a' + 'd'
'x' => array( '[0-9A-Fa-f---]', null ),
'z' => array( '\0', null ),
);
$ranges = array();
function addRange( $k, $start, $end ) {
global $X, $ranges;
// Speed/memory tradeoff
if ( !( $start >= 0x20 && $start < 0x7f ) && $end - $start >= 10 ) {
$ranges[$k][] = sprintf( "c >= 0x%06x and c < 0x%06x", $start, $end );
} else {
for ( $i = $start; $i < $end; $i++ ) {
fprintf( $X, "\t\t[0x%06x] = 1,\n", $i );
}
}
}
fprintf( $X, "-- This file is automatically generated by make-tables.php\n" );
fprintf( $X, "local pats = {\n" );
foreach ( $pats as $k => $pp ) {
$ranges[$k] = array();
$re=$pp[0];
if ( !$re ) {
fprintf( $X, "\t[0x%02x] = {},\n", ord( $k ) );
continue;
}
$re2='fail';
if ( $pp[1] ) {
$re2 = array();
foreach ( str_split( $pp[1] ) as $p ) {
$re2[] = $pats[$p][0];
}
$re2=join( '|', $re2 );
}
fprintf( $X, "\t[0x%02x] = {\n", ord( $k ) );
$rstart = null;
foreach ( $chars as $i => $c ) {
if ( preg_match( "/^$re$/u", $c ) && !preg_match( "/^$re2$/u", $c ) ) {
if ( $rstart === null ) {
$rstart = $i;
}
} else {
if ( $rstart !== null ) {
addRange( $k, $rstart, $i );
$rstart = null;
}
}
}
if ( $rstart !== null ) {
addRange( $k, $rstart, 0x110000 );
}
fprintf( $X, "\t},\n" );
}
foreach ( $pats as $k => $pp ) {
$kk = strtoupper( $k );
fprintf( $X, "\t[0x%02x] = {},\n", ord( $kk ) );
}
fprintf( $X, "}\n" );
foreach ( $pats as $k => $pp ) {
$body = '';
$check = array();
if ( $pp[1] ) {
foreach ( str_split( $pp[1] ) as $p ) {
$check[] = sprintf( "pats[0x%02x][k]", ord( $p ) );
}
}
if ( $ranges[$k] ) {
$body = "\tlocal c = tonumber( k ) or 0/0;\n";
$check = array_merge( $check, $ranges[$k] );
}
if ( $check ) {
$body .= "\treturn " . join( " or\n\t\t", $check );
fprintf( $X, "setmetatable( pats[0x%02x], { __index = function ( t, k )\n%s\nend } )\n",
ord( $k ), $body );
}
}
foreach ( $pats as $k => $pp ) {
fprintf( $X, "setmetatable( pats[0x%02x], { __index = function ( t, k ) return k and not pats[0x%02x][k] end } )\n",
ord( strtoupper( $k ) ), ord( $k ) );
}
fprintf( $X, "\n-- For speed, cache printable ASCII characters in main tables\n" );
fprintf( $X, "for k, t in pairs( pats ) do\n" );
fprintf( $X, "\tif k >= 0x61 then\n" );
fprintf( $X, "\t\tfor i = 0x20, 0x7e do\n" );
fprintf( $X, "\t\t\tt[i] = t[i] or false\n" );
fprintf( $X, "\t\tend\n" );
fprintf( $X, "\tend\n" );
fprintf( $X, "end\n" );
fprintf( $X, "\nreturn pats\n" );
fclose( $X );

View File

@@ -0,0 +1,15 @@
string.isutf8 = ustring.isutf8
string.byteoffset = ustring.byteoffset
string.codepoint = ustring.codepoint
string.gcodepoint = ustring.gcodepoint
string.toNFC = ustring.toNFC
string.toNFD = ustring.toNFD
string.uchar = ustring.char
string.ulen = ustring.len
string.usub = ustring.sub
string.uupper = ustring.upper
string.ulower = ustring.lower
string.ufind = ustring.find
string.umatch = ustring.match
string.ugmatch = ustring.gmatch
string.ugsub = ustring.gsub

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,643 @@
MWServer = {}
--- Create a new MWServer object
function MWServer:new()
obj = {
nextChunkId = 1,
chunks = {},
xchunks = {},
protectedFunctions = {},
protectedEnvironments = {},
baseEnv = {}
}
setmetatable( obj, self )
self.__index = self
obj:init()
return obj
end
--- Initialise a new MWServer object
function MWServer:init()
self.baseEnv = self:newEnvironment()
for funcName, func in pairs( self ) do
if type(func) == 'function' then
self.protectedFunctions[func] = true
end
end
self.protectedEnvironments[_G] = true
end
--- Serve requests until exit is requested
function MWServer:execute()
self:dispatch( nil )
self:debug( 'MWServer:execute: returning' )
end
-- Convert a multiple-return-value or a ... into a count and a table
function MWServer:listToCountAndTable( ... )
return select( '#', ... ), { ... }
end
--- Call a PHP function
-- Raise an error if the PHP handler requests it. May return any number
-- of values.
--
-- @param id The function ID, specified by a registerLibrary message
-- @param nargs Count of function arguments
-- @param args The function arguments
-- @return The return values from the PHP function
function MWServer:call( id, nargs, args )
local result = self:dispatch( {
op = 'call',
id = id,
nargs = nargs,
args = args
} )
if result.op == 'return' then
return unpack( result.values, 1, result.nvalues )
elseif result.op == 'error' then
-- Raise an error in the actual user code that called the function
-- The level is 3 since our immediate caller is a closure
error( result.value, 3 )
else
self:internalError( 'MWServer:call: unexpected result op' )
end
end
--- Handle a "call" message from PHP. Call the relevant function.
--
-- @param message The message from PHP
-- @return A response message to send back to PHP
function MWServer:handleCall( message )
if not self.chunks[message.id] then
return {
op = 'error',
value = 'function id ' .. message.id .. ' does not exist'
}
end
local n, result = self:listToCountAndTable( xpcall(
function ()
return self.chunks[message.id]( unpack( message.args, 1, message.nargs ) )
end,
function ( err )
return MWServer:attachTrace( err )
end
) )
if result[1] then
-- table.remove( result, 1 ) renumbers from 2 to #result. But #result
-- is not necessarily "right" if result contains nils.
result = { unpack( result, 2, n ) }
return {
op = 'return',
nvalues = n - 1,
values = result
}
else
if result[2].value and result[2].trace then
return {
op = 'error',
value = result[2].value,
trace = result[2].trace,
}
else
return {
op = 'error',
value = result[2]
}
end
end
end
--- The xpcall() error handler for handleCall(). Modifies the error object
-- to include a structured backtrace
--
-- @param err The error object
-- @return The new error object
function MWServer:attachTrace( err )
return {
value = err,
trace = self:getStructuredTrace( 2 )
}
end
--- Handle a "loadString" message from PHP.
-- Load the function and return a chunk ID.
--
-- @param message The message from PHP
-- @return A response message to send back to PHP
function MWServer:handleLoadString( message )
if string.find( message.text, '\27Lua', 1, true ) then
return {
op = 'error',
value = 'cannot load code with a Lua binary chunk marker escape sequence in it'
}
end
local chunk, errorMsg = loadstring( message.text, message.chunkName )
if chunk then
setfenv( chunk, self.baseEnv )
local id = self:addChunk( chunk )
return {
op = 'return',
nvalues = 1,
values = {id}
}
else
return {
op = 'error',
value = errorMsg
}
end
end
--- Add a function value to the list of tracked chunks and return its associated ID.
-- Adding a chunk allows it to be referred to in messages from PHP.
--
-- @param chunk The function value
-- @return The chunk ID
function MWServer:addChunk( chunk )
local id = self.nextChunkId
self.nextChunkId = id + 1
self.chunks[id] = chunk
self.xchunks[chunk] = id
return id
end
--- Handle a "cleanupChunks" message from PHP.
-- Remove any chunks no longer referenced by PHP code.
--
-- @param message The message from PHP
-- @return A response message to send back to PHP
function MWServer:handleCleanupChunks( message )
for id, chunk in pairs( message.ids ) do -- XOWA: different from MW, in that message has chunks to cleanup, not chunks to preserv
local chunk_key = self.chunks[id];
-- dbg(id .. ':' .. tostring(chunk_key));
self.chunks[id] = nil
self.xchunks[chunk_key] = nil
end
return {
op = 'return',
nvalues = 0,
values = {}
}
end
--- Handle a "registerLibrary" message from PHP.
-- Add the relevant functions to the base environment.
--
-- @param message The message from PHP
-- @return The response message
function MWServer:handleRegisterLibrary( message )
local startPos = 1
local component
if not self.baseEnv[message.name] then
self.baseEnv[message.name] = {}
end
local t = self.baseEnv[message.name]
for name, id in pairs( message.functions ) do
t[name] = function( ... )
return self:call( id, self:listToCountAndTable( ... ) )
end
-- Protect the function against setfenv()
self.protectedFunctions[t[name]] = true
end
return {
op = 'return',
nvalues = 0,
values = {}
}
end
--- Handle a "wrapPhpFunction" message from PHP.
-- Create an anonymous function
--
-- @param message The message from PHP
-- @return The response message
function MWServer:handleWrapPhpFunction( message )
local id = message.id
local func = function( ... )
return self:call( id, self:listToCountAndTable( ... ) )
end
-- Protect the function against setfenv()
self.protectedFunctions[func] = true
return {
op = 'return',
nvalues = 1,
values = { func }
}
end
--- Handle a "getStatus" message from PHP
--
-- @param message The request message
-- @return The response message
function MWServer:handleGetStatus( message )
local nullRet = {
op = 'return',
nvalues = 0,
values = {}
}
local file = io.open( '/proc/self/stat' )
if not file then
return nullRet
end
local s = file:read('*a')
file:close()
local t = {}
for token in string.gmatch(s, '[^ ]+') do
t[#t + 1] = token
end
if #t < 22 then
return nullRet
end
return {
op = 'return',
nvalues = 1,
values = {{
pid = tonumber(t[1]),
time = tonumber(t[14]) + tonumber(t[15]) + tonumber(t[16]) + tonumber(t[17]),
vsize = tonumber(t[23]),
}}
}
end
--- The main request/response loop
--
-- Send a request message and return its matching reply message. Handle any
-- intervening requests (i.e. re-entrant calls) by dispatching them to the
-- relevant handler function.
--
-- The request message may optionally be omitted, to listen for request messages
-- without first sending a request of its own. Such a dispatch() call will
-- continue running until termination is requested by PHP. Typically, PHP does
-- this with a SIGTERM signal.
--
-- @param msgToPhp The message to send to PHP. Optional.
-- @return The matching response message
function MWServer:dispatch( msgToPhp )
if msgToPhp then
self:sendMessage( msgToPhp )
end
while true do
local msgFromPhp = self:receiveMessage()
local msgToPhp
local op = msgFromPhp.op
if op == 'return' or op == 'error' then
return msgFromPhp
elseif op == 'call' then
msgToPhp = self:handleCall( msgFromPhp )
self:sendMessage( msgToPhp )
elseif op == 'loadString' then
msgToPhp = self:handleLoadString( msgFromPhp )
self:sendMessage( msgToPhp )
elseif op == 'registerLibrary' then
msgToPhp = self:handleRegisterLibrary( msgFromPhp )
self:sendMessage( msgToPhp )
elseif op == 'wrapPhpFunction' then
msgToPhp = self:handleWrapPhpFunction( msgFromPhp )
self:sendMessage( msgToPhp )
elseif op == 'cleanupChunks' then
msgToPhp = self:handleCleanupChunks( msgFromPhp )
self:sendMessage( msgToPhp )
elseif op == 'getStatus' then
msgToPhp = self:handleGetStatus( msgFromPhp )
self:sendMessage( msgToPhp )
elseif op == 'quit' then
self:debug( 'MWServer:dispatch: quit message received' )
os.exit(0)
else
self:internalError( "Invalid message operation" )
end
end
end
--- Write a message to the debug output stream.
-- Some day this may be configurable, currently it just unconditionally writes
-- the message to stderr. The PHP host will redirect those errors to /dev/null
-- by default, but it can be configured to send them to a file.
--
-- @param s The message
function MWServer:debug( s )
if ( type(s) == 'string' ) then
io.stderr:write( s .. '\n' )
else
io.stderr:write( self:serialize( s ) .. '\n' )
end
end
--- Raise an internal error
-- Write a message to stderr and then exit with a failure status. This should
-- be called for errors which cannot be allowed to be caught with pcall().
--
-- This must be used for protocol errors, or indeed any error from a context
-- where a dispatch() call lies between the error source and a possible pcall()
-- handler. If dispatch() were terminated by a regular error() call, the
-- resulting protocol violation could lead to a deadlock.
--
-- @param msg The error message
function MWServer:internalError( msg )
io.stderr:write( debug.traceback( msg ) .. '\n' )
os.exit( 1 )
end
--- Raise an I/O error
-- Helper function for errors from the io and file modules, which may optionally
-- return an informative error message as their second return value.
function MWServer:ioError( header, info )
if type( info) == 'string' then
self:internalError( header .. ': ' .. info )
else
self:internalError( header )
end
end
--- Send a message to PHP
-- @param msg The message table
function MWServer:sendMessage( msg )
if not msg.op then
self:internalError( "MWServer:sendMessage: invalid message", 2 )
end
self:debug('TX ==> ' .. msg.op)
local encMsg = self:encodeMessage( msg )
local success, errorMsg = io.stdout:write( encMsg )
if not success then
self:ioError( 'Write error', errorMsg )
end
io.stdout:flush()
end
--- Wait for a message from PHP and then decode and return it as a table
-- @return The received message
function MWServer:receiveMessage()
-- Read the header
local header, errorMsg = io.stdin:read( 16 )
if header == nil and errorMsg == nil then
-- End of file on stdin, exit gracefully
os.exit(0)
end
if not header or #header ~= 16 then
self:ioError( 'Read error', errorMsg )
end
local length = self:decodeHeader( header )
-- Read the body
local body, errorMsg = io.stdin:read( length )
if not body then
self:ioError( 'Read error', errorMsg )
end
if #body ~= length then
self:ioError( 'Read error', errorMsg )
end
-- Unserialize it
msg = self:unserialize( body )
self:debug('RX <== ' .. msg.op)
if msg.op == 'error' then
self:debug( 'Error: ' .. tostring( msg.value ) )
end
return msg
end
--- Encode a message for sending to PHP
function MWServer:encodeMessage( message )
local serialized = self:serialize( message )
local length = #serialized
local check = length * 2 - 1
return string.format( '%08x%08x', length, check ) .. serialized
end
-- Faster to create the table once than for each call to MWServer:serialize()
local serialize_replacements = {
['\r'] = '\\r',
['\n'] = '\\n',
['\\'] = '\\\\',
}
--- Convert a value to a string suitable for passing to PHP's unserialize().
-- Note that the following replacements must be performed before calling
-- unserialize:
-- "\\r" => "\r"
-- "\\n" => "\n"
-- "\\\\" => "\\"
--
-- @param var The value.
function MWServer:serialize( var )
local done = {}
local int_min = -2147483648
local int_max = 2147483647
local function isInteger( var )
return type(var) == 'number'
and math.floor( var ) == var
and var >= int_min
and var <= int_max
end
local function recursiveEncode( var, level )
local t = type( var )
if t == 'nil' then
return 'N;'
elseif t == 'number' then
if isInteger(var) then
return 'i:' .. var .. ';'
elseif var < math.huge and var > -math.huge then
return 'd:' .. var .. ';'
elseif var == math.huge then
return 'd:INF;'
elseif var == -math.huge then
return 'd:-INF;'
else
return 'd:NAN;'
end
elseif t == 'string' then
return 's:' .. string.len( var ) .. ':"' .. var .. '";'
elseif t == 'boolean' then
if var then
return 'b:1;'
else
return 'b:0;'
end
elseif t == 'table' then
if done[var] then
error("Cannot pass circular reference to PHP")
end
done[var] = true
local buf = { '' }
local tmpString
local numElements = 0
for key, value in pairs(var) do
if (isInteger(key)) then
buf[#buf + 1] = 'i:' .. key .. ';'
else
tmpString = tostring( key )
buf[#buf + 1] = recursiveEncode( tostring( key ), level + 1 )
end
buf[#buf + 1] = recursiveEncode( value, level + 1 )
numElements = numElements + 1
end
buf[1] = 'a:' .. numElements .. ':{'
buf[#buf + 1] = '}'
return table.concat(buf)
elseif t == 'function' then
local id
if self.xchunks[var] then
id = self.xchunks[var]
else
id = self:addChunk(var)
end
return 'O:42:"Scribunto_LuaStandaloneInterpreterFunction":1:{s:2:"id";i:' .. id .. ';}'
elseif t == 'thread' then
error("Cannot pass thread to PHP")
elseif t == 'userdata' then
error("Cannot pass userdata to PHP")
else
error("Cannot pass unrecognised type to PHP")
end
end
return recursiveEncode( var, 0 ):gsub( '[\r\n\\]', serialize_replacements )
end
--- Convert a Lua expression string to its corresponding value.
-- Convert any references of the form chunk[id] to the corresponding function
-- values.
function MWServer:unserialize( text )
local func = loadstring( 'return ' .. text )
if not func then
self:internalError( "MWServer:unserialize: invalid chunk" )
end
-- Don't waste JIT cache space by storing every message in it
if jit then
jit.off( func )
end
setfenv( func, { chunks = self.chunks } )
return func()
end
--- Decode a message header.
-- @param header The header string
-- @return The body length
function MWServer:decodeHeader( header )
local length = string.sub( header, 1, 8 )
local check = string.sub( header, 9, 16 )
if not string.match( length, '^%x+$' ) or not string.match( check, '^%x+$' ) then
self:internalError( "Error decoding message header: " .. length .. '/' .. check )
end
length = tonumber( length, 16 )
check = tonumber( check, 16 )
if length * 2 - 1 ~= check then
self:internalError( "Error decoding message header" )
end
return length
end
--- Get a traceback similar to the one from debug.traceback(), but as a table
-- rather than formatted as a string
--
-- @param The level to start at: 1 for the function that called getStructuredTrace()
-- @return A table with the backtrace information
function MWServer:getStructuredTrace( level )
level = level + 1
local trace = {}
while true do
local rawInfo = debug.getinfo( level, 'nSl' )
if rawInfo == nil then
break
end
local info = {}
for i, key in ipairs({'short_src', 'what', 'currentline', 'name', 'namewhat', 'linedefined'}) do
info[key] = rawInfo[key]
end
if string.match( info['short_src'], '/MWServer.lua$' ) then
info['short_src'] = 'MWServer.lua'
end
if string.match( rawInfo['short_src'], '/mw_main.lua$' ) then
info['short_src'] = 'mw_main.lua'
end
table.insert( trace, info )
level = level + 1
end
return trace
end
--- Create a table to be used as a restricted environment, based on the current
-- global environment.
--
-- @return The environment table
function MWServer:newEnvironment()
local allowedGlobals = {
-- base
"assert",
"error",
"getmetatable",
"ipairs",
"next",
"pairs",
"pcall",
"rawequal",
"rawget",
"rawset",
"select",
"setmetatable",
"tonumber",
"type",
"unpack",
"xpcall",
"_VERSION",
-- libs
"table",
"math"
}
local env = {}
for i = 1, #allowedGlobals do
env[allowedGlobals[i]] = mw.clone( _G[allowedGlobals[i]] )
end
-- Cloning 'string' doesn't work right, because strings still use the old
-- 'string' as the metatable. So just copy it.
env.string = string
env._G = env
env.tostring = function( val )
return self:tostring( val )
end
env.string.dump = nil
env.setfenv, env.getfenv = mw.makeProtectedEnvFuncs(
self.protectedEnvironments, self.protectedFunctions )
env.debug = {
traceback = debug.traceback
}
env.os = {
date = os.date,
difftime = os.difftime,
time = os.time,
clock = os.clock
}
return env
end
--- An implementation of tostring() which does not expose pointers.
function MWServer:tostring(val)
local mt = getmetatable( val )
if mt and mt.__tostring then
return mt.__tostring(val)
end
local typeName = type(val)
local nonPointerTypes = {number = true, string = true, boolean = true, ['nil'] = true}
if nonPointerTypes[typeName] then
return tostring(val)
else
return typeName
end
end
return MWServer

View File

@@ -0,0 +1,8 @@
package.path = arg[1] .. '/engines/LuaStandalone/?.lua;' ..
arg[1] .. '/engines/LuaCommon/lualib/?.lua'
require('MWServer')
require('mwInit')
server = MWServer:new()
server:execute()

View File

@@ -0,0 +1,635 @@
--require('MWClient')
MWServer = {}
--- Create a new MWServer object
function MWServer:new()
obj = {
nextChunkId = 1,
chunks = {},
xchunks = {},
protectedFunctions = {},
protectedEnvironments = {},
baseEnv = {}
}
setmetatable( obj, self )
self.__index = self
obj:init()
return obj
end
--- Initialise a new MWServer object
function MWServer:init()
self.baseEnv = self:newEnvironment()
for funcName, func in pairs( self ) do
if type(func) == 'function' then
self.protectedFunctions[func] = true
end
end
self.protectedEnvironments[_G] = true
end
--- Serve requests until exit is requested
function MWServer:execute()
-- self:dispatch( nil ) // starts do / loop; not needed
self:debug( 'MWServer:execute: returning' )
end
-- Convert a multiple-return-value or a ... into a count and a table
function MWServer:listToCountAndTable( ... )
return select( '#', ... ), { ... }
end
--- Call a PHP function
-- Raise an error if the PHP handler requests it. May return any number
-- of values.
--
-- @param id The function ID, specified by a registerLibrary message
-- @param nargs Count of function arguments
-- @param args The function arguments
-- @return The return values from the PHP function
function MWServer:call( id, nargs, args )
local result = self:server_send( {
op = 'call',
id = id,
nargs = nargs,
args = args
} )
if result.op == 'return' then
return unpack( result.values, 1, result.nvalues )
elseif result.op == 'error' then
-- Raise an error in the actual user code that called the function
-- The level is 3 since our immediate caller is a closure
error( result.value, 3 )
else
self:internalError( 'MWServer:call: unexpected result op' )
end
end
--- Handle a "call" message from PHP. Call the relevant function.
--
-- @param message The message from PHP
-- @return A response message to send back to PHP
function MWServer:handleCall( message )
if not self.chunks[message.id] then
return {
op = 'error',
value = 'function id ' .. message.id .. ' does not exist'
}
end
-- print(message.id .. '|' .. message.nargs);
-- print(self.chunks[message.id]);
-- print(message.args[1]);
local n, result = self:listToCountAndTable( xpcall(
function ()
return self.chunks[message.id]( unpack( message.args, 1, message.nargs ) )
end,
function ( err )
return MWServer:attachTrace( err )
end
) )
if result[1] then
-- table.remove( result, 1 ) renumbers from 2 to #result. But #result
-- is not necessarily "right" if result contains nils.
result = { unpack( result, 2, n ) }
return {
op = 'return',
nvalues = n - 1,
values = result
}
else
if result[2].value and result[2].trace then
return {
op = 'error',
value = result[2].value,
trace = result[2].trace,
}
else
return {
op = 'error',
value = result[2]
}
end
end
end
--- The xpcall() error handler for handleCall(). Modifies the error object
-- to include a structured backtrace
--
-- @param err The error object
-- @return The new error object
function MWServer:attachTrace( err )
return {
value = err,
trace = self:getStructuredTrace( 2 )
}
end
--- Handle a "loadString" message from PHP.
-- Load the function and return a chunk ID.
--
-- @param message The message from PHP
-- @return A response message to send back to PHP
function MWServer:handleLoadString( message )
if string.find( message.text, '\27Lua', 1, true ) then
return {
op = 'error',
value = 'cannot load code with a Lua binary chunk marker escape sequence in it'
}
end
local chunk, errorMsg = loadstring( message.text, message.chunkName )
if chunk then
setfenv( chunk, self.baseEnv )
local id = self:addChunk( chunk )
return {
op = 'return',
nvalues = 1,
values = {id}
}
else
return {
op = 'error',
value = errorMsg
}
end
end
--- Add a function value to the list of tracked chunks and return its associated ID.
-- Adding a chunk allows it to be referred to in messages from PHP.
--
-- @param chunk The function value
-- @return The chunk ID
function MWServer:addChunk( chunk )
local id = self.nextChunkId
self.nextChunkId = id + 1
self.chunks[id] = chunk
self.xchunks[chunk] = id
return id
end
--- Handle a "cleanupChunks" message from PHP.
-- Remove any chunks no longer referenced by PHP code.
--
-- @param message The message from PHP
-- @return A response message to send back to PHP
function MWServer:handleCleanupChunks( message )
for id, chunk in pairs( message.ids ) do -- XOWA: different from MW, in that message has chunks to cleanup, not chunks to preserv
local chunk_key = self.chunks[id];
-- dbg(id .. ':' .. tostring(chunk_key));
self.chunks[id] = nil
self.xchunks[chunk_key] = nil
end
return {
op = 'return',
nvalues = 0,
values = {}
}
end
--- Handle a "registerLibrary" message from PHP.
-- Add the relevant functions to the base environment.
--
-- @param message The message from PHP
-- @return The response message
function MWServer:handleRegisterLibrary( message )
local startPos = 1
local component
if not self.baseEnv[message.name] then
self.baseEnv[message.name] = {}
end
local t = self.baseEnv[message.name]
for name, id in pairs( message.functions ) do
t[name] = function( ... )
return self:call( id, self:listToCountAndTable( ... ) )
end
-- Protect the function against setfenv()
self.protectedFunctions[t[name]] = true
end
return {
op = 'return',
nvalues = 0,
values = {}
}
end
--- Handle a "wrapPhpFunction" message from PHP.
-- Create an anonymous function
--
-- @param message The message from PHP
-- @return The response message
function MWServer:handleWrapPhpFunction( message )
local id = message.id
local func = function( ... )
return self:call( id, self:listToCountAndTable( ... ) )
end
-- Protect the function against setfenv()
self.protectedFunctions[func] = true
return {
op = 'return',
nvalues = 1,
values = { func }
}
end
--- Handle a "getStatus" message from PHP
--
-- @param message The request message
-- @return The response message
function MWServer:handleGetStatus( message )
local nullRet = {
op = 'return',
nvalues = 0,
values = {}
}
local file = io.open( '/proc/self/stat' )
if not file then
return nullRet
end
local s = file:read('*a')
file:close()
local t = {}
for token in string.gmatch(s, '[^ ]+') do
t[#t + 1] = token
end
if #t < 22 then
return nullRet
end
return {
op = 'return',
nvalues = 1,
values = {{
pid = tonumber(t[1]),
time = tonumber(t[14]) + tonumber(t[15]) + tonumber(t[16]) + tonumber(t[17]),
vsize = tonumber(t[23]),
}}
}
end
--- The main request/response loop
--
-- Send a request message and return its matching reply message. Handle any
-- intervening requests (i.e. re-entrant calls) by dispatching them to the
-- relevant handler function.
--
-- The request message may optionally be omitted, to listen for request messages
-- without first sending a request of its own. Such a dispatch() call will
-- continue running until termination is requested by PHP. Typically, PHP does
-- this with a SIGTERM signal.
--
-- @param msgToPhp The message to send to PHP. Optional.
-- @return The matching response message
function MWServer:server_send( send_msg )
return self:sendMessage( send_msg )
end
function MWServer:server_recv( recv_msg )
local send_msg
local op = recv_msg.op
if op == 'return' or op == 'error' then
return recv_msg
elseif op == 'call' then
return self:handleCall( recv_msg )
elseif op == 'loadString' then
return self:handleLoadString( recv_msg )
elseif op == 'registerLibrary' then
return self:handleRegisterLibrary( recv_msg )
elseif op == 'wrapPhpFunction' then
return self:handleWrapPhpFunction( recv_msg )
elseif op == 'cleanupChunks' then
return self:handleCleanupChunks( recv_msg )
elseif op == 'getStatus' then
return self:handleGetStatus( recv_msg )
elseif op == 'quit' then
self:debug( 'MWServer:dispatch: quit message received' )
os.exit(0)
else
self:internalError( "Invalid message operation" )
end
end
--- Write a message to the debug output stream.
-- Some day this may be configurable, currently it just unconditionally writes
-- the message to stderr. The PHP host will redirect those errors to /dev/null
-- by default, but it can be configured to send them to a file.
--
-- @param s The message
function MWServer:debug( s )
if ( type(s) == 'string' ) then
io.stderr:write( s .. '\n' )
else
io.stderr:write( self:serialize( s ) .. '\n' )
end
end
--- Raise an internal error
-- Write a message to stderr and then exit with a failure status. This should
-- be called for errors which cannot be allowed to be caught with pcall().
--
-- This must be used for protocol errors, or indeed any error from a context
-- where a dispatch() call lies between the error source and a possible pcall()
-- handler. If dispatch() were terminated by a regular error() call, the
-- resulting protocol violation could lead to a deadlock.
--
-- @param msg The error message
function MWServer:internalError( msg )
io.stderr:write( debug.traceback( msg ) .. '\n' )
os.exit( 1 )
end
--- Raise an I/O error
-- Helper function for errors from the io and file modules, which may optionally
-- return an informative error message as their second return value.
function MWServer:ioError( header, info )
if type( info) == 'string' then
self:internalError( header .. ': ' .. info )
else
self:internalError( header )
end
end
--- Send a message to PHP
-- @param msg The message table
function MWServer:sendMessage( msg )
if not msg.op then
self:internalError( "MWServer:sendMessage: invalid message", 2 )
end
--self:debug('TX ==> ' .. msg.op)
return MWClient.client_recv(msg)
end
--- Wait for a message from PHP and then decode and return it as a table
-- @return The received message
function MWServer:receiveMessage()
-- Read the header
local header, errorMsg = io.stdin:read( 16 )
if header == nil and errorMsg == nil then
-- End of file on stdin, exit gracefully
os.exit(0)
end
if not header or #header ~= 16 then
self:ioError( 'Read error', errorMsg )
end
local length = self:decodeHeader( header )
-- Read the body
local body, errorMsg = io.stdin:read( length )
if not body then
self:ioError( 'Read error', errorMsg )
end
if #body ~= length then
self:ioError( 'Read error', errorMsg )
end
-- Unserialize it
msg = self:unserialize( body )
self:debug('RX <== ' .. msg.op)
if msg.op == 'error' then
self:debug( 'Error: ' .. tostring( msg.value ) )
end
return msg
end
--- Encode a message for sending to PHP
function MWServer:encodeMessage( message )
local serialized = self:serialize( message )
local length = #serialized
local check = length * 2 - 1
return string.format( '%08x%08x', length, check ) .. serialized
end
-- Faster to create the table once than for each call to MWServer:serialize()
local serialize_replacements = {
['\r'] = '\\r',
['\n'] = '\\n',
['\\'] = '\\\\',
}
--- Convert a value to a string suitable for passing to PHP's unserialize().
-- Note that the following replacements must be performed before calling
-- unserialize:
-- "\\r" => "\r"
-- "\\n" => "\n"
-- "\\\\" => "\\"
--
-- @param var The value.
function MWServer:serialize( var )
local done = {}
local int_min = -2147483648
local int_max = 2147483647
local function isInteger( var )
return type(var) == 'number'
and math.floor( var ) == var
and var >= int_min
and var <= int_max
end
local function recursiveEncode( var, level )
local t = type( var )
if t == 'nil' then
return 'N;'
elseif t == 'number' then
if isInteger(var) then
return 'i:' .. var .. ';'
elseif var < math.huge and var > -math.huge then
return 'd:' .. var .. ';'
elseif var == math.huge then
return 'd:INF;'
elseif var == -math.huge then
return 'd:-INF;'
else
return 'd:NAN;'
end
elseif t == 'string' then
return 's:' .. string.len( var ) .. ':"' .. var .. '";'
elseif t == 'boolean' then
if var then
return 'b:1;'
else
return 'b:0;'
end
elseif t == 'table' then
if done[var] then
error("Cannot pass circular reference to PHP")
end
done[var] = true
local buf = { '' }
local tmpString
local numElements = 0
for key, value in pairs(var) do
if (isInteger(key)) then
buf[#buf + 1] = 'i:' .. key .. ';'
else
tmpString = tostring( key )
buf[#buf + 1] = recursiveEncode( tostring( key ), level + 1 )
end
buf[#buf + 1] = recursiveEncode( value, level + 1 )
numElements = numElements + 1
end
buf[1] = 'a:' .. numElements .. ':{'
buf[#buf + 1] = '}'
return table.concat(buf)
elseif t == 'function' then
local id
if self.xchunks[var] then
id = self.xchunks[var]
else
id = self:addChunk(var)
end
return 'O:42:"Scribunto_LuaStandaloneInterpreterFunction":1:{s:2:"id";i:' .. id .. ';}'
elseif t == 'thread' then
error("Cannot pass thread to PHP")
elseif t == 'userdata' then
error("Cannot pass userdata to PHP")
else
error("Cannot pass unrecognised type to PHP")
end
end
return recursiveEncode( var, 0 ):gsub( '[\r\n\\]', serialize_replacements )
end
--- Convert a Lua expression string to its corresponding value.
-- Convert any references of the form chunk[id] to the corresponding function
-- values.
function MWServer:unserialize( text )
local func = loadstring( 'return ' .. text )
if not func then
self:internalError( "MWServer:unserialize: invalid chunk" )
end
-- Don't waste JIT cache space by storing every message in it
if jit then
jit.off( func )
end
setfenv( func, { chunks = self.chunks } )
return func()
end
--- Decode a message header.
-- @param header The header string
-- @return The body length
function MWServer:decodeHeader( header )
local length = string.sub( header, 1, 8 )
local check = string.sub( header, 9, 16 )
if not string.match( length, '^%x+$' ) or not string.match( check, '^%x+$' ) then
self:internalError( "Error decoding message header: " .. length .. '/' .. check )
end
length = tonumber( length, 16 )
check = tonumber( check, 16 )
if length * 2 - 1 ~= check then
self:internalError( "Error decoding message header" )
end
return length
end
--- Get a traceback similar to the one from debug.traceback(), but as a table
-- rather than formatted as a string
--
-- @param The level to start at: 1 for the function that called getStructuredTrace()
-- @return A table with the backtrace information
function MWServer:getStructuredTrace( level )
level = level + 1
local trace = {}
while true do
local rawInfo = debug.getinfo( level, 'nSl' )
if rawInfo == nil then
break
end
local info = {}
for i, key in ipairs({'short_src', 'what', 'currentline', 'name', 'namewhat', 'linedefined'}) do
info[key] = rawInfo[key]
end
if string.match( info['short_src'], '/MWServer.lua$' ) then
info['short_src'] = 'MWServer.lua'
end
if string.match( rawInfo['short_src'], '/mw_main.lua$' ) then
info['short_src'] = 'mw_main.lua'
end
table.insert( trace, info )
level = level + 1
end
return trace
end
--- Create a table to be used as a restricted environment, based on the current
-- global environment.
--
-- @return The environment table
function MWServer:newEnvironment()
local allowedGlobals = {
-- base
"assert",
"error",
"getmetatable",
"ipairs",
"next",
"pairs",
"pcall",
"rawequal",
"rawget",
"rawset",
"select",
"setmetatable",
"tonumber",
"type",
"unpack",
"xpcall",
"_VERSION",
"dbg", --for debugging
-- libs
"table",
"math"
}
local env = {}
for i = 1, #allowedGlobals do
env[allowedGlobals[i]] = mw.clone( _G[allowedGlobals[i]] )
end
-- Cloning 'string' doesn't work right, because strings still use the old
-- 'string' as the metatable. So just copy it.
env.string = string
env._G = env
env.tostring = function( val )
return self:tostring( val )
end
env.string.dump = nil
env.setfenv, env.getfenv = mw.makeProtectedEnvFuncs(
self.protectedEnvironments, self.protectedFunctions )
-- env.debug = {
-- traceback = debug.traceback
-- }
env.os = {
date = os.date,
difftime = os.difftime,
time = os.time,
clock = os.clock
}
return env
end
--- An implementation of tostring() which does not expose pointers.
function MWServer:tostring(val)
local mt = getmetatable( val )
if mt and mt.__tostring then
return mt.__tostring(val)
end
local typeName = type(val)
local nonPointerTypes = {number = true, string = true, boolean = true, ['nil'] = true}
if nonPointerTypes[typeName] then
return tostring(val)
else
return typeName
end
end
return MWServer

View File

@@ -0,0 +1,9 @@
require('MWServer')
require('mwInit')
_G.getfenv = require 'compat_env'.getfenv
_G.setfenv = require 'compat_env'.setfenv
_G.loadstring = load
_G.unpack = table.unpack
server = MWServer:new()
return server

View File

@@ -0,0 +1 @@
The Luaj files are not part of the official Scribunto distribution. They were developed for XOWA and the Luaj jar